diff --git a/SavingSedit/OneLoneCoder_PGE_SavingSedit.cpp b/SavingSedit/OneLoneCoder_PGE_SavingSedit.cpp new file mode 100644 index 0000000..cd38cb8 --- /dev/null +++ b/SavingSedit/OneLoneCoder_PGE_SavingSedit.cpp @@ -0,0 +1,1509 @@ + +/* + Saving Sedit: An OLC Code Jam 2018 Submission + "At least this is checked off my list now..." - javidx9 + + Download the playable version here: https://onelonecoder.itch.io/saving-sedit + + Note: This was a JAM entry, it is incomplete, has bugs, has features that + dont go anywhere. I've not tidied up the code - it is a record of how + the JAM went. + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Relevant Video: https://youtu.be/Qco5BstCxRM + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" +#define OLC_PGE_GRAPHICS2D +#include "olcPGEX_Graphics2D.h" + +#include "olcPGEX_TileMaps_new.h" +#define OLC_PGEX_SOUND +#include "olcPGEX_Sound.h" + +#include "Zix_PGE_Controller.h" + +#include +#include +#include +#include +#include +using namespace std; + + +class cAnimator +{ +public: + std::map> mapStates; + +public: + std::string sCurrentState; + int nCurrentFrame = 0; + float fTimeBetweenFrames = 0.1f; + float fTimeCounter = 0.0f; + + + void ChangeState(std::string s) + { + if (s != sCurrentState) + { + sCurrentState = s; + fTimeCounter = 0; + nCurrentFrame = 0; + } + } + + void Update(float fElapsedTime) + { + fTimeCounter += fElapsedTime; + if (fTimeCounter >= fTimeBetweenFrames) + { + fTimeCounter -= fTimeBetweenFrames; + nCurrentFrame++; + if (nCurrentFrame >= mapStates[sCurrentState].size()) + nCurrentFrame = 0; + } + } + + void DrawSelf(olc::PixelGameEngine *pge, olc::GFX2D::Transform2D &t) + { + olc::GFX2D::DrawSprite(mapStates[sCurrentState][nCurrentFrame], t); + } + +}; + +// Override base class with your custom functionality +class Discovery : public olc::PixelGameEngine +{ +public: + Discovery() + { + sAppName = "Discovery - OLC CodeJam 2018"; + } + +private: + + int nTileSizeX = 64; + int nTileSizeY = 64; + olc::TILE::Layer layerWorld; + olc::TILE::Layer layerCollectibles; + olc::TILE::Layer layerJavid; + olc::TILE::Atlas atlasWorldMagetzub; + olc::TILE::Atlas atlasWorldJavid; + olc::TILE::Atlas atlasWorldHopson; + olc::TILE::Atlas atlasCollectibles; + + olc::ResourcePack rpPlayer; + + olc::Sprite *sprBackdropMagetzub; + olc::Sprite *sprBackdropHopson; + + olc::Sprite *sprTitleScreen; + olc::Sprite *sprStoryScreen; + olc::Sprite *sprControlScreen; + olc::Sprite *sprCreditScreen; + olc::Sprite *sprCompleteScreen; + + float fBackdropScaleX = 1.0f; + float fBackdropScaleY = 1.0f; + + olc::Sprite *sprMiniMap = nullptr; + + olc::Sprite *backBuff; + + + + cAnimator animPlayer; + ControllerManager controller; + + float fPlayerPosX = 2.0f; + float fPlayerPosY = 2.0f; + float fPlayerVelX = 0.0f; + float fPlayerVelY = 0.0f; + float fCameraPosX = 0.0f; + float fCameraPosY = 0.0f; + bool bPlayerOnGround = false; + bool bPlayerTouchingWall = false; + bool bPlayerTouchingWallOld = false; + bool bCollisionLeft = false; + float fTimeToJump = 0.0f; + float fTimeToJumpMax = 0.25f; + float fFaceDir = 1.0f; + int nFloppyDisks = 0; + float fGameTime = 0.0f; + float fGameTimeMultiplier = 1.0f; + int nHopsonTokens = 0; + int nJavidTokens = 0; + + int nKeys = 0; + float fEffectTime = 0.0f; + float fModeTime = 0.0f; + + bool bFire = false; + + list> listKeys; + + struct sBullet + { + float bx; + float by; + float vx; + bool bRemove = false; + }; + + enum + { + MODE_MAGETZUB, + MODE_JAVID, + MODE_HOPSON, + MODE_HUHLIG + + } nRenderMode; + + enum + { + EFFECT_NONE, + EFFECT_UGLYSWEDISHFISH, + EFFECT_SEDIT, + EFFECT_BRANK, + EFFECT_GORBIT, + + } nRenderEffect; + + + enum + { + GS_LOADING, + GS_TITLE, + GS_STORY, + GS_CREDITS, + GS_GENERATE, + GS_MAIN, + GS_COMPLETE, + } nGameState = GS_LOADING; + + int nChallengeMapSizeX = 8; + int nChallengeMapSizeY = 8; + int nChallengePathSizeX = 4; + int nChallengePathSizeY = 4; + + void CreateMaze(bool* &pMap, int nCellWidth, int nCellHeight, int nCellsX, int nCellsY, int &nMapWidth, int &nMapHeight) + { + int *pLevel = new int[nCellsX * nCellsY]{ 0 }; + + enum + { + CELL_PATH_N = 0x01, + CELL_PATH_E = 0x02, + CELL_PATH_S = 0x04, + CELL_PATH_W = 0x08, + CELL_VISITED = 0x10, + }; + + std::stack> m_stack; + + + auto offset = [&](int x, int y) + { + return (m_stack.top().second + y) * nCellsX + (m_stack.top().first + x); + }; + + m_stack.push(std::make_pair(0, 0)); + pLevel[0] = CELL_VISITED; + int nVisitedCells = 1; + + // Do Maze Algorithm + while (nVisitedCells < nCellsX * nCellsY) + { + // Step 1: Create a set of the unvisted neighbours + + std::vector neighbours; + + // North Neighbour + if (m_stack.top().second > 0 && (pLevel[offset(0, -1)] & CELL_VISITED) == 0) + neighbours.push_back(0); + // East neighbour + if (m_stack.top().first < nCellsX - 1 && (pLevel[offset(1, 0)] & CELL_VISITED) == 0) + neighbours.push_back(1); + // South neighbour + if (m_stack.top().second < nCellsY - 1 && (pLevel[offset(0, 1)] & CELL_VISITED) == 0) + neighbours.push_back(2); + // West neighbour + if (m_stack.top().first > 0 && (pLevel[offset(-1, 0)] & CELL_VISITED) == 0) + neighbours.push_back(3); + + + // Are there any neighbours available? + if (!neighbours.empty()) + { + // Choose one available neighbour at random + int next_cell_dir = neighbours[rand() % neighbours.size()]; + + // Create a path between the neighbour and the current cell + switch (next_cell_dir) + { + case 0: // North + pLevel[offset(0, -1)] |= CELL_VISITED | CELL_PATH_S; + pLevel[offset(0, 0)] |= CELL_PATH_N; + m_stack.push(std::make_pair((m_stack.top().first + 0), (m_stack.top().second - 1))); + break; + + case 1: // East + pLevel[offset(+1, 0)] |= CELL_VISITED | CELL_PATH_W; + pLevel[offset(0, 0)] |= CELL_PATH_E; + m_stack.push(std::make_pair((m_stack.top().first + 1), (m_stack.top().second + 0))); + break; + + case 2: // South + pLevel[offset(0, +1)] |= CELL_VISITED | CELL_PATH_N; + pLevel[offset(0, 0)] |= CELL_PATH_S; + m_stack.push(std::make_pair((m_stack.top().first + 0), (m_stack.top().second + 1))); + break; + + case 3: // West + pLevel[offset(-1, 0)] |= CELL_VISITED | CELL_PATH_E; + pLevel[offset(0, 0)] |= CELL_PATH_W; + m_stack.push(std::make_pair((m_stack.top().first - 1), (m_stack.top().second + 0))); + break; + + } + + nVisitedCells++; + } + else + { + m_stack.pop(); // Backtrack + } + } + + printf("M1 %d\n", m_stack.size()); + + // Convert Maze into tile map + nMapWidth = (nCellWidth + 1) * nCellsX + 1; + nMapHeight = (nCellHeight + 1) * nCellsY + 1; + pMap = new bool[nMapWidth * nMapHeight]{ 0 }; + printf("M1\n"); + + // Draw Maze + for (int x = 0; x < nCellsX; x++) + { + for (int y = 0; y < nCellsY; y++) + { + for (int py = 0; py < nCellHeight; py++) + for (int px = 0; px < nCellWidth; px++) + { + if (pLevel[y * nCellsX + x] & CELL_VISITED) + pMap[(y* (nCellHeight + 1) + py + 1) * nMapWidth + (x * (nCellWidth + 1) + px) + 1] = true; + else + pMap[(y* (nCellHeight + 1) + py + 1) * nMapWidth + (x * (nCellWidth + 1) + px + 1)] = false; + } + + for (int p = 0; p < nCellWidth; p++) + if (pLevel[y * nCellsX + x] & CELL_PATH_S) + pMap[(y * (nCellHeight + 1) + nCellHeight + 1) * nMapWidth + (x * (nCellWidth + 1) + p + 1)] = true; + + for (int p = 0; p < nCellHeight; p++) + if (pLevel[y * nCellsX + x] & CELL_PATH_E) + pMap[(y * (nCellHeight + 1) + p + 1) * nMapWidth + (x * (nCellWidth + 1) + nCellWidth + 1)] = true; + } + } + + printf("M1\n"); + delete[] pLevel; + printf("M1\n"); + } + + void CalculateFlowMap() + { + // Update Flow map + + int nFlowWidth = layerJavid.nLayerWidth; + int nFlowHeight = layerJavid.nLayerHeight; + + // 1) Prepare it centered on player + for (int x = 0; x < nFlowWidth; x++) + for (int y = 0; y < nFlowHeight; y++) + { + layerJavid.GetTile(x, y)->id = 2; + layerJavid.GetTile(x, y)->edge_id[0] = 0; + + if (x == 0 || x == (nFlowWidth - 1) || y == 0 || y == (nFlowHeight - 1)) + layerJavid.GetTile(x, y)->edge_id[0] = -1; + else + //layerJavid.GetTile(x, y)->exist = layerWorld.GetTile(x, y)->exist; + layerJavid.GetTile(x, y)->edge_id[0] = layerWorld.GetTile(x, y)->exist ? -1 : 0; + + + } + + + + list> nodes; + + nodes.push_back({ listKeys.back().first,listKeys.back().second, 1 }); + + while (!nodes.empty()) + { + list> new_nodes; + + // For each node in processing queue, set its count value, and add unmarked + // neighbour nodes + for (auto &n : nodes) + { + int x = get<0>(n); + int y = get<1>(n); + int d = get<2>(n); + + // Set distance for this node + layerJavid.GetTile(x, y)->edge_id[0] = d; + + // Add neigbour nodes if unmarked + if ((x + 1) < nFlowWidth && layerJavid.GetTile(x+1, y)->edge_id[0] == 0) + new_nodes.push_back({ x + 1, y, d + 1 }); + if ((x - 1) >= 0 && layerJavid.GetTile(x - 1, y)->edge_id[0] == 0) + new_nodes.push_back({ x - 1, y, d + 1}); + if ((y + 1) < nFlowHeight && layerJavid.GetTile(x, y+1)->edge_id[0] == 0) + new_nodes.push_back({ x, y + 1, d + 1 }); + if ((y - 1) >= 0 && layerJavid.GetTile(x, y-1)->edge_id[0] == 0) + new_nodes.push_back({ x, y - 1, d + 1 }); + } + + new_nodes.sort([&](const tuple &n1, const tuple &n2) + { + return (get<1>(n1) * layerJavid.nLayerWidth + get<0>(n1)) < (get<1>(n2) * layerJavid.nLayerWidth + get<0>(n2)); + }); + + new_nodes.unique([&](const tuple &n1, const tuple &n2) + { + return (get<1>(n1) * layerJavid.nLayerWidth + get<0>(n1)) == (get<1>(n2) * layerJavid.nLayerWidth + get<0>(n2)); + }); + + + nodes.clear(); + nodes.insert(nodes.begin(), new_nodes.begin(), new_nodes.end()); + } + + for (int x = 0; x < nFlowWidth; x++) + { + for (int y = 0; y < nFlowHeight; y++) + { + if (layerJavid.GetTile(x, y)->edge_id[0] > 0) + { + int flow_xa, flow_xb, flow_ya, flow_yb; + flow_xa = flow_xb = flow_ya = flow_yb = layerJavid.GetTile(x, y)->edge_id[0]; + + if ((x + 1) < nFlowWidth && layerJavid.GetTile(x+1, y)->edge_id[0] > 0) + flow_xb = layerJavid.GetTile(x + 1, y)->edge_id[0]; + + if ((x - 1) >= 0 && layerJavid.GetTile(x-1, y)->edge_id[0] > 0) + flow_xa = layerJavid.GetTile(x - 1, y)->edge_id[0]; + + if ((y + 1) < nFlowHeight && layerJavid.GetTile(x, y+1)->edge_id[0] > 0) + flow_yb = layerJavid.GetTile(x, y + 1)->edge_id[0]; + + if ((y - 1) >= 0 && layerJavid.GetTile(x, y-1)->edge_id[0] > 0) + flow_ya = layerJavid.GetTile(x, y - 1)->edge_id[0]; + + float fdx = (float)(flow_xa - flow_xb); + float fdy = (float)(flow_ya - flow_yb); + float r = 1.0f / sqrtf(fdx * fdx + fdy * fdy); + + if (fabs(fdx) > fabs(fdy)) + { + if (fdx > 0) + { + layerJavid.GetTile(x, y)->exist = true; + layerJavid.GetTile(x, y)->id = 8; + } + + if (fdx < 0) + { + layerJavid.GetTile(x, y)->exist = true; + layerJavid.GetTile(x, y)->id = 10; + } + } + else + { + if (fdy > 0) + { + layerJavid.GetTile(x, y)->exist = true; + layerJavid.GetTile(x, y)->id = 9; + } + + if (fdy < 0) + { + layerJavid.GetTile(x, y)->exist = true; + layerJavid.GetTile(x, y)->id = 7; + } + } + } + } + } + } + + void CreateBorderMap() + { + // Use OLC Standard Bordered tile atlas + for (int x = 0; x < layerWorld.nLayerWidth; x++) + { + for (int y = 0; y < layerWorld.nLayerHeight; y++) + { + int s = 0; + auto a = [&](int i, int j) + { + return (layerWorld.GetTile(i, j) && layerWorld.GetTile(i, j)->exist); + }; + + if (a(x, y)) + { + s |= a(x - 1, y + 0) ? 1 : 0; s <<= 1; + s |= a(x - 1, y + 1) ? 1 : 0; s <<= 1; + s |= a(x + 0, y + 1) ? 1 : 0; s <<= 1; + s |= a(x + 1, y + 1) ? 1 : 0; s <<= 1; + s |= a(x + 1, y + 0) ? 1 : 0; s <<= 1; + s |= a(x + 1, y - 1) ? 1 : 0; s <<= 1; + s |= a(x - 0, y - 1) ? 1 : 0; s <<= 1; + s |= a(x - 1, y - 1) ? 1 : 0; + + int ix = s % 16; + int iy = s / 16; + layerWorld.GetTile(x, y)->id = (iy * 256 + 64) + (ix * 4) + 1; + } + else + layerWorld.GetTile(x, y)->id = 0; + } + } + } + +public: + + int sndHelperChange; + int sndJump; + int sndWall; + int sndGithubPatch; + int sndKeyCollect; + int sndDiskCollect; + int sndThump; + int sndTheme; + + + + bool OnUserCreate() override + { + controller.Initialize(); + backBuff = new olc::Sprite(ScreenWidth(), ScreenHeight()); + + olc::SOUND::InitialiseAudio(); + + animPlayer.ChangeState("idle"); + return true; + } + + bool OnUserDestroy() + { + olc::SOUND::DestroyAudio(); + return true; + } + + bool bFirstFrameLoading = true; + + bool GameState_Loading(float fElapsedTime) + { + + + + if (bFirstFrameLoading) + { + Clear(olc::BLACK); + DrawString(60, 240, "- Loading, Please Wait - ", olc::WHITE, 2); + bFirstFrameLoading = false; + + return true; + } + + rpPlayer.LoadPack("./discres/savingsedit.olcdat"); + + sndHelperChange = olc::SOUND::LoadAudioSample("E:\\linshare\\olcSimpleGameEngine\\discres\\PP_Negative_Trigger_1_2.wav", &rpPlayer); + sndJump = olc::SOUND::LoadAudioSample("E:\\linshare\\olcSimpleGameEngine\\discres\\PP_Jump_1_5.wav", &rpPlayer); + sndWall = olc::SOUND::LoadAudioSample("E:\\linshare\\olcSimpleGameEngine\\discres\\LQ_Back_Button.wav", &rpPlayer); + sndThump = olc::SOUND::LoadAudioSample("E:\\linshare\\olcSimpleGameEngine\\discres\\PP_Small_Impact_1_1.wav", &rpPlayer); + sndGithubPatch = olc::SOUND::LoadAudioSample("E:\\linshare\\olcSimpleGameEngine\\discres\\PP_Skill_Unlock.wav", &rpPlayer); + sndKeyCollect = olc::SOUND::LoadAudioSample("E:\\linshare\\olcSimpleGameEngine\\discres\\FA_Special_Item_2_1.wav", &rpPlayer); + sndDiskCollect = olc::SOUND::LoadAudioSample("E:\\linshare\\olcSimpleGameEngine\\discres\\FA_Funny_Impact_1_3.wav", &rpPlayer); + sndTheme = olc::SOUND::LoadAudioSample("E:\\linshare\\olcSimpleGameEngine\\discres\\Funky Chill 2 loop.wav", &rpPlayer); + + olc::SOUND::PlaySample(sndHelperChange); + + // Load Game Resources + // Define OLC Standard atlas + //atlasWorldMagetzub.Create(new olc::Sprite());// new olc::Sprite("thruster_landscape_all3_neo.png")); + //atlasWorldMagetzub.sprTileSheet->SaveToPGESprFile("discres\\discovery_magetzub_64x64.olcspr"); + //atlasWorldMagetzub.sprTileSheet->LoadFromPGESprFile("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_magetzub_64x64.olcspr"); + atlasWorldMagetzub.Create(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_magetzub_64x64.olcspr", &rpPlayer)); + + //atlasWorldJavid.Create(new olc::Sprite());//new olc::Sprite("discovery_javid_64x64.png")); + //atlasWorldJavid.sprTileSheet->SaveToPGESprFile("discres\\discovery_javid_64x64.olcspr"); + //atlasWorldJavid.sprTileSheet->LoadFromPGESprFile("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_javid_64x64.olcspr"); + atlasWorldJavid.Create(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_javid_64x64.olcspr", &rpPlayer)); + + //atlasWorldHopson.Create(new olc::Sprite());//new olc::Sprite("discovery_hopson_64x64.png")); + //atlasWorldHopson.sprTileSheet->SaveToPGESprFile("discres\\discovery_hopson_64x64.olcspr"); + atlasWorldHopson.Create(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_hopson_64x64.olcspr", &rpPlayer)); + //atlasWorldHopson.sprTileSheet->LoadFromPGESprFile("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_hopson_64x64.olcspr"); + for (int i = 0; i < 64; i++) + for (int j = 0; j < 64; j++) + { + //atlasWorld.location.emplace_back(j * 16, i * 16, 16, 16); + atlasWorldMagetzub.location.emplace_back(j * 64, i * 64, 64, 64); + atlasWorldJavid.location.emplace_back(j * 64, i * 64, 64, 64); + atlasWorldHopson.location.emplace_back(j * 64, i * 64, 64, 64); + } + + //atlasCollectibles.Create(new olc::Sprite());// "discovery_collect1.png")); + //atlasCollectibles.sprTileSheet->SaveToPGESprFile("discres\\discovery_collectibles.olcspr"); + //atlasCollectibles.sprTileSheet->LoadFromPGESprFile("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_collectibles.olcspr"); + atlasCollectibles.Create(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_collectibles.olcspr", &rpPlayer)); + atlasCollectibles.location.emplace_back(0, 0, 16, 16); // Blank + atlasCollectibles.location.emplace_back(16, 0, 16, 16); // Disk + atlasCollectibles.location.emplace_back(32, 0, 16, 16); // Exit + atlasCollectibles.location.emplace_back(48, 0, 16, 16); // Key + atlasCollectibles.location.emplace_back(64, 0, 16, 16); // Javid + atlasCollectibles.location.emplace_back(80, 0, 16, 16); // Github + atlasCollectibles.location.emplace_back(96, 0, 16, 16); // Hopson + atlasCollectibles.location.emplace_back(112, 0, 16, 16); // Up + atlasCollectibles.location.emplace_back(128, 0, 16, 16); // Right + atlasCollectibles.location.emplace_back(144, 0, 16, 16); // Down + atlasCollectibles.location.emplace_back(160, 0, 16, 16); // Left + + + + + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_000.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_001.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_002.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_003.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_004.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_005.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_006.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_007.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_008.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_009.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_010.olcspr", &rpPlayer)); + animPlayer.mapStates["idle"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Idle_011.olcspr", &rpPlayer)); + + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_000.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_001.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_002.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_003.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_004.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_005.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_006.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_007.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_008.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_009.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_010.olcspr", &rpPlayer)); + animPlayer.mapStates["idle_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_IdleFire_011.olcspr", &rpPlayer)); + + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_000.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_001.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_002.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_003.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_004.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_005.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_006.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_007.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_008.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_009.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_010.olcspr", &rpPlayer)); + animPlayer.mapStates["run"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Run_011.olcspr", &rpPlayer)); + + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_000.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_001.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_002.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_003.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_004.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_005.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_006.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_007.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_008.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_009.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_010.olcspr", &rpPlayer)); + animPlayer.mapStates["run_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_RunFire_011.olcspr", &rpPlayer)); + + animPlayer.mapStates["jump"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Jump_000.olcspr", &rpPlayer)); + animPlayer.mapStates["jump"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Jump_001.olcspr", &rpPlayer)); + animPlayer.mapStates["jump"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Jump_002.olcspr", &rpPlayer)); + animPlayer.mapStates["jump"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Jump_003.olcspr", &rpPlayer)); + animPlayer.mapStates["jump"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Jump_004.olcspr", &rpPlayer)); + animPlayer.mapStates["jump"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Jump_005.olcspr", &rpPlayer)); + + animPlayer.mapStates["fall"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Fall_000.olcspr", &rpPlayer)); + animPlayer.mapStates["fall"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Fall_001.olcspr", &rpPlayer)); + animPlayer.mapStates["fall"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Fall_002.olcspr", &rpPlayer)); + animPlayer.mapStates["fall"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Fall_003.olcspr", &rpPlayer)); + animPlayer.mapStates["fall"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Fall_004.olcspr", &rpPlayer)); + animPlayer.mapStates["fall"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_Fall_005.olcspr", &rpPlayer)); + + animPlayer.mapStates["air_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_AirFire_000.olcspr", &rpPlayer)); + animPlayer.mapStates["air_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_AirFire_001.olcspr", &rpPlayer)); + animPlayer.mapStates["air_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_AirFire_002.olcspr", &rpPlayer)); + animPlayer.mapStates["air_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_AirFire_003.olcspr", &rpPlayer)); + animPlayer.mapStates["air_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_AirFire_004.olcspr", &rpPlayer)); + animPlayer.mapStates["air_fire"].push_back(new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\Player_AirFire_005.olcspr", &rpPlayer)); + + //sprBackdropMagetzub = new olc::Sprite("discovery_magetzub_bg.png"); + //sprBackdropHopson = new olc::Sprite("discovery_hopson_bg.png"); + //sprTitleScreen = new olc::Sprite("discovery_titlescreen.png"); + //sprStoryScreen = new olc::Sprite("discovery_story.png"); + //sprCreditScreen = new olc::Sprite("discovery_credits.png"); + //sprControlScreen = new olc::Sprite("discovery_story2.png"); + //sprCompleteScreen = new olc::Sprite("discovery_title_bg.png"); + + //sprBackdropMagetzub->SaveToPGESprFile("discres\\discovery_magetzub_bg.olcspr"); + //sprBackdropHopson->SaveToPGESprFile("discres\\discovery_hopson_bg.olcspr"); + //sprTitleScreen->SaveToPGESprFile("discres\\discovery_titlescreen.olcspr"); + //sprStoryScreen->SaveToPGESprFile("discres\\discovery_story.olcspr"); + //sprCreditScreen->SaveToPGESprFile("discres\\discovery_credits.olcspr"); + //sprControlScreen->SaveToPGESprFile("discres\\discovery_story2.olcspr"); + //sprCompleteScreen->SaveToPGESprFile("discres\\discovery_title_bg.olcspr"); + + sprBackdropMagetzub = new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_magetzub_bg.olcspr", &rpPlayer); + sprBackdropHopson = new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_hopson_bg.olcspr", &rpPlayer); + sprTitleScreen = new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_titlescreen.olcspr", &rpPlayer); + sprStoryScreen = new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_story.olcspr", &rpPlayer); + sprCreditScreen = new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_credits.olcspr", &rpPlayer); + sprControlScreen = new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_story2.olcspr", &rpPlayer); + sprCompleteScreen = new olc::Sprite("E:\\linshare\\olcSimpleGameEngine\\discres\\discovery_title_bg.olcspr", &rpPlayer); + + rpPlayer.ClearPack(); + + nGameState = GS_TITLE; + + return true; + } + + int nTitleSelection = 0; + int nLevelSeed = 1; + int nLevelSize = 2; + + bool GameState_Title(float fElapsedTime) + { + controller.Update(fElapsedTime); + + DrawSprite(0, 0, sprTitleScreen); + + DrawString(300, 280, "Story & Help", nTitleSelection == 0 ? olc::WHITE : olc::YELLOW, 2); + DrawString(300, 310, "Credits", nTitleSelection == 1 ? olc::WHITE : olc::YELLOW, 2); + DrawString(300, 340, "Level: " + to_string(nLevelSeed), nTitleSelection == 2 ? olc::WHITE : olc::YELLOW, 2); + DrawString(300, 370, "Size: " + to_string((int)pow(2, (nLevelSize+2))), nTitleSelection == 3 ? olc::WHITE : olc::YELLOW, 2); + DrawString(300, 400, "Start", nTitleSelection == 4 ? olc::WHITE : olc::YELLOW, 2); + + if (GetKey(olc::Key::UP).bPressed || controller.GetButton(UP).bPressed) + { + nTitleSelection--; + if (nTitleSelection < 0) nTitleSelection = 4; + olc::SOUND::PlaySample(sndThump); + } + + if (GetKey(olc::Key::DOWN).bPressed || controller.GetButton(DOWN).bPressed) + { + nTitleSelection++; + if (nTitleSelection > 4 ) nTitleSelection = 0; + olc::SOUND::PlaySample(sndThump); + } + + if (GetKey(olc::Key::LEFT).bPressed || controller.GetButton(LEFT).bPressed) + { + if (nTitleSelection == 2) + { + nLevelSeed--; + if (nLevelSeed < 0) nLevelSeed = 999; + } + + if (nTitleSelection == 3) + { + nLevelSize--; + if (nLevelSize < 1) nLevelSize = 3; + } + } + + if (GetKey(olc::Key::RIGHT).bPressed || controller.GetButton(RIGHT).bPressed) + { + if (nTitleSelection == 2) + { + nLevelSeed++; + if (nLevelSeed > 999) nLevelSeed = 0; + } + + if (nTitleSelection == 3) + { + nLevelSize++; + if (nLevelSize > 3) nLevelSize = 1; + } + } + + if (GetKey(olc::Key::SPACE).bPressed || controller.GetButton(A).bPressed) + { + if (nTitleSelection == 0) nGameState = GS_STORY; + if (nTitleSelection == 1) nGameState = GS_CREDITS; + + if (nTitleSelection == 4) nGameState = GS_GENERATE; + } + + return true; + } + + bool GameState_Generating(float fElapsedTime) + { + fPlayerPosX = 2.0f; + fPlayerPosY = 2.0f; + fPlayerVelX = 0.0f; + fPlayerVelY = 0.0f; + fCameraPosX = 0.0f; + fCameraPosY = 0.0f; + bPlayerOnGround = false; + bPlayerTouchingWall = false; + bPlayerTouchingWallOld = false; + bCollisionLeft = false; + fTimeToJump = 0.0f; + fTimeToJumpMax = 0.25f; + fFaceDir = 1.0f; + nFloppyDisks = 0; + fGameTime = 0.0f; + fGameTimeMultiplier = 1.0f; + nHopsonTokens = 0; + nJavidTokens = 0; + nKeys = 0; + fEffectTime = 0.0f; + fModeTime = 0.0f; + + nChallengeMapSizeX = (int)pow(2, nLevelSize + 2); + nChallengeMapSizeY = (int)pow(2, nLevelSize + 2); + srand(nLevelSeed); + + + // Generate a boolean maze + bool *bTileMaze = nullptr; + int nMapWidth, nMapHeight; + printf("D\n"); + CreateMaze(bTileMaze, nChallengePathSizeX - 1, nChallengePathSizeY - 1, nChallengeMapSizeX, nChallengeMapSizeY, nMapWidth, nMapHeight); + + if (sprMiniMap != nullptr) delete sprMiniMap; + sprMiniMap = new olc::Sprite(nChallengeMapSizeX, nChallengeMapSizeY); + printf("D\n"); + // Create tilemap to match dimensions of generated maze + layerWorld.Create(nMapWidth, nMapHeight, nTileSizeX, nTileSizeY); + layerCollectibles.Create(nMapWidth, nMapHeight, nTileSizeX, nTileSizeY); + layerJavid.Create(nMapWidth, nMapHeight, nTileSizeX, nTileSizeY); + + printf("D\n"); + // Transfer over boolean maze to tilemap + for (int x = 0; x < layerWorld.nLayerWidth; x++) + for (int y = 0; y < layerWorld.nLayerHeight; y++) + { + layerWorld.GetTile(x, y)->exist = !bTileMaze[y*layerWorld.nLayerWidth + x]; + } + + printf("D\n"); + CreateBorderMap(); + + printf("D\n"); + delete[] bTileMaze; + printf("D\n"); + listKeys.clear(); + printf("D\n"); + // Add 100 Drops + for (int i = 0; i < 136; i++) + { + bool bDone = false; + do + { + int x = rand() % layerWorld.nLayerWidth; + int y = rand() % layerWorld.nLayerHeight; + if (!layerWorld.GetTile(x, y)->exist && !layerCollectibles.GetTile(x, y)->exist) + { + + layerCollectibles.GetTile(x, y)->exist = true; + if (i < 100) layerCollectibles.GetTile(x, y)->id = 1; // Place disks + if (i == 100) // Place Exit + { + layerCollectibles.GetTile(x, y)->id = 2; listKeys.push_back(make_pair(x, y)); + } + if (i > 100 && i <= 104)// Place Keys + { + layerCollectibles.GetTile(x, y)->id = 3; listKeys.push_back(make_pair(x, y)); + } + if (i > 105 && i <= 125) layerCollectibles.GetTile(x, y)->id = 5; // Place Githubs + if (i > 125 && i <= 130) layerCollectibles.GetTile(x, y)->id = 4; // Place Javids + if (i > 130 && i <= 135) layerCollectibles.GetTile(x, y)->id = 6; // Place Hopsons + bDone = true; + } + + } while (!bDone); + } + + olc::SOUND::PlaySample(sndTheme, true); + nGameState = GS_MAIN; + printf("Entering Main\n"); + + return true; + } + + bool GameState_Main(float fElapsedTime) + { + fGameTime += fElapsedTime * fGameTimeMultiplier; + + controller.Update(fElapsedTime); + animPlayer.Update(fElapsedTime); + + bool bStartWallJump = bPlayerTouchingWall & !bPlayerTouchingWallOld; + bPlayerTouchingWallOld = bPlayerTouchingWall; + + if (bStartWallJump) + { + fTimeToJump = fTimeToJumpMax; + olc::SOUND::PlaySample(sndWall); + } + + if (fTimeToJump >= 0.0f) + fTimeToJump -= fElapsedTime; + + bPlayerTouchingWall = false; + + // Handle Input + if (IsFocused()) + { + /*if (GetKey(olc::Key::F1).bReleased) nRenderMode = MODE_MAGETZUB; + if (GetKey(olc::Key::F2).bReleased) { + nRenderMode = MODE_JAVID; fModeTime = 500.0f; CalculateFlowMap(); + } + if (GetKey(olc::Key::F3).bReleased) nRenderMode = MODE_HOPSON; + + + if (GetKey(olc::Key::F5).bReleased) nRenderEffect = EFFECT_NONE; + if (GetKey(olc::Key::F6).bReleased) nRenderEffect = EFFECT_UGLYSWEDISHFISH; + if (GetKey(olc::Key::F7).bReleased) nRenderEffect = EFFECT_GORBIT; + if (GetKey(olc::Key::F8).bReleased) nRenderEffect = EFFECT_SEDIT;*/ + + /*if (GetKey(olc::Key::UP).bHeld || controller.GetButton(UP).bHeld) + { + fPlayerVelY = -2.0f; + } + + if (GetKey(olc::Key::DOWN).bHeld || controller.GetButton(DOWN).bHeld) + { + fPlayerVelY = 2.0f; + }*/ + + if (GetKey(olc::Key::LEFT).bHeld || controller.GetButton(LEFT).bHeld) + { + fPlayerVelX += (bPlayerOnGround ? -25.0f : -15.0f) *fElapsedTime; + fFaceDir = -1.0f; + } + + if (GetKey(olc::Key::RIGHT).bHeld || controller.GetButton(RIGHT).bHeld) + { + fPlayerVelX += (bPlayerOnGround ? 25.0f : 15.0f) *fElapsedTime; + fFaceDir = +1.0f; + } + + if (GetKey(olc::Key::SPACE).bPressed || controller.GetButton(A).bPressed) + { + if (bPlayerOnGround) + { + fPlayerVelY = -12.0f; + olc::SOUND::PlaySample(sndJump); + } + + if (fTimeToJump >= 0.0f) + { + if (fPlayerVelX < 0 && !bCollisionLeft) + { + fPlayerVelY = -12.0f; + olc::SOUND::PlaySample(sndJump); + } + + if (fPlayerVelX > 0 && bCollisionLeft) + { + fPlayerVelY = -12.0f; + olc::SOUND::PlaySample(sndJump); + } + } + } + + + bFire = false; + if (GetKey(olc::Key::A).bPressed || controller.GetButton(X).bPressed) + { + //bFire = true; + + if (nRenderMode == MODE_HOPSON) + { + if (fFaceDir < 0) + { + int bx = fPlayerPosX; + int by = fPlayerPosY + 0.5f; + if (bx > 0 && bx < (layerWorld.nLayerWidth - 1) && by > 0 && by < (layerWorld.nLayerHeight - 1) && layerWorld.GetTile(bx, by)->exist) + { + layerWorld.GetTile(bx, by)->exist = false; + CreateBorderMap(); + } + } + else + { + int bx = fPlayerPosX + 1.0f; + int by = fPlayerPosY + 0.5f; + if (bx > 0 && bx < (layerWorld.nLayerWidth - 1) && by > 0 && by < (layerWorld.nLayerHeight - 1) && layerWorld.GetTile(bx, by)->exist) + { + layerWorld.GetTile(bx, by)->exist = false; + CreateBorderMap(); + } + + } + } + } + + if (GetKey(olc::Key::J).bPressed || controller.GetButton(Y).bPressed) + { + if (nRenderMode != MODE_JAVID && nJavidTokens > 0) + { + nJavidTokens--; + nRenderMode = MODE_JAVID; + fModeTime = 60.0f; + CalculateFlowMap(); + olc::SOUND::PlaySample(sndHelperChange); + } + } + + if (GetKey(olc::Key::H).bPressed || controller.GetButton(B).bPressed) + { + if (nRenderMode != MODE_HOPSON && nHopsonTokens > 0) + { + nHopsonTokens--; + nRenderMode = MODE_HOPSON; + fModeTime = 30.0f; + olc::SOUND::PlaySample(sndHelperChange); + } + } + } + + + fPlayerVelY += 20.0f * fElapsedTime; + + if (bPlayerOnGround) + { + + fPlayerVelX += -3.0f * fPlayerVelX * fElapsedTime; + if (fabs(fPlayerVelX) < 0.01f) + { + fPlayerVelX = 0.0f; + animPlayer.ChangeState(bFire ? "idle_fire" : "idle"); + } + else + { + animPlayer.ChangeState(bFire ? "run_fire" : "run"); + } + + } + else + { + if (!bFire) + { + if (fPlayerVelY < 0) + animPlayer.ChangeState("jump"); + else + animPlayer.ChangeState("fall"); + } + else + animPlayer.ChangeState("air_fire"); + } + + + if (layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id == 1) // Disk + { + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id = 0; + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->exist = false; + nFloppyDisks++; + olc::SOUND::PlaySample(sndDiskCollect); + } + + if (layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id == 2) // Exit + { + if (nKeys == 4) + { + // Game Completed + nGameState = GS_COMPLETE; + } + } + + if (layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id == 3) // Keys + { + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id = 0; + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->exist = false; + nKeys++; + + + // Remove key from list + listKeys.remove_if([&](pair &p) {return p.first == ((int)(fPlayerPosX + 0.5f)) && p.second == ((int)(fPlayerPosY + 0.5f)); }); + CalculateFlowMap(); + olc::SOUND::PlaySample(sndKeyCollect); + } + + if (layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id == 4) // Javid + { + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id = 0; + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->exist = false; + nJavidTokens++; + + //fModeTime = 30.0f; + //nRenderMode = MODE_JAVID; + + //if (nRenderMode == MODE_JAVID) + //{ + // // Javid is helpful and likes to simplify so help out the user + // CalculateFlowMap(); + //} + olc::SOUND::PlaySample(sndKeyCollect); + } + + if (layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id == 6) // Hopson + { + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id = 0; + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->exist = false; + nHopsonTokens++; + + /*fModeTime = 30.0f; + nRenderMode = MODE_HOPSON;*/ + olc::SOUND::PlaySample(sndKeyCollect); + } + + if (layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id == 5) // Github + { + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->id = 0; + layerCollectibles.GetTile(fPlayerPosX + 0.5f, fPlayerPosY + 0.5f)->exist = false; + fEffectTime = 10.0f; + switch (rand() % 4) + { + case 0: nRenderEffect = EFFECT_UGLYSWEDISHFISH; break; + case 1: nRenderEffect = EFFECT_SEDIT; break; + case 2: nRenderEffect = EFFECT_GORBIT; break; + case 3: nRenderEffect = EFFECT_BRANK; break; + } + fGameTimeMultiplier = 0.75f; + olc::SOUND::PlaySample(sndGithubPatch); + } + + if (fEffectTime > 0.0f) + { + fEffectTime -= fElapsedTime; + } + else + { + nRenderEffect = EFFECT_NONE; + fGameTimeMultiplier = 1.0f; + } + + + if (fModeTime > 0.0f) + { + fModeTime -= fElapsedTime; + } + else + { + nRenderMode = MODE_MAGETZUB; + //olc::SOUND::PlaySample(sndHelperChange); + } + + + if (fPlayerVelX > 10.0f) + fPlayerVelX = 10.0f; + + if (fPlayerVelX < -10.0f) + fPlayerVelX = -10.0f; + + if (fPlayerVelY > 100.0f) + fPlayerVelY = 100.0f; + + if (fPlayerVelY < -100.0f) + fPlayerVelY = -100.0f; + + sprMiniMap->SetPixel(fPlayerPosX / nChallengePathSizeX, fPlayerPosY / nChallengePathSizeY, olc::GREEN); + + float fNewPlayerPosX = fPlayerPosX + fPlayerVelX * fElapsedTime; + float fNewPlayerPosY = fPlayerPosY + fPlayerVelY * fElapsedTime; + + sprMiniMap->SetPixel(fNewPlayerPosX / nChallengePathSizeX, fNewPlayerPosY / nChallengePathSizeY, olc::WHITE); + + // Collision + float fOffset = 0.2f; + if (fPlayerVelX <= 0) + { + if (layerWorld.GetTile(fNewPlayerPosX + 0.0f + fOffset, fPlayerPosY + 0.0f)->exist || layerWorld.GetTile(fNewPlayerPosX + 0.0f + fOffset, fPlayerPosY + 0.9f)->exist) + { + fNewPlayerPosX = (int)fNewPlayerPosX + 1 - fOffset; + fPlayerVelX = 0; + bPlayerTouchingWall = true; + bCollisionLeft = true; + } + } + else + { + if (layerWorld.GetTile(fNewPlayerPosX + 1.0f - fOffset, fPlayerPosY + 0.0f)->exist || layerWorld.GetTile(fNewPlayerPosX + 1.0f - fOffset, fPlayerPosY + 0.9f)->exist) + { + fNewPlayerPosX = (int)fNewPlayerPosX + fOffset; + fPlayerVelX = 0; + bPlayerTouchingWall = true; + bCollisionLeft = false; + } + } + + bPlayerOnGround = false; + if (fPlayerVelY <= 0) + { + if (layerWorld.GetTile(fNewPlayerPosX + 0.0f + fOffset, fNewPlayerPosY)->exist || layerWorld.GetTile(fNewPlayerPosX + 0.9f - fOffset, fNewPlayerPosY)->exist) + { + fNewPlayerPosY = (int)fNewPlayerPosY + 1; + fPlayerVelY = 0; + } + } + else + { + if (layerWorld.GetTile(fNewPlayerPosX + 0.0f + fOffset, fNewPlayerPosY + 1.0f)->exist || layerWorld.GetTile(fNewPlayerPosX + 0.9f - fOffset, fNewPlayerPosY + 1.0f)->exist) + { + fNewPlayerPosY = (int)fNewPlayerPosY; + fPlayerVelY = 0; + bPlayerOnGround = true; + + } + } + + fPlayerPosX = fNewPlayerPosX; + fPlayerPosY = fNewPlayerPosY; + + fCameraPosX = fPlayerPosX; + fCameraPosY = fPlayerPosY; + + int nVisibleTilesX = ScreenWidth() / nTileSizeX; + int nVisibleTilesY = ScreenHeight() / nTileSizeY; + + // Calculate Top-Leftmost visible tile + float fOffsetX = fCameraPosX - (float)nVisibleTilesX / 2.0f; + float fOffsetY = fCameraPosY - (float)nVisibleTilesY / 2.0f; + + + // Clamp camera to game boundaries + if (fOffsetX < 0) fOffsetX = 0; + if (fOffsetY < 0) fOffsetY = 0; + if (fOffsetX > layerWorld.nLayerWidth - nVisibleTilesX) fOffsetX = layerWorld.nLayerWidth - nVisibleTilesX; + if (fOffsetY > layerWorld.nLayerHeight - nVisibleTilesY) fOffsetY = layerWorld.nLayerHeight - nVisibleTilesY; + + + + + + SetDrawTarget(backBuff); + //Clear(olc::VERY_DARK_BLUE); + + + if (nRenderMode == MODE_MAGETZUB) + { + fBackdropScaleX = (float)(sprBackdropMagetzub->width - ScreenWidth()) / (float)((layerWorld.nLayerWidth) + (float)nVisibleTilesX); + fBackdropScaleY = (float)(sprBackdropMagetzub->height - ScreenHeight()) / (float)((layerWorld.nLayerHeight) + (float)nVisibleTilesY); + DrawPartialSprite(0, 0, sprBackdropMagetzub, fOffsetX * fBackdropScaleX, fOffsetY * fBackdropScaleY, ScreenWidth(), ScreenHeight()); + + + SetPixelMode(olc::Pixel::ALPHA); + olc::TILE::DrawLayer(layerWorld, atlasWorldMagetzub, fOffsetX, fOffsetY, nVisibleTilesX + 1, nVisibleTilesY + 1, 1); + olc::TILE::DrawLayer(layerCollectibles, atlasCollectibles, fOffsetX, fOffsetY, nVisibleTilesX + 1, nVisibleTilesY + 1, 3); + SetPixelMode(olc::Pixel::NORMAL); + } + + if (nRenderMode == MODE_HOPSON) + { + fBackdropScaleX = (float)(sprBackdropHopson->width - ScreenWidth()) / (float)((layerWorld.nLayerWidth) + (float)nVisibleTilesX); + fBackdropScaleY = (float)(sprBackdropHopson->height - ScreenHeight()) / (float)((layerWorld.nLayerHeight) + (float)nVisibleTilesY); + DrawPartialSprite(0, 0, sprBackdropHopson, fOffsetX * fBackdropScaleX, fOffsetY * fBackdropScaleY, ScreenWidth(), ScreenHeight()); + + + SetPixelMode(olc::Pixel::ALPHA); + olc::TILE::DrawLayer(layerWorld, atlasWorldHopson, fOffsetX, fOffsetY, nVisibleTilesX + 1, nVisibleTilesY + 1, 1); + olc::TILE::DrawLayer(layerCollectibles, atlasCollectibles, fOffsetX, fOffsetY, nVisibleTilesX + 1, nVisibleTilesY + 1, 3); + SetPixelMode(olc::Pixel::NORMAL); + } + + if (nRenderMode == MODE_JAVID) + { + Clear(olc::BLACK); + SetPixelMode(olc::Pixel::ALPHA); + olc::TILE::DrawLayer(layerWorld, atlasWorldJavid, fOffsetX, fOffsetY, nVisibleTilesX + 1, nVisibleTilesY + 1, 1); + + olc::TILE::DrawLayer(layerJavid, atlasCollectibles, fOffsetX, fOffsetY, nVisibleTilesX + 1, nVisibleTilesY + 1, 3); + + olc::TILE::DrawLayer(layerCollectibles, atlasCollectibles, fOffsetX, fOffsetY, nVisibleTilesX + 1, nVisibleTilesY + 1, 3); + SetPixelMode(olc::Pixel::NORMAL); + } + + // Draw Player + + olc::GFX2D::Transform2D t; + t.Translate(-220.0f, 174.0f); + t.Scale(fFaceDir * 0.25f, 0.25f); + + t.Translate((fPlayerPosX - fOffsetX) * nTileSizeX + 32, (fPlayerPosY - fOffsetY) * nTileSizeY - 48); + + SetPixelMode(olc::Pixel::ALPHA); + animPlayer.DrawSelf(this, t); + SetPixelMode(olc::Pixel::NORMAL); + + + //this_thread::sleep_for(20ms); + + string s = "Disks: " + to_string(nFloppyDisks) + "/100\nJx9: " + to_string(nJavidTokens) + " Hop: " + to_string(nHopsonTokens) + "\nKeys: " + to_string(nKeys) + " /4"; + + + + DrawString(11, 11, s, olc::BLACK, 2); + DrawString(9, 9, s, olc::YELLOW, 2); + + SetPixelBlend(0.5f); + SetPixelMode(olc::Pixel::ALPHA); + DrawSprite(10, ScreenHeight() - (sprMiniMap->height + 4) * 3, sprMiniMap, 3); + SetPixelMode(olc::Pixel::NORMAL); + SetPixelBlend(1.0f); + + //Draw(10 + (fNewPlayerPosX / nChallengePathSizeX), ScreenHeight() - sprMiniMap->height - 8 + (fNewPlayerPosY / nChallengePathSizeY), olc::YELLOW); + + //DrawString(10, ScreenHeight() - (sprMiniMap->height - 16) * 2, "Map", olc::BLACK, 2); + //DrawString(9, ScreenHeight() - (sprMiniMap->height - 17) * 2, "Map", olc::YELLOW, 2); + + // Draw Time Information + int minutes = (int)fGameTime / 60; + int seconds = (int)fGameTime - (minutes * 60); + string sMinutes = (minutes <= 9 ? "0" : "") + to_string(minutes); + string sSeconds = (seconds <= 9 ? "0" : "") + to_string(seconds); + DrawString(ScreenWidth() - 158, 12, sMinutes + ":" + sSeconds, olc::BLACK, 3); + DrawString(ScreenWidth() - 160, 10, sMinutes + ":" + sSeconds, olc::YELLOW, 3); + + if (fGameTimeMultiplier != 1.0f) + { + DrawString(ScreenWidth() - 158, 50, "x0.75", olc::BLACK, 4); + DrawString(ScreenWidth() - 160, 53, "x0.75", olc::YELLOW, 4); + } + + SetDrawTarget(nullptr); + + + // UglySwedishFisk Mode + if (nRenderEffect == EFFECT_UGLYSWEDISHFISH) + { + int sparkles = 100 / fElapsedTime; + for (int i = 0; i < sparkles; i++) + { + int x = rand() % ScreenWidth(); + int y = rand() % ScreenHeight(); + Draw(x, y, backBuff->GetPixel(x, y)); + } + } + + if (nRenderEffect == EFFECT_GORBIT) + { + int sparkles = 10 / fElapsedTime; + for (int i = 0; i < sparkles; i++) + { + int x = rand() % 32; + int y = rand() % 32; + DrawPartialSprite(x * 32, y * 32, backBuff, x * 32, y * 32, 32, 32); + } + } + + + if (nRenderEffect == EFFECT_SEDIT) + { + SetPixelMode([](int x, int y, const olc::Pixel &s, const olc::Pixel &d) + { + olc::Pixel p = s; + if (y % 2) + { + float c = ((float)s.r * 1.5f + (float)s.g * 1.5f + (float)s.b * 1.5f) / 3.0f; + p.r = c; + p.g = c; + p.b = c; + } + return p; + }); + + DrawSprite(0, 0, backBuff); + + SetPixelMode(olc::Pixel::NORMAL); + } + + if (nRenderEffect == EFFECT_BRANK) + { + for (int i = 0; i < 4; i++) + { + int sx = rand() % 2; + int sy = rand() % 2; + int tx = rand() % 2; + int ty = rand() % 2; + DrawPartialSprite(0, 0, backBuff, ScreenWidth()/2,ScreenHeight()/2, ScreenWidth()/2, ScreenHeight()/2); + DrawPartialSprite(ScreenWidth()/2, 0, backBuff, 0, ScreenHeight() / 2, ScreenWidth() / 2, ScreenHeight() / 2); + DrawPartialSprite( 0,ScreenHeight()/2, backBuff, ScreenWidth()/2, 0, ScreenWidth() / 2, ScreenHeight() / 2); + DrawPartialSprite(ScreenWidth() / 2, ScreenHeight()/2, backBuff, 0, 0, ScreenWidth() / 2, ScreenHeight() / 2); + } + } + + + + // No effect mode + if (nRenderEffect == EFFECT_NONE) + DrawSprite(0, 0, backBuff); + + string sHelper = ""; + if (nRenderEffect == EFFECT_UGLYSWEDISHFISH) + sHelper = "UGLYSWEDISHFISH Is Patching Your Code!\nHonestly, It's better like this..."; + if (nRenderEffect == EFFECT_GORBIT) + sHelper = "GORBIT Is Patching Your Code!\nLook how beautiful my gifs are..."; + if (nRenderEffect == EFFECT_SEDIT) + sHelper = "SEDIT Is Patching Your Code!\nI'm only going to change one little thing... oh."; + if (nRenderEffect == EFFECT_BRANK) + sHelper = "BRANKEC Is Patching Your Code!\nNow it'll be at least 7623 FPS..."; + + DrawString(ScreenWidth() - 338, ScreenHeight() - 22, sHelper, olc::BLACK); + DrawString(ScreenWidth() - 339, ScreenHeight() - 23, sHelper, olc::YELLOW); + return true; + } + + bool GameState_Complete(float fElapsedTime) + { + + controller.Update(fElapsedTime); + DrawSprite(0, 0, sprCompleteScreen); + + DrawString(4, 10, "YOU SAVED SEDIT!", olc::YELLOW, 4); + + int minutes = (int)fGameTime / 60; + int seconds = (int)fGameTime - (minutes * 60); + string sMinutes = (minutes <= 9 ? "0" : "") + to_string(minutes); + string sSeconds = (seconds <= 9 ? "0" : "") + to_string(seconds); + string sTime = "Your Time: " + sMinutes + ":" + sSeconds; + DrawString(10, 200, sTime, olc::YELLOW, 2); + DrawString(10, 240, "OS Disks Found: " + to_string(nFloppyDisks) + "/100", olc::YELLOW, 2); + DrawString(10, 260, "Level: " + to_string(nLevelSeed) + " Size: " + to_string((int)pow(2,2+nLevelSize)), olc::YELLOW, 2); + DrawString(10, 300, "Jump to Title Screen?", olc::YELLOW, 2); + + if (GetKey(olc::Key::SPACE).bPressed || controller.GetButton(A).bPressed) + { + nGameState = GS_TITLE; + } + + return true; + } + + int nStoryStage = 0; + + bool GameState_Story(float fElapsedTime) + { + controller.Update(fElapsedTime); + + if(nStoryStage == 0) + DrawSprite(0, 0, sprStoryScreen); + else + DrawSprite(0, 0, sprControlScreen); + + if (GetKey(olc::Key::SPACE).bPressed || controller.GetButton(A).bPressed) + { + nStoryStage++; + if (nStoryStage == 2) + { + nGameState = GS_TITLE; + nStoryStage = 0; + } + } + + return true; + } + + bool GameState_Credits(float fElapsedTime) + { + controller.Update(fElapsedTime); + DrawSprite(0, 0, sprCreditScreen); + if (GetKey(olc::Key::SPACE).bPressed || controller.GetButton(A).bPressed) + { + nGameState = GS_TITLE; + } + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + + switch (nGameState) + { + case GS_LOADING: GameState_Loading(fElapsedTime); break; + case GS_TITLE: GameState_Title(fElapsedTime); break; + case GS_GENERATE: GameState_Generating(fElapsedTime); break; + case GS_MAIN: GameState_Main(fElapsedTime); break; + case GS_STORY: GameState_Story(fElapsedTime); break; + case GS_CREDITS: GameState_Credits(fElapsedTime); break; + case GS_COMPLETE: GameState_Complete(fElapsedTime); break; + } + + return true; + } +}; + +int main() +{ + Discovery demo; + if (demo.Construct(512, 448, 2, 2)) + demo.Start(); + return 0; +} diff --git a/SavingSedit/Zix_PGE_Controller.h b/SavingSedit/Zix_PGE_Controller.h new file mode 100644 index 0000000..351e486 --- /dev/null +++ b/SavingSedit/Zix_PGE_Controller.h @@ -0,0 +1,224 @@ +#pragma once + +#ifdef WIN32 +#include +#include + +typedef DWORD(WINAPI XInputGetState_t)(DWORD dwUserIndex, XINPUT_STATE* pState); +static XInputGetState_t* XInputStateGet; + +typedef DWORD(WINAPI XInputSetState_t)(DWORD dwUserIndex, XINPUT_VIBRATION* pVibration); +static XInputSetState_t* XInputStateSet; +#endif + +#define C_BUTTON_COUNT 14 +enum CButton +{ + UP, + DOWN, + LEFT, + RIGHT, + START, + BACK, + A, + B, + X, + Y, + LEFT_SHOULDER, + RIGHT_SHOULDER, + LEFT_THUMB, + RIGHT_THUMB +}; + +struct CBState +{ + bool bPressed = false; + bool bReleased = false; + bool bHeld = false; +}; + +class ControllerManager +{ +private: + bool buttonState[C_BUTTON_COUNT]; + bool lastButtonState[C_BUTTON_COUNT]; + + // Trigger values are in the range of 0 to 1, where 0 is fully + // released and 1 is fully pressed. + float triggerLeft = 0; + float triggerRight = 0; + + // Stick values are in the range of -1 to 1. For X values, -1 is + // all the way to the left while +1 is all the way to the right. + float leftStickX = 0; + float leftStickY = 0; + float rightStickX = 0; + float rightStickY = 0; + + // Whether or not the controller is plugged in. + bool pluggedIn = true; + + bool vibrating = false; + float vibrateTime = 0; + float vibrateCounter = 0; + +public: + bool Initialize(); + void Update(float dt); + + void Vibrate(short amt, int timeMs); + + CBState GetButton(CButton button); + + float GetLeftTrigger() { return triggerLeft; } + float GetRightTrigger() { return triggerRight; } + + float GetLeftStickX() { return leftStickX; } + float GetLeftStickY() { return leftStickY; } + + float GetRightStickX() { return rightStickX; } + float GetRightStickY() { return rightStickY; } + + bool IsVibrating() { return vibrating; } + bool IsPluggedIn() { return pluggedIn; } + +private: + float NormalizeStickValue(short value); +}; + +bool ControllerManager::Initialize() +{ +#ifdef WIN32 + // TODO: Should we check for version 9.1.0 if we fail to find 1.4? + HMODULE lib = LoadLibraryA("xinput1_4.dll"); + + if (!lib) return false; + + XInputStateGet = (XInputGetState_t*)GetProcAddress(lib, "XInputGetState"); + XInputStateSet = (XInputSetState_t*)GetProcAddress(lib, "XInputSetState"); +#endif + + return true; +} + +float ControllerManager::NormalizeStickValue(short value) +{ + // The value we are given is in the range -32768 to 32767 with some deadzone around zero. + // We will assume all values in this dead zone to be a reading of zero (the stick is not moved). + if (value > -7000 && value < 7000) return 0; + + // Otherwise, we are going to normalize the value. + return ((value + 32768.0f) / (32768.0f + 32767.0f) * 2) - 1; +} + +void ControllerManager::Vibrate(short amt, int timeMs) +{ + // If we are already vibrating, just ignore this, unless they say zero, in which case we will let them stop it. + if (vibrating && amt != 0) return; + + // Only start the timer if we are actually vibrating. + if (amt != 0) + { + vibrateTime = timeMs / 1000.0f; + vibrating = true; + } +#ifdef WIN32 + XINPUT_VIBRATION info = + { + amt, + amt + }; + XInputStateSet(0, &info); +#endif +} + +CBState ControllerManager::GetButton(CButton button) +{ + return + { + !lastButtonState[button] && buttonState[button], + lastButtonState[button] && !buttonState[button], + lastButtonState[button] && buttonState[button] + }; +} + +void ControllerManager::Update(float dt) +{ +#ifdef WIN32 + if (vibrating) + { + vibrateCounter += dt; + if (vibrateCounter >= vibrateTime) + { + XINPUT_VIBRATION info = + { + 0, 0 + }; + XInputStateSet(0, &info); + + vibrating = false; + vibrateCounter = 0; + vibrateTime = 0; + } + } + + for (int i = 0; i < C_BUTTON_COUNT; i++) + { + lastButtonState[i] = buttonState[i]; + } + + XINPUT_STATE state; + + // Try and get the first controller. For now we will only support a single one. + DWORD res = XInputStateGet(0, &state); + + // If the controller is plugged in, handle input. + if (res == ERROR_SUCCESS) + { + XINPUT_GAMEPAD* pad = &state.Gamepad; + + buttonState[UP] = (pad->wButtons & XINPUT_GAMEPAD_DPAD_UP); + buttonState[DOWN] = (pad->wButtons & XINPUT_GAMEPAD_DPAD_DOWN); + buttonState[LEFT] = (pad->wButtons & XINPUT_GAMEPAD_DPAD_LEFT); + buttonState[RIGHT] = (pad->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT); + buttonState[START] = (pad->wButtons & XINPUT_GAMEPAD_START); + buttonState[BACK] = (pad->wButtons & XINPUT_GAMEPAD_BACK); + buttonState[LEFT_SHOULDER] = (pad->wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER); + buttonState[RIGHT_SHOULDER] = (pad->wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER); + buttonState[LEFT_THUMB] = (pad->wButtons & XINPUT_GAMEPAD_LEFT_THUMB); + buttonState[RIGHT_THUMB] = (pad->wButtons & XINPUT_GAMEPAD_RIGHT_THUMB); + buttonState[A] = (pad->wButtons & XINPUT_GAMEPAD_A); + buttonState[B] = (pad->wButtons & XINPUT_GAMEPAD_B); + buttonState[X] = (pad->wButtons & XINPUT_GAMEPAD_X); + buttonState[Y] = (pad->wButtons & XINPUT_GAMEPAD_Y); + + triggerLeft = pad->bLeftTrigger / 255.0f; + triggerRight = pad->bRightTrigger / 255.0f; + leftStickX = NormalizeStickValue(pad->sThumbLX); + leftStickY = NormalizeStickValue(pad->sThumbLY); + rightStickX = NormalizeStickValue(pad->sThumbRX); + rightStickY = NormalizeStickValue(pad->sThumbRY); + + if (!pluggedIn) + { + pluggedIn = true; + // Send callback. + // printf("Plugged in.\n"); + } + } + else + { + if (pluggedIn) + { + pluggedIn = false; + // Send callback. + // printf("Unplugged.\n"); + } + } +#else + for (int i = 0; i < C_BUTTON_COUNT; i++) + { + lastButtonState[i] = buttonState[i] = false; + } +#endif +} \ No newline at end of file diff --git a/SavingSedit/licence.txt b/SavingSedit/licence.txt new file mode 100644 index 0000000..3e4e818 --- /dev/null +++ b/SavingSedit/licence.txt @@ -0,0 +1,42 @@ +License (OLC-3) +~~~~~~~~~~~~~~~ + +Copyright 2018 - 2019 OneLoneCoder.com + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions or derivations of source code must retain the above +copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions or derivative works in binary form must reproduce +the above copyright notice. This list of conditions and the following +disclaimer must be reproduced in the documentation and/or other +materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Links +~~~~~ +YouTube: https://www.youtube.com/javidx9 +Discord: https://discord.gg/WhwHUMV +Twitter: https://www.twitter.com/javidx9 +Twitch: https://www.twitch.tv/javidx9 +GitHub: https://www.github.com/onelonecoder +Homepage: https://www.onelonecoder.com +Patreon: https://www.patreon.com/javidx9 diff --git a/SavingSedit/olcPGEX_Graphics2D.h b/SavingSedit/olcPGEX_Graphics2D.h new file mode 100644 index 0000000..146e7c3 --- /dev/null +++ b/SavingSedit/olcPGEX_Graphics2D.h @@ -0,0 +1,313 @@ +/* + olcPGEX_Graphics2D.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine Extension | + | Advanced 2D Rendering - v0.4 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + This is an extension to the olcPixelGameEngine, which provides + advanced olc::Sprite manipulation and drawing routines. To use + it, simply include this header file. + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +/* + Matrices stored as [Column][Row] (i.e. x, y) + + |C0R0 C1R0 C2R0| | x | | x'| + |C0R1 C1R1 C2R1| * | y | = | y'| + |C0R2 C1R2 C2R2| |1.0| | - | +*/ + + + +#ifndef OLC_PGEX_GFX2D +#define OLC_PGEX_GFX2D + +#include +#undef min +#undef max + +namespace olc +{ + // Container class for Advanced 2D Drawing functions + class GFX2D : public olc::PGEX + { + // A representation of an affine transform, used to rotate, scale, offset & shear space + public: + class Transform2D + { + public: + Transform2D(); + + public: + // Set this transformation to unity + void Reset(); + // Append a rotation of fTheta radians to this transform + void Rotate(float fTheta); + // Append a translation (ox, oy) to this transform + void Translate(float ox, float oy); + // Append a scaling operation (sx, sy) to this transform + void Scale(float sx, float sy); + // Append a shear operation (sx, sy) to this transform + void Shear(float sx, float sy); + + void Perspective(float ox, float oy); + // Calculate the Forward Transformation of the coordinate (in_x, in_y) -> (out_x, out_y) + void Forward(float in_x, float in_y, float &out_x, float &out_y); + // Calculate the Inverse Transformation of the coordinate (in_x, in_y) -> (out_x, out_y) + void Backward(float in_x, float in_y, float &out_x, float &out_y); + // Regenerate the Inverse Transformation + void Invert(); + + private: + void Multiply(); + float matrix[4][3][3]; + int nTargetMatrix; + int nSourceMatrix; + bool bDirty; + }; + + public: + // Draws a sprite with the transform applied + static void DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform); + }; +} + + +#ifdef OLC_PGE_GRAPHICS2D +#undef OLC_PGE_GRAPHICS2D + +namespace olc +{ + void GFX2D::DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform) + { + if (sprite == nullptr) + return; + + // Work out bounding rectangle of sprite + float ex, ey; + float sx, sy; + float px, py; + + transform.Forward(0.0f, 0.0f, sx, sy); + px = sx; py = sy; + sx = std::min(sx, px); sy = std::min(sy, py); + ex = std::max(ex, px); ey = std::max(ey, py); + + transform.Forward((float)sprite->width, (float)sprite->height, px, py); + sx = std::min(sx, px); sy = std::min(sy, py); + ex = std::max(ex, px); ey = std::max(ey, py); + + transform.Forward(0.0f, (float)sprite->height, px, py); + sx = std::min(sx, px); sy = std::min(sy, py); + ex = std::max(ex, px); ey = std::max(ey, py); + + transform.Forward((float)sprite->width, 0.0f, px, py); + sx = std::min(sx, px); sy = std::min(sy, py); + ex = std::max(ex, px); ey = std::max(ey, py); + + // Perform inversion of transform if required + transform.Invert(); + + if (ex < sx) + std::swap(ex, sx); + if (ey < sy) + std::swap(ey, sy); + + // Iterate through render space, and sample Sprite from suitable texel location + for (float i = sx; i < ex; i++) + { + for (float j = sy; j < ey; j++) + { + float ox, oy; + transform.Backward(i, j, ox, oy); + pge->Draw((int32_t)i, (int32_t)j, sprite->GetPixel((int32_t)(ox+0.5f), (int32_t)(oy+0.5f))); + } + } + } + + olc::GFX2D::Transform2D::Transform2D() + { + Reset(); + } + + void olc::GFX2D::Transform2D::Reset() + { + nTargetMatrix = 0; + nSourceMatrix = 1; + bDirty = true; + + // Columns Then Rows + + // Matrices 0 & 1 are used as swaps in Transform accumulation + matrix[0][0][0] = 1.0f; matrix[0][1][0] = 0.0f; matrix[0][2][0] = 0.0f; + matrix[0][0][1] = 0.0f; matrix[0][1][1] = 1.0f; matrix[0][2][1] = 0.0f; + matrix[0][0][2] = 0.0f; matrix[0][1][2] = 0.0f; matrix[0][2][2] = 1.0f; + + matrix[1][0][0] = 1.0f; matrix[1][1][0] = 0.0f; matrix[1][2][0] = 0.0f; + matrix[1][0][1] = 0.0f; matrix[1][1][1] = 1.0f; matrix[1][2][1] = 0.0f; + matrix[1][0][2] = 0.0f; matrix[1][1][2] = 0.0f; matrix[1][2][2] = 1.0f; + + // Matrix 2 is a cache matrix to hold the immediate transform operation + // Matrix 3 is a cache matrix to hold the inverted transform + } + + void olc::GFX2D::Transform2D::Multiply() + { + for (int c = 0; c < 3; c++) + { + for (int r = 0; r < 3; r++) + { + matrix[nTargetMatrix][c][r] = matrix[2][0][r] * matrix[nSourceMatrix][c][0] + + matrix[2][1][r] * matrix[nSourceMatrix][c][1] + + matrix[2][2][r] * matrix[nSourceMatrix][c][2]; + } + } + + std::swap(nTargetMatrix, nSourceMatrix); + bDirty = true; // Any transform multiply dirties the inversion + } + + void olc::GFX2D::Transform2D::Rotate(float fTheta) + { + // Construct Rotation Matrix + matrix[2][0][0] = cosf(fTheta); matrix[2][1][0] = sinf(fTheta); matrix[2][2][0] = 0.0f; + matrix[2][0][1] = -sinf(fTheta); matrix[2][1][1] = cosf(fTheta); matrix[2][2][1] = 0.0f; + matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f; + Multiply(); + } + + void olc::GFX2D::Transform2D::Scale(float sx, float sy) + { + // Construct Scale Matrix + matrix[2][0][0] = sx; matrix[2][1][0] = 0.0f; matrix[2][2][0] = 0.0f; + matrix[2][0][1] = 0.0f; matrix[2][1][1] = sy; matrix[2][2][1] = 0.0f; + matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f; + Multiply(); + } + + void olc::GFX2D::Transform2D::Shear(float sx, float sy) + { + // Construct Shear Matrix + matrix[2][0][0] = 1.0f; matrix[2][1][0] = sx; matrix[2][2][0] = 0.0f; + matrix[2][0][1] = sy; matrix[2][1][1] = 1.0f; matrix[2][2][1] = 0.0f; + matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f; + Multiply(); + } + + void olc::GFX2D::Transform2D::Translate(float ox, float oy) + { + // Construct Translate Matrix + matrix[2][0][0] = 1.0f; matrix[2][1][0] = 0.0f; matrix[2][2][0] = ox; + matrix[2][0][1] = 0.0f; matrix[2][1][1] = 1.0f; matrix[2][2][1] = oy; + matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f; + Multiply(); + } + + void olc::GFX2D::Transform2D::Perspective(float ox, float oy) + { + // Construct Translate Matrix + matrix[2][0][0] = 1.0f; matrix[2][1][0] = 0.0f; matrix[2][2][0] = 0.0f; + matrix[2][0][1] = 0.0f; matrix[2][1][1] = 1.0f; matrix[2][2][1] = 0.0f; + matrix[2][0][2] = ox; matrix[2][1][2] = oy; matrix[2][2][2] = 1.0f; + Multiply(); + } + + void olc::GFX2D::Transform2D::Forward(float in_x, float in_y, float &out_x, float &out_y) + { + out_x = in_x * matrix[nSourceMatrix][0][0] + in_y * matrix[nSourceMatrix][1][0] + matrix[nSourceMatrix][2][0]; + out_y = in_x * matrix[nSourceMatrix][0][1] + in_y * matrix[nSourceMatrix][1][1] + matrix[nSourceMatrix][2][1]; + float out_z = in_x * matrix[nSourceMatrix][0][2] + in_y * matrix[nSourceMatrix][1][2] + matrix[nSourceMatrix][2][2]; + if (out_z != 0) + { + out_x /= out_z; + out_y /= out_z; + } + } + + void olc::GFX2D::Transform2D::Backward(float in_x, float in_y, float &out_x, float &out_y) + { + out_x = in_x * matrix[3][0][0] + in_y * matrix[3][1][0] + matrix[3][2][0]; + out_y = in_x * matrix[3][0][1] + in_y * matrix[3][1][1] + matrix[3][2][1]; + float out_z = in_x * matrix[3][0][2] + in_y * matrix[3][1][2] + matrix[3][2][2]; + if (out_z != 0) + { + out_x /= out_z; + out_y /= out_z; + } + } + + void olc::GFX2D::Transform2D::Invert() + { + if (bDirty) // Obviously costly so only do if needed + { + float det = matrix[nSourceMatrix][0][0] * (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) - + matrix[nSourceMatrix][1][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2]) + + matrix[nSourceMatrix][2][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][0][2]); + + float idet = 1.0f / det; + matrix[3][0][0] = (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) * idet; + matrix[3][1][0] = (matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][2]) * idet; + matrix[3][2][0] = (matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][1] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][1]) * idet; + matrix[3][0][1] = (matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2]) * idet; + matrix[3][1][1] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][0][2]) * idet; + matrix[3][2][1] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][1]) * idet; + matrix[3][0][2] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][1]) * idet; + matrix[3][1][2] = (matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][2]) * idet; + matrix[3][2][2] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][1] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][0]) * idet; + bDirty = false; + } + } +} + +#endif +#endif \ No newline at end of file diff --git a/SavingSedit/olcPGEX_Sound.h b/SavingSedit/olcPGEX_Sound.h new file mode 100644 index 0000000..28a7759 --- /dev/null +++ b/SavingSedit/olcPGEX_Sound.h @@ -0,0 +1,906 @@ +/* + olcPGEX_Sound.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine Extension | + | Sound - v0.4 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + This is an extension to the olcPixelGameEngine, which provides + sound generation and wave playing routines. + + Special Thanks: + ~~~~~~~~~~~~~~~ + Slavka - For entire non-windows system back end! + Gorbit99 - Testing, Bug Fixes + Cyberdroid - Testing, Bug Fixes + Dragoneye - Testing + Puol - Testing + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + Patreon: https://www.patreon.com/javidx9 + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + + +#ifndef OLC_PGEX_SOUND_H +#define OLC_PGEX_SOUND_H + +#include +#include +#include + +#include +#undef min +#undef max + +// Choose a default sound backend +#if !defined(USE_ALSA) && !defined(USE_OPENAL) && !defined(USE_WINDOWS) +#ifdef __linux__ +#define USE_ALSA +#endif + +#ifdef __EMSCRIPTEN__ +#define USE_OPENAL +#endif + +#ifdef _WIN32 +#define USE_WINDOWS +#endif + +#endif + +#ifdef USE_ALSA +#define ALSA_PCM_NEW_HW_PARAMS_API +#include +#endif + +#ifdef USE_OPENAL +#include +#include +#include +#endif + +#pragma pack(push, 1) +typedef struct { + uint16_t wFormatTag; + uint16_t nChannels; + uint32_t nSamplesPerSec; + uint32_t nAvgBytesPerSec; + uint16_t nBlockAlign; + uint16_t wBitsPerSample; + uint16_t cbSize; +} OLC_WAVEFORMATEX; +#pragma pack(pop) + +namespace olc +{ + // Container class for Advanced 2D Drawing functions + class SOUND : public olc::PGEX + { + // A representation of an affine transform, used to rotate, scale, offset & shear space + public: + class AudioSample + { + public: + AudioSample(); + AudioSample(std::string sWavFile, olc::ResourcePack *pack = nullptr); + olc::rcode LoadFromFile(std::string sWavFile, olc::ResourcePack *pack = nullptr); + + public: + OLC_WAVEFORMATEX wavHeader; + float *fSample = nullptr; + long nSamples = 0; + int nChannels = 0; + bool bSampleValid = false; + }; + + struct sCurrentlyPlayingSample + { + int nAudioSampleID = 0; + long nSamplePosition = 0; + bool bFinished = false; + bool bLoop = false; + bool bFlagForStop = false; + }; + + static std::list listActiveSamples; + + public: + static bool InitialiseAudio(unsigned int nSampleRate = 44100, unsigned int nChannels = 1, unsigned int nBlocks = 8, unsigned int nBlockSamples = 512); + static bool DestroyAudio(); + static void SetUserSynthFunction(std::function func); + static void SetUserFilterFunction(std::function func); + + public: + static int LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack = nullptr); + static void PlaySample(int id, bool bLoop = false); + static void StopSample(int id); + static void StopAll(); + static float GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep); + + + private: +#ifdef USE_WINDOWS // Windows specific sound management + static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2); + static unsigned int m_nSampleRate; + static unsigned int m_nChannels; + static unsigned int m_nBlockCount; + static unsigned int m_nBlockSamples; + static unsigned int m_nBlockCurrent; + static short* m_pBlockMemory; + static WAVEHDR *m_pWaveHeaders; + static HWAVEOUT m_hwDevice; + static std::atomic m_nBlockFree; + static std::condition_variable m_cvBlockNotZero; + static std::mutex m_muxBlockNotZero; +#endif + +#ifdef USE_ALSA + static snd_pcm_t *m_pPCM; + static unsigned int m_nSampleRate; + static unsigned int m_nChannels; + static unsigned int m_nBlockSamples; + static short* m_pBlockMemory; +#endif + +#ifdef USE_OPENAL + static std::queue m_qAvailableBuffers; + static ALuint *m_pBuffers; + static ALuint m_nSource; + static ALCdevice *m_pDevice; + static ALCcontext *m_pContext; + static unsigned int m_nSampleRate; + static unsigned int m_nChannels; + static unsigned int m_nBlockCount; + static unsigned int m_nBlockSamples; + static short* m_pBlockMemory; +#endif + + static void AudioThread(); + static std::thread m_AudioThread; + static std::atomic m_bAudioThreadActive; + static std::atomic m_fGlobalTime; + static std::function funcUserSynth; + static std::function funcUserFilter; + }; +} + + +// Implementation, platform-independent + +#ifdef OLC_PGEX_SOUND +#undef OLC_PGEX_SOUND + +namespace olc +{ + SOUND::AudioSample::AudioSample() + { } + + SOUND::AudioSample::AudioSample(std::string sWavFile, olc::ResourcePack *pack) + { + LoadFromFile(sWavFile, pack); + } + + olc::rcode SOUND::AudioSample::LoadFromFile(std::string sWavFile, olc::ResourcePack *pack) + { + auto ReadWave = [&](std::istream &is) + { + char dump[4]; + is.read(dump, sizeof(char) * 4); // Read "RIFF" + if (strncmp(dump, "RIFF", 4) != 0) return olc::FAIL; + is.read(dump, sizeof(char) * 4); // Not Interested + is.read(dump, sizeof(char) * 4); // Read "WAVE" + if (strncmp(dump, "WAVE", 4) != 0) return olc::FAIL; + + // Read Wave description chunk + is.read(dump, sizeof(char) * 4); // Read "fmt " + unsigned int nHeaderSize = 0; + is.read((char*)&nHeaderSize, sizeof(unsigned int)); // Not Interested + is.read((char*)&wavHeader, nHeaderSize);// sizeof(WAVEFORMATEX)); // Read Wave Format Structure chunk + // Note the -2, because the structure has 2 bytes to indicate its own size + // which are not in the wav file + + // Just check if wave format is compatible with olcPGE + if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100) + return olc::FAIL; + + // Search for audio data chunk + uint32_t nChunksize = 0; + is.read(dump, sizeof(char) * 4); // Read chunk header + is.read((char*)&nChunksize, sizeof(uint32_t)); // Read chunk size + while (strncmp(dump, "data", 4) != 0) + { + // Not audio data, so just skip it + //std::fseek(f, nChunksize, SEEK_CUR); + is.seekg(nChunksize, std::istream::cur); + is.read(dump, sizeof(char) * 4); + is.read((char*)&nChunksize, sizeof(uint32_t)); + } + + // Finally got to data, so read it all in and convert to float samples + nSamples = nChunksize / (wavHeader.nChannels * (wavHeader.wBitsPerSample >> 3)); + nChannels = wavHeader.nChannels; + + // Create floating point buffer to hold audio sample + fSample = new float[nSamples * nChannels]; + float *pSample = fSample; + + // Read in audio data and normalise + for (long i = 0; i < nSamples; i++) + { + for (int c = 0; c < nChannels; c++) + { + short s = 0; + if (!is.eof()) + { + is.read((char*)&s, sizeof(short)); + + *pSample = (float)s / (float)(SHRT_MAX); + pSample++; + } + } + } + + // All done, flag sound as valid + bSampleValid = true; + return olc::OK; + }; + + if (pack != nullptr) + { + olc::ResourcePack::sEntry entry = pack->GetStreamBuffer(sWavFile); + std::istream is(&entry); + return ReadWave(is); + } + else + { + // Read from file + std::ifstream ifs(sWavFile, std::ifstream::binary); + if (ifs.is_open()) + { + return ReadWave(ifs); + } + else + return olc::FAIL; + } + } + + // This vector holds all loaded sound samples in memory + std::vector vecAudioSamples; + + // This structure represents a sound that is currently playing. It only + // holds the sound ID and where this instance of it is up to for its + // current playback + + void SOUND::SetUserSynthFunction(std::function func) + { + funcUserSynth = func; + } + + void SOUND::SetUserFilterFunction(std::function func) + { + funcUserFilter = func; + } + + // Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID + // number is returned if successful, otherwise -1 + int SOUND::LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack) + { + + olc::SOUND::AudioSample a(sWavFile, pack); + if (a.bSampleValid) + { + vecAudioSamples.push_back(a); + return (unsigned int)vecAudioSamples.size(); + } + else + return -1; + } + + // Add sample 'id' to the mixers sounds to play list + void SOUND::PlaySample(int id, bool bLoop) + { + olc::SOUND::sCurrentlyPlayingSample a; + a.nAudioSampleID = id; + a.nSamplePosition = 0; + a.bFinished = false; + a.bFlagForStop = false; + a.bLoop = bLoop; + SOUND::listActiveSamples.push_back(a); + } + + void SOUND::StopSample(int id) + { + // Find first occurence of sample id + auto s = std::find_if(listActiveSamples.begin(), listActiveSamples.end(), [&](const olc::SOUND::sCurrentlyPlayingSample &s) { return s.nAudioSampleID == id; }); + if (s != listActiveSamples.end()) + s->bFlagForStop = true; + } + + void SOUND::StopAll() + { + for (auto &s : listActiveSamples) + { + s.bFlagForStop = true; + } + } + + float SOUND::GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep) + { + // Accumulate sample for this channel + float fMixerSample = 0.0f; + + for (auto &s : listActiveSamples) + { + if (m_bAudioThreadActive) + { + if (s.bFlagForStop) + { + s.bLoop = false; + s.bFinished = true; + } + else + { + // Calculate sample position + s.nSamplePosition += roundf((float)vecAudioSamples[s.nAudioSampleID - 1].wavHeader.nSamplesPerSec * fTimeStep); + + // If sample position is valid add to the mix + if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples) + fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * vecAudioSamples[s.nAudioSampleID - 1].nChannels) + nChannel]; + else + { + if (s.bLoop) + { + s.nSamplePosition = 0; + } + else + s.bFinished = true; // Else sound has completed + } + } + } + else + return 0.0f; + } + + // If sounds have completed then remove them + listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; }); + + // The users application might be generating sound, so grab that if it exists + if (funcUserSynth != nullptr) + fMixerSample += funcUserSynth(nChannel, fGlobalTime, fTimeStep); + + // Return the sample via an optional user override to filter the sound + if (funcUserFilter != nullptr) + return funcUserFilter(nChannel, fGlobalTime, fMixerSample); + else + return fMixerSample; + } + + std::thread SOUND::m_AudioThread; + std::atomic SOUND::m_bAudioThreadActive{ false }; + std::atomic SOUND::m_fGlobalTime{ 0.0f }; + std::list SOUND::listActiveSamples; + std::function SOUND::funcUserSynth = nullptr; + std::function SOUND::funcUserFilter = nullptr; +} + +// Implementation, Windows-specific +#ifdef USE_WINDOWS +#pragma comment(lib, "winmm.lib") + +namespace olc +{ + bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples) + { + // Initialise Sound Engine + m_bAudioThreadActive = false; + m_nSampleRate = nSampleRate; + m_nChannels = nChannels; + m_nBlockCount = nBlocks; + m_nBlockSamples = nBlockSamples; + m_nBlockFree = m_nBlockCount; + m_nBlockCurrent = 0; + m_pBlockMemory = nullptr; + m_pWaveHeaders = nullptr; + + // Device is available + WAVEFORMATEX waveFormat; + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + waveFormat.nSamplesPerSec = m_nSampleRate; + waveFormat.wBitsPerSample = sizeof(short) * 8; + waveFormat.nChannels = m_nChannels; + waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels; + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + waveFormat.cbSize = 0; + + listActiveSamples.clear(); + + // Open Device if valid + if (waveOutOpen(&m_hwDevice, WAVE_MAPPER, &waveFormat, (DWORD_PTR)SOUND::waveOutProc, (DWORD_PTR)0, CALLBACK_FUNCTION) != S_OK) + return DestroyAudio(); + + // Allocate Wave|Block Memory + m_pBlockMemory = new short[m_nBlockCount * m_nBlockSamples]; + if (m_pBlockMemory == nullptr) + return DestroyAudio(); + ZeroMemory(m_pBlockMemory, sizeof(short) * m_nBlockCount * m_nBlockSamples); + + m_pWaveHeaders = new WAVEHDR[m_nBlockCount]; + if (m_pWaveHeaders == nullptr) + return DestroyAudio(); + ZeroMemory(m_pWaveHeaders, sizeof(WAVEHDR) * m_nBlockCount); + + // Link headers to block memory + for (unsigned int n = 0; n < m_nBlockCount; n++) + { + m_pWaveHeaders[n].dwBufferLength = m_nBlockSamples * sizeof(short); + m_pWaveHeaders[n].lpData = (LPSTR)(m_pBlockMemory + (n * m_nBlockSamples)); + } + + m_bAudioThreadActive = true; + m_AudioThread = std::thread(&SOUND::AudioThread); + + // Start the ball rolling with the sound delivery thread + std::unique_lock lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); + return true; + } + + // Stop and clean up audio system + bool SOUND::DestroyAudio() + { + m_bAudioThreadActive = false; + if(m_AudioThread.joinable()) + m_AudioThread.join(); + return false; + } + + // Handler for soundcard request for more data + void CALLBACK SOUND::waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2) + { + if (uMsg != WOM_DONE) return; + m_nBlockFree++; + std::unique_lock lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); + } + + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void SOUND::AudioThread() + { + m_fGlobalTime = 0.0f; + static float fTimeStep = 1.0f / (float)m_nSampleRate; + + // Goofy hack to get maximum integer for a type at run-time + short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; + float fMaxSample = (float)nMaxSample; + short nPreviousSample = 0; + + auto tp1 = std::chrono::system_clock::now(); + auto tp2 = std::chrono::system_clock::now(); + + while (m_bAudioThreadActive) + { + // Wait for block to become available + if (m_nBlockFree == 0) + { + std::unique_lock lm(m_muxBlockNotZero); + while (m_nBlockFree == 0) // sometimes, Windows signals incorrectly + m_cvBlockNotZero.wait(lm); + } + + // Block is here, so use it + m_nBlockFree--; + + // Prepare block for processing + if (m_pWaveHeaders[m_nBlockCurrent].dwFlags & WHDR_PREPARED) + waveOutUnprepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + + short nNewSample = 0; + int nCurrentBlock = m_nBlockCurrent * m_nBlockSamples; + + auto clip = [](float fSample, float fMax) + { + if (fSample >= 0.0) + return fmin(fSample, fMax); + else + return fmax(fSample, -fMax); + }; + + tp2 = std::chrono::system_clock::now(); + std::chrono::duration elapsedTime = tp2 - tp1; + tp1 = tp2; + + // Our time per frame coefficient + float fElapsedTime = elapsedTime.count(); + + for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) + { + // User Process + for (unsigned int c = 0; c < m_nChannels; c++) + { + nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime + fTimeStep * (float)n, fTimeStep), 1.0) * fMaxSample); + m_pBlockMemory[nCurrentBlock + n + c] = nNewSample; + nPreviousSample = nNewSample; + } + } + + m_fGlobalTime = m_fGlobalTime + fTimeStep * (float)m_nBlockSamples; + + // Send block to sound device + waveOutPrepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + waveOutWrite(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + m_nBlockCurrent++; + m_nBlockCurrent %= m_nBlockCount; + } + } + + unsigned int SOUND::m_nSampleRate = 0; + unsigned int SOUND::m_nChannels = 0; + unsigned int SOUND::m_nBlockCount = 0; + unsigned int SOUND::m_nBlockSamples = 0; + unsigned int SOUND::m_nBlockCurrent = 0; + short* SOUND::m_pBlockMemory = nullptr; + WAVEHDR *SOUND::m_pWaveHeaders = nullptr; + HWAVEOUT SOUND::m_hwDevice; + std::atomic SOUND::m_nBlockFree = 0; + std::condition_variable SOUND::m_cvBlockNotZero; + std::mutex SOUND::m_muxBlockNotZero; +} + +#elif defined(USE_ALSA) + +namespace olc +{ + bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples) + { + // Initialise Sound Engine + m_bAudioThreadActive = false; + m_nSampleRate = nSampleRate; + m_nChannels = nChannels; + m_nBlockSamples = nBlockSamples; + m_pBlockMemory = nullptr; + + // Open PCM stream + int rc = snd_pcm_open(&m_pPCM, "default", SND_PCM_STREAM_PLAYBACK, 0); + if (rc < 0) + return DestroyAudio(); + + + // Prepare the parameter structure and set default parameters + snd_pcm_hw_params_t *params; + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(m_pPCM, params); + + // Set other parameters + snd_pcm_hw_params_set_access(m_pPCM, params, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(m_pPCM, params, SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_rate(m_pPCM, params, m_nSampleRate, 0); + snd_pcm_hw_params_set_channels(m_pPCM, params, m_nChannels); + snd_pcm_hw_params_set_period_size(m_pPCM, params, m_nBlockSamples, 0); + snd_pcm_hw_params_set_periods(m_pPCM, params, nBlocks, 0); + + // Save these parameters + rc = snd_pcm_hw_params(m_pPCM, params); + if (rc < 0) + return DestroyAudio(); + + listActiveSamples.clear(); + + // Allocate Wave|Block Memory + m_pBlockMemory = new short[m_nBlockSamples]; + if (m_pBlockMemory == nullptr) + return DestroyAudio(); + std::fill(m_pBlockMemory, m_pBlockMemory + m_nBlockSamples, 0); + + // Unsure if really needed, helped prevent underrun on my setup + snd_pcm_start(m_pPCM); + for (unsigned int i = 0; i < nBlocks; i++) + rc = snd_pcm_writei(m_pPCM, m_pBlockMemory, 512); + + snd_pcm_start(m_pPCM); + m_bAudioThreadActive = true; + m_AudioThread = std::thread(&SOUND::AudioThread); + + return true; + } + + // Stop and clean up audio system + bool SOUND::DestroyAudio() + { + m_bAudioThreadActive = false; + if(m_AudioThread.joinable()) + m_AudioThread.join(); + snd_pcm_drain(m_pPCM); + snd_pcm_close(m_pPCM); + return false; + } + + + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void SOUND::AudioThread() + { + m_fGlobalTime = 0.0f; + static float fTimeStep = 1.0f / (float)m_nSampleRate; + + // Goofy hack to get maximum integer for a type at run-time + short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; + float fMaxSample = (float)nMaxSample; + short nPreviousSample = 0; + + while (m_bAudioThreadActive) + { + short nNewSample = 0; + + auto clip = [](float fSample, float fMax) + { + if (fSample >= 0.0) + return fmin(fSample, fMax); + else + return fmax(fSample, -fMax); + }; + + for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) + { + // User Process + for (unsigned int c = 0; c < m_nChannels; c++) + { + nNewSample = (short)(GetMixerOutput(c, m_fGlobalTime + fTimeStep * (float)n, fTimeStep), 1.0) * fMaxSample; + m_pBlockMemory[n + c] = nNewSample; + nPreviousSample = nNewSample; + } + } + + m_fGlobalTime = m_fGlobalTime + fTimeStep * (float)m_nBlockSamples; + + // Send block to sound device + snd_pcm_uframes_t nLeft = m_nBlockSamples; + short *pBlockPos = m_pBlockMemory; + while (nLeft > 0) + { + int rc = snd_pcm_writei(m_pPCM, pBlockPos, nLeft); + if (rc > 0) + { + pBlockPos += rc * m_nChannels; + nLeft -= rc; + } + if (rc == -EAGAIN) continue; + if (rc == -EPIPE) // an underrun occured, prepare the device for more data + snd_pcm_prepare(m_pPCM); + } + } + } + + snd_pcm_t* SOUND::m_pPCM = nullptr; + unsigned int SOUND::m_nSampleRate = 0; + unsigned int SOUND::m_nChannels = 0; + unsigned int SOUND::m_nBlockSamples = 0; + short* SOUND::m_pBlockMemory = nullptr; +} + +#elif defined(USE_OPENAL) + +namespace olc +{ + bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples) + { + // Initialise Sound Engine + m_bAudioThreadActive = false; + m_nSampleRate = nSampleRate; + m_nChannels = nChannels; + m_nBlockCount = nBlocks; + m_nBlockSamples = nBlockSamples; + m_pBlockMemory = nullptr; + + // Open the device and create the context + m_pDevice = alcOpenDevice(NULL); + if (m_pDevice) + { + m_pContext = alcCreateContext(m_pDevice, NULL); + alcMakeContextCurrent(m_pContext); + } + else + return DestroyAudio(); + + // Allocate memory for sound data + alGetError(); + m_pBuffers = new ALuint[m_nBlockCount]; + alGenBuffers(m_nBlockCount, m_pBuffers); + alGenSources(1, &m_nSource); + + for (unsigned int i = 0; i < m_nBlockCount; i++) + m_qAvailableBuffers.push(m_pBuffers[i]); + + listActiveSamples.clear(); + + // Allocate Wave|Block Memory + m_pBlockMemory = new short[m_nBlockSamples]; + if (m_pBlockMemory == nullptr) + return DestroyAudio(); + std::fill(m_pBlockMemory, m_pBlockMemory + m_nBlockSamples, 0); + + m_bAudioThreadActive = true; + m_AudioThread = std::thread(&SOUND::AudioThread); + return true; + } + + // Stop and clean up audio system + bool SOUND::DestroyAudio() + { + m_bAudioThreadActive = false; + if(m_AudioThread.joinable()) + m_AudioThread.join(); + + alDeleteBuffers(m_nBlockCount, m_pBuffers); + delete[] m_pBuffers; + alDeleteSources(1, &m_nSource); + + alcMakeContextCurrent(NULL); + alcDestroyContext(m_pContext); + alcCloseDevice(m_pDevice); + return false; + } + + + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void SOUND::AudioThread() + { + m_fGlobalTime = 0.0f; + static float fTimeStep = 1.0f / (float)m_nSampleRate; + + // Goofy hack to get maximum integer for a type at run-time + short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; + float fMaxSample = (float)nMaxSample; + short nPreviousSample = 0; + + std::vector vProcessed; + + while (m_bAudioThreadActive) + { + ALint nState, nProcessed; + alGetSourcei(m_nSource, AL_SOURCE_STATE, &nState); + alGetSourcei(m_nSource, AL_BUFFERS_PROCESSED, &nProcessed); + + // Add processed buffers to our queue + vProcessed.resize(nProcessed); + alSourceUnqueueBuffers(m_nSource, nProcessed, vProcessed.data()); + for (ALint nBuf : vProcessed) m_qAvailableBuffers.push(nBuf); + + // Wait until there is a free buffer (ewww) + if (m_qAvailableBuffers.empty()) continue; + + short nNewSample = 0; + + auto clip = [](float fSample, float fMax) + { + if (fSample >= 0.0) + return fmin(fSample, fMax); + else + return fmax(fSample, -fMax); + }; + + for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) + { + // User Process + for (unsigned int c = 0; c < m_nChannels; c++) + { + nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample); + m_pBlockMemory[n + c] = nNewSample; + nPreviousSample = nNewSample; + } + + m_fGlobalTime = m_fGlobalTime + fTimeStep; + } + + // Fill OpenAL data buffer + alBufferData( + m_qAvailableBuffers.front(), + m_nChannels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, + m_pBlockMemory, + 2 * m_nBlockSamples, + m_nSampleRate + ); + // Add it to the OpenAL queue + alSourceQueueBuffers(m_nSource, 1, &m_qAvailableBuffers.front()); + // Remove it from ours + m_qAvailableBuffers.pop(); + + // If it's not playing for some reason, change that + if (nState != AL_PLAYING) + alSourcePlay(m_nSource); + } + } + + std::queue SOUND::m_qAvailableBuffers; + ALuint *SOUND::m_pBuffers = nullptr; + ALuint SOUND::m_nSource = 0; + ALCdevice *SOUND::m_pDevice = nullptr; + ALCcontext *SOUND::m_pContext = nullptr; + unsigned int SOUND::m_nSampleRate = 0; + unsigned int SOUND::m_nChannels = 0; + unsigned int SOUND::m_nBlockCount = 0; + unsigned int SOUND::m_nBlockSamples = 0; + short* SOUND::m_pBlockMemory = nullptr; +} + +#else // Some other platform + +namespace olc +{ + bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, unsigned int nBlocks, unsigned int nBlockSamples) + { + return true; + } + + // Stop and clean up audio system + bool SOUND::DestroyAudio() + { + return false; + } + + + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void SOUND::AudioThread() + { } +} + +#endif +#endif +#endif // OLC_PGEX_SOUND \ No newline at end of file diff --git a/SavingSedit/olcPGEX_TileMaps_new.h b/SavingSedit/olcPGEX_TileMaps_new.h new file mode 100644 index 0000000..f09d3d1 --- /dev/null +++ b/SavingSedit/olcPGEX_TileMaps_new.h @@ -0,0 +1,381 @@ +#pragma once +#include "olcPixelGameEngine.h" + +#include +#undef min +#undef max + +namespace olc +{ + + class TILE : public olc::PGEX + { + + public: + + + struct Edge + { + float sx, sy; + float ex, ey; + }; + + public: + class Atlas + { + public: + Atlas(); + void Create(olc::Sprite *tileSheet); + olc::rcode LoadFromFile(std::string filename); + olc::rcode SaveToFile(std::string filename); + + public: + olc::Sprite *sprTileSheet; + std::vector> location; + }; + + public: + + template + class Layer + { + public: + Layer(); + void Create(int32_t w, int32_t h, int32_t tw, int32_t th); + olc::rcode LoadFromFile(std::string filename); + olc::rcode SaveToFile(std::string filename); + T* GetTile(int32_t x, int32_t y); + + public: + int32_t nLayerWidth; + int32_t nLayerHeight; + int32_t nTileWidth; + int32_t nTileHeight; + + private: + T *pTiles; + + }; + + class BasicTile + { + public: + BasicTile(); + + public: + int32_t id; + bool exist; + + int edge_id[4]; + bool edge_exist[4]; + }; + + public: + template + static void DrawLayer(olc::TILE::Layer &layer, olc::TILE::Atlas &atlas, float cam_x, float cam_y, int tiles_x, int tiles_y, int nScale = 1); + + template + static olc::Pixel GetLayerPixel(olc::TILE::Layer &layer, olc::TILE::Atlas &atlas, float x, float y); + + template + static std::vector ExtractEdgesFromLayer(olc::TILE::Layer &layer, int sx, int sy, int width, int height); + + }; +} + + + + +namespace olc +{ + TILE::BasicTile::BasicTile() + { + exist = false; + id = 0; + + for (int i = 0; i < 4; i++) + { + edge_exist[i] = false; + edge_id[i] = 0; + } + } + + template + TILE::Layer::Layer() + { + + } + + + + template + void TILE::Layer::Create(int32_t w, int32_t h, int32_t tw, int32_t th) + { + nLayerWidth = w; + nLayerHeight = h; + nTileWidth = tw; + nTileHeight = th; + + pTiles = new T[nLayerWidth * nLayerHeight]; + for (int i = 0; i < nLayerWidth*nLayerHeight; i++) + { + pTiles[i].id = 0; + } + } + + template + olc::rcode TILE::Layer::LoadFromFile(std::string filename) + { + return olc::FAIL; + } + + template + olc::rcode TILE::Layer::SaveToFile(std::string filename) + { + return olc::FAIL; + } + + template + T* TILE::Layer::GetTile(int32_t x, int32_t y) + { + if (x < 0 || x >= nLayerWidth || y < 0 || y >= nLayerHeight) + return nullptr; + else + return &pTiles[y*nLayerWidth + x]; + } + + template + void TILE::DrawLayer(olc::TILE::Layer &layer, olc::TILE::Atlas &atlas, float cam_x, float cam_y, int32_t tiles_x, int32_t tiles_y, int nScale) + { + float fOffsetX = cam_x - (int)cam_x; + float fOffsetY = cam_y - (int)cam_y; + + for (int32_t x = 0; x < tiles_x; x++) + { + for (int32_t y = 0; y < tiles_y; y++) + { + olc::TILE::BasicTile *t = layer.GetTile(x + (int)cam_x, y + (int)cam_y); + if (t != nullptr && t->exist) + { + float fx = (int)(((float)x - fOffsetX) * (float)(layer.nTileWidth)); + float fy = (int)(((float)y - fOffsetY) * (float)(layer.nTileHeight)); + + pge->DrawPartialSprite( + fx + 0.5f - (fx < 0.0f), + fy + 0.5f - (fy < 0.0f), + atlas.sprTileSheet, + std::get<0>(atlas.location[t->id]), + std::get<1>(atlas.location[t->id]), + std::get<2>(atlas.location[t->id]), + std::get<3>(atlas.location[t->id]), + nScale); + } + } + } + } + + template + olc::Pixel TILE::GetLayerPixel(olc::TILE::Layer &layer, olc::TILE::Atlas &atlas, float x, float y) + { + olc::TILE::BasicTile *t = layer.GetTile((int32_t)x, (int32_t)y); + if (t != nullptr) + { + float fOffsetX = x - (int)x; + float fOffsetY = y - (int)y; + return atlas.sprTileSheet->GetPixel(std::get<0>(atlas.location[t->id]) + fOffsetX * std::get<2>(atlas.location[t->id]), + std::get<1>(atlas.location[t->id]) + fOffsetX * std::get<3>(atlas.location[t->id])); + } + else + return olc::BLANK; + } + + template + std::vector TILE::ExtractEdgesFromLayer(olc::TILE::Layer &layer, int sx, int sy, int width, int height) + { + enum + { + NORTH = 0, + EAST = 1, + SOUTH = 2, + WEST = 3 + }; + + std::vector vecEdges; + + for (int x = -1; x < width + 1; x++) + for (int y = -1; y < height + 1; y++) + for (int j = 0; j < 4; j++) + { + if ((x + sx) >= 0 && (y + sy) >= 0 && (x + sx) < (layer.nLayerWidth - 1) && (y + sy) < (layer.nLayerHeight - 1)) + { + layer.GetTile(x + sx, y + sy)->edge_exist[j] = false; + layer.GetTile(x + sx, y + sy)->edge_id[j] = 0; + } + } + + // Add boundary edges + vecEdges.push_back({ (float)(sx)* layer.nTileWidth, (float)(sy)*layer.nTileHeight, (float)(sx + width)*layer.nTileWidth, (float)(sy)*layer.nTileHeight }); + vecEdges.push_back({ (float)(sx + width)* layer.nTileWidth, (float)(sy)*layer.nTileHeight, (float)(sx + width)*layer.nTileWidth, (float)(sy + height)*layer.nTileHeight }); + vecEdges.push_back({ (float)(sx + width)* layer.nTileWidth, (float)(sy + height)*layer.nTileHeight, (float)(sx)*layer.nTileWidth, (float)(sy + height)*layer.nTileHeight }); + vecEdges.push_back({ (float)(sx)* layer.nTileWidth, (float)(sy + height)*layer.nTileHeight, (float)(sx)*layer.nTileWidth, (float)(sy)*layer.nTileHeight }); + + + // Iterate through region from top left to bottom right + for (int x = 0; x < width; x++) + for (int y = 0; y < height; y++) + { + T* i = layer.GetTile(x + sx, y + sy); //This + T* n = layer.GetTile(x + sx, y + sy - 1); + T* s = layer.GetTile(x + sx, y + sy + 1); + T* w = layer.GetTile(x + sx - 1, y + sy); + T* e = layer.GetTile(x + sx + 1, y + sy); + + // If this cell exists, check if it needs edges + if (i->exist) + { + // If this cell has no western neighbour, it needs a western edge + if (w && !w->exist) + { + // It can either extend it from its northern neighbour if they have + // one, or It can start a new one. + if (n && n->edge_exist[WEST]) + { + // Northern neighbour has a western edge, so grow it downwards + vecEdges[n->edge_id[WEST]].ey += layer.nTileHeight; + i->edge_id[WEST] = n->edge_id[WEST]; + i->edge_exist[WEST] = true; + } + else + { + // Northern neighbour does not have one, so create one + olc::TILE::Edge edge; + edge.sx = (sx + x) * layer.nTileWidth; edge.sy = (sy + y) * layer.nTileHeight; + edge.ex = edge.sx; edge.ey = edge.sy + layer.nTileHeight; + + // Add edge to Polygon Pool + int edge_id = vecEdges.size(); + vecEdges.push_back(edge); + + // Update tile information with edge information + i->edge_id[WEST] = edge_id; + i->edge_exist[WEST] = true; + } + } + + + // If this cell dont have an eastern neignbour, It needs a eastern edge + if (e && !e->exist) + { + // It can either extend it from its northern neighbour if they have + // one, or It can start a new one. + if (n && n->edge_exist[EAST]) + { + // Northern neighbour has one, so grow it downwards + vecEdges[n->edge_id[EAST]].ey += layer.nTileHeight; + i->edge_id[EAST] = n->edge_id[EAST]; + i->edge_exist[EAST] = true; + } + else + { + // Northern neighbour does not have one, so create one + olc::TILE::Edge edge; + edge.sx = (sx + x + 1) * layer.nTileWidth; edge.sy = (sy + y) * layer.nTileHeight; + edge.ex = edge.sx; edge.ey = edge.sy + layer.nTileHeight; + + // Add edge to Polygon Pool + int edge_id = vecEdges.size(); + vecEdges.push_back(edge); + + // Update tile information with edge information + i->edge_id[EAST] = edge_id; + i->edge_exist[EAST] = true; + } + } + + // If this cell doesnt have a northern neignbour, It needs a northern edge + if (n && !n->exist) + { + // It can either extend it from its western neighbour if they have + // one, or It can start a new one. + if (w && w->edge_exist[NORTH]) + { + // Western neighbour has one, so grow it eastwards + vecEdges[w->edge_id[NORTH]].ex += layer.nTileWidth; + i->edge_id[NORTH] = w->edge_id[NORTH]; + i->edge_exist[NORTH] = true; + } + else + { + // Western neighbour does not have one, so create one + olc::TILE::Edge edge; + edge.sx = (sx + x) * layer.nTileWidth; edge.sy = (sy + y) * layer.nTileHeight; + edge.ex = edge.sx + layer.nTileWidth; edge.ey = edge.sy; + + // Add edge to Polygon Pool + int edge_id = vecEdges.size(); + vecEdges.push_back(edge); + + // Update tile information with edge information + i->edge_id[NORTH] = edge_id; + i->edge_exist[NORTH] = true; + } + } + + // If this cell doesnt have a southern neignbour, It needs a southern edge + if (s && !s->exist) + { + // It can either extend it from its western neighbour if they have + // one, or It can start a new one. + if (w && w->edge_exist[SOUTH]) + { + // Western neighbour has one, so grow it eastwards + vecEdges[w->edge_id[SOUTH]].ex += layer.nTileWidth; + i->edge_id[SOUTH] = w->edge_id[SOUTH]; + i->edge_exist[SOUTH] = true; + } + else + { + // Western neighbour does not have one, so I need to create one + olc::TILE::Edge edge; + edge.sx = (sx + x) * layer.nTileWidth; edge.sy = (sy + y + 1) * layer.nTileHeight; + edge.ex = edge.sx + layer.nTileWidth; edge.ey = edge.sy; + + // Add edge to Polygon Pool + int edge_id = vecEdges.size(); + vecEdges.push_back(edge); + + // Update tile information with edge information + i->edge_id[SOUTH] = edge_id; + i->edge_exist[SOUTH] = true; + } + } + } + } + + return vecEdges; + } + + + + TILE::Atlas::Atlas() + { + } + + void TILE::Atlas::Create(olc::Sprite *tileSheet) + { + sprTileSheet = tileSheet; + location.clear(); + + } + + olc::rcode TILE::Atlas::LoadFromFile(std::string filename) + { + return olc::FAIL; + } + + olc::rcode TILE::Atlas::SaveToFile(std::string filename) + { + return olc::FAIL; + } + +} diff --git a/SavingSedit/olcPixelGameEngine.h b/SavingSedit/olcPixelGameEngine.h new file mode 100644 index 0000000..67032fb --- /dev/null +++ b/SavingSedit/olcPixelGameEngine.h @@ -0,0 +1,2317 @@ +/* + olcPixelGameEngine.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine v1.18 | + | "Like the command prompt console one, but not..." - javidx9 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + The olcConsoleGameEngine has been a surprising and wonderful success for me, + and I'm delighted how people have reacted so positively towards it, so thanks + for that. + + However, there are limitations that I simply cannot avoid. Firstly, I need to + maintain several different versions of it to accommodate users on Windows7, + 8, 10, Linux, Mac, Visual Studio & Code::Blocks. Secondly, this year I've been + pushing the console to the limits of its graphical capabilities and the effect + is becoming underwhelming. The engine itself is not slow at all, but the process + that Windows uses to draw the command prompt to the screen is, and worse still, + it's dynamic based upon the variation of character colours and glyphs. Sadly + I have no control over this, and recent videos that are extremely graphical + (for a command prompt :P ) have been dipping to unacceptable framerates. As + the channel has been popular with aspiring game developers, I'm concerned that + the visual appeal of the command prompt is perhaps limited to us oldies, and I + dont want to alienate younger learners. Finally, I'd like to demonstrate many + more algorithms and image processing that exist in the graphical domain, for + which the console is insufficient. + + For this reason, I have created olcPixelGameEngine! The look and feel to the + programmer is almost identical, so all of my existing code from the videos is + easily portable, and the programmer uses this file in exactly the same way. But + I've decided that rather than just build a command prompt emulator, that I + would at least harness some modern(ish) portable technologies. + + As a result, the olcPixelGameEngine supports 32-bit colour, is written in a + cross-platform style, uses modern(ish) C++ conventions and most importantly, + renders much much faster. I will use this version when my applications are + predominantly graphics based, but use the console version when they are + predominantly text based - Don't worry, loads more command prompt silliness to + come yet, but evolution is important!! + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions or derivations of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce the above + copyright notice. This list of conditions and the following disclaimer must be + reproduced in the documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its contributors may + be used to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + Patreon: https://www.patreon.com/javidx9 + + Relevant Videos + ~~~~~~~~~~~~~~~ + https://youtu.be/kRH6oJLFYxY Introducing olcPixelGameEngine + + Compiling in Linux + ~~~~~~~~~~~~~~~~~~ + You will need a modern C++ compiler, so update yours! + To compile use the command: + + g++ -o YourProgName YourSource.cpp -lX11 -lGL -lpthread -lpng + + On some Linux configurations, the frame rate is locked to the refresh + rate of the monitor. This engine tries to unlock it but may not be + able to, in which case try launching your program like this: + + vblank_mode=0 ./YourProgName + + + Compiling in Code::Blocks on Windows + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Well I wont judge you, but make sure your Code::Blocks installation + is really up to date - you may even consider updating your C++ toolchain + to use MinGW32-W64, so google this. You will also need to enable C++14 + in your build options, and add to your linker the following libraries: + user32 gdi32 opengl32 gdiplus + + Ports + ~~~~~ + olc::PixelGameEngine has been ported and tested with varying degrees of + success to: WinXP, Win7, Win8, Win10, Various Linux, Rapberry Pi, + Chromebook, Playstation Portable (PSP) and Nintendo Switch. If you are + interested in the details of these ports, come and visit the Discord! + + Thanks + ~~~~~~ + I'd like to extend thanks to Eremiell, slavka, gurkanctn, Phantim, + JackOJC, KrossX, Huhlig, Dragoneye, Appa, JustinRichardsMusic, SliceNDice + Ralakus, Gorbit99, raoul, joshinils, benedani & MagetzUb for advice, ideas and + testing, and I'd like to extend my appreciation to the 40K YouTube followers, + 22 Patreons and 2.6K Discord server members who give me the motivation to keep + going with all this :D + + Special thanks to those who bring gifts! + GnarGnarHead.......Domina + Gorbit99...........Bastion, Ori & The Blind Forest + Marti Morta........Gris + + Special thanks to my Patreons too - I wont name you on here, but I've + certainly enjoyed my tea and flapjacks :D + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2018, 2019 +*/ + +////////////////////////////////////////////////////////////////////////////////////////// + +/* Example Usage (main.cpp) + #define OLC_PGE_APPLICATION + #include "olcPixelGameEngine.h" + // Override base class with your custom functionality + class Example : public olc::PixelGameEngine + { + public: + Example() + { + sAppName = "Example"; + } + public: + bool OnUserCreate() override + { + // Called once at the start, so create things here + return true; + } + bool OnUserUpdate(float fElapsedTime) override + { + // called once per frame, draws random coloured pixels + for (int x = 0; x < ScreenWidth(); x++) + for (int y = 0; y < ScreenHeight(); y++) + Draw(x, y, olc::Pixel(rand() % 255, rand() % 255, rand()% 255)); + return true; + } + }; + int main() + { + Example demo; + if (demo.Construct(256, 240, 4, 4)) + demo.Start(); + return 0; + } +*/ + +#ifndef OLC_PGE_DEF +#define OLC_PGE_DEF + +#ifdef _WIN32 + // Link to libraries +#ifndef __MINGW32__ + #pragma comment(lib, "user32.lib") // Visual Studio Only + #pragma comment(lib, "gdi32.lib") // For other Windows Compilers please add + #pragma comment(lib, "opengl32.lib") // these libs to your linker input + #pragma comment(lib, "gdiplus.lib") +#else + // In Code::Blocks, Select C++14 in your build options, and add the + // following libs to your linker: user32 gdi32 opengl32 gdiplus + #if !defined _WIN32_WINNT + #ifdef HAVE_MSMF + #define _WIN32_WINNT 0x0600 // Windows Vista + #else + #define _WIN32_WINNT 0x0500 // Windows 2000 + #endif + #endif +#endif + // Include WinAPI + #include + #include + + // OpenGL Extension + #include + typedef BOOL(WINAPI wglSwapInterval_t) (int interval); + static wglSwapInterval_t *wglSwapInterval; +#else + #include + #include + #include + #include + #include + typedef int(glSwapInterval_t) (Display *dpy, GLXDrawable drawable, int interval); + static glSwapInterval_t *glSwapIntervalEXT; +#endif + + +// Standard includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef min +#undef max + +namespace olc // All OneLoneCoder stuff will now exist in the "olc" namespace +{ + struct Pixel + { + union + { + uint32_t n = 0xFF000000; + struct + { + uint8_t r; uint8_t g; uint8_t b; uint8_t a; + }; + }; + + Pixel(); + Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255); + Pixel(uint32_t p); + enum Mode { NORMAL, MASK, ALPHA, CUSTOM }; + }; + + // Some constants for symbolic naming of Pixels + static const Pixel + WHITE(255, 255, 255), + GREY(192, 192, 192), DARK_GREY(128, 128, 128), VERY_DARK_GREY(64, 64, 64), + RED(255, 0, 0), DARK_RED(128, 0, 0), VERY_DARK_RED(64, 0, 0), + YELLOW(255, 255, 0), DARK_YELLOW(128, 128, 0), VERY_DARK_YELLOW(64, 64, 0), + GREEN(0, 255, 0), DARK_GREEN(0, 128, 0), VERY_DARK_GREEN(0, 64, 0), + CYAN(0, 255, 255), DARK_CYAN(0, 128, 128), VERY_DARK_CYAN(0, 64, 64), + BLUE(0, 0, 255), DARK_BLUE(0, 0, 128), VERY_DARK_BLUE(0, 0, 64), + MAGENTA(255, 0, 255), DARK_MAGENTA(128, 0, 128), VERY_DARK_MAGENTA(64, 0, 64), + BLACK(0, 0, 0), + BLANK(0, 0, 0, 0); + + enum rcode + { + FAIL = 0, + OK = 1, + NO_FILE = -1, + }; + + //================================================================================== + + template + struct v2d_generic + { + T x = 0; + T y = 0; + + inline v2d_generic() : x(0), y(0) { } + inline v2d_generic(T _x, T _y) : x(_x), y(_y) { } + inline v2d_generic(const v2d_generic& v) : x(v.x), y(v.y){ } + inline T mag() { return sqrt(x * x + y * y); } + inline v2d_generic norm() { T r = 1 / mag(); return v2d_generic(x*r, y*r); } + inline v2d_generic perp() { return v2d_generic(-y, x); } + inline T dot(const v2d_generic& rhs) { return this->x * rhs.x + this->y * rhs.y; } + inline T cross(const v2d_generic& rhs) { return this->x * rhs.y - this->y * rhs.x; } + inline v2d_generic operator + (const v2d_generic& rhs) const { return v2d_generic(this->x + rhs.x, this->y + rhs.y);} + inline v2d_generic operator - (const v2d_generic& rhs) const { return v2d_generic(this->x - rhs.x, this->y - rhs.y);} + inline v2d_generic operator * (const T& rhs) const { return v2d_generic(this->x * rhs, this->y * rhs); } + inline v2d_generic operator / (const T& rhs) const { return v2d_generic(this->x / rhs, this->y / rhs); } + inline v2d_generic& operator += (const v2d_generic& rhs) { this->x += rhs.x; this->y += rhs.y; return *this; } + inline v2d_generic& operator -= (const v2d_generic& rhs) { this->x -= rhs.x; this->y -= rhs.y; return *this; } + inline v2d_generic& operator *= (const T& rhs) { this->x *= rhs; this->y *= rhs; return *this; } + inline v2d_generic& operator /= (const T& rhs) { this->x /= rhs; this->y /= rhs; return *this; } + inline T& operator [] (std::size_t i) { return *((T*)this + i); /* <-- D'oh :( */ } + }; + + template inline v2d_generic operator * (const float& lhs, const v2d_generic& rhs) { return v2d_generic(lhs * rhs.x, lhs * rhs.y); } + template inline v2d_generic operator * (const double& lhs, const v2d_generic& rhs){ return v2d_generic(lhs * rhs.x, lhs * rhs.y); } + template inline v2d_generic operator * (const int& lhs, const v2d_generic& rhs) { return v2d_generic(lhs * rhs.x, lhs * rhs.y); } + template inline v2d_generic operator / (const float& lhs, const v2d_generic& rhs) { return v2d_generic(lhs / rhs.x, lhs / rhs.y); } + template inline v2d_generic operator / (const double& lhs, const v2d_generic& rhs){ return v2d_generic(lhs / rhs.x, lhs / rhs.y); } + template inline v2d_generic operator / (const int& lhs, const v2d_generic& rhs) { return v2d_generic(lhs / rhs.x, lhs / rhs.y); } + + typedef v2d_generic vi2d; + typedef v2d_generic vf2d; + typedef v2d_generic vd2d; + + //============================================================= + + struct HWButton + { + bool bPressed = false; // Set once during the frame the event occurs + bool bReleased = false; // Set once during the frame the event occurs + bool bHeld = false; // Set true for all frames between pressed and released events + }; + + //============================================================= + + + class ResourcePack + { + public: + ResourcePack(); + ~ResourcePack(); + struct sEntry : public std::streambuf { + uint32_t nID, nFileOffset, nFileSize; uint8_t* data; void _config() { this->setg((char*)data, (char*)data, (char*)(data + nFileSize)); } + }; + + public: + olc::rcode AddToPack(std::string sFile); + + public: + olc::rcode SavePack(std::string sFile); + olc::rcode LoadPack(std::string sFile); + olc::rcode ClearPack(); + + public: + olc::ResourcePack::sEntry GetStreamBuffer(std::string sFile); + + private: + + std::map mapFiles; + }; + + //============================================================= + + // A bitmap-like structure that stores a 2D array of Pixels + class Sprite + { + public: + Sprite(); + Sprite(std::string sImageFile); + Sprite(std::string sImageFile, olc::ResourcePack *pack); + Sprite(int32_t w, int32_t h); + ~Sprite(); + + public: + olc::rcode LoadFromFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); + olc::rcode LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack = nullptr); + olc::rcode SaveToPGESprFile(std::string sImageFile); + + public: + int32_t width = 0; + int32_t height = 0; + enum Mode { NORMAL, PERIODIC }; + + public: + void SetSampleMode(olc::Sprite::Mode mode = olc::Sprite::Mode::NORMAL); + Pixel GetPixel(int32_t x, int32_t y); + bool SetPixel(int32_t x, int32_t y, Pixel p); + + Pixel Sample(float x, float y); + Pixel SampleBL(float u, float v); + Pixel* GetData(); + + private: + Pixel *pColData = nullptr; + Mode modeSample = Mode::NORMAL; + +#ifdef OLC_DBG_OVERDRAW + public: + static int nOverdrawCount; +#endif + + }; + + //============================================================= + + enum Key + { + NONE, + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + K0, K1, K2, K3, K4, K5, K6, K7, K8, K9, + F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + UP, DOWN, LEFT, RIGHT, + SPACE, TAB, SHIFT, CTRL, INS, DEL, HOME, END, PGUP, PGDN, + BACK, ESCAPE, RETURN, ENTER, PAUSE, SCROLL, + NP0, NP1, NP2, NP3, NP4, NP5, NP6, NP7, NP8, NP9, + NP_MUL, NP_DIV, NP_ADD, NP_SUB, NP_DECIMAL, + }; + + + //============================================================= + + class PixelGameEngine + { + public: + PixelGameEngine(); + + public: + olc::rcode Construct(uint32_t screen_w, uint32_t screen_h, uint32_t pixel_w, uint32_t pixel_h, bool full_screen = false); + olc::rcode Start(); + + public: // Override Interfaces + // Called once on application startup, use to load your resources + virtual bool OnUserCreate(); + // Called every frame, and provides you with a time per frame value + virtual bool OnUserUpdate(float fElapsedTime); + // Called once on application termination, so you can be a clean coder + virtual bool OnUserDestroy(); + + public: // Hardware Interfaces + // Returns true if window is currently in focus + bool IsFocused(); + // Get the state of a specific keyboard button + HWButton GetKey(Key k); + // Get the state of a specific mouse button + HWButton GetMouse(uint32_t b); + // Get Mouse X coordinate in "pixel" space + int32_t GetMouseX(); + // Get Mouse Y coordinate in "pixel" space + int32_t GetMouseY(); + // Get Mouse Wheel Delta + int32_t GetMouseWheel(); + + public: // Utility + // Returns the width of the screen in "pixels" + int32_t ScreenWidth(); + // Returns the height of the screen in "pixels" + int32_t ScreenHeight(); + // Returns the width of the currently selected drawing target in "pixels" + int32_t GetDrawTargetWidth(); + // Returns the height of the currently selected drawing target in "pixels" + int32_t GetDrawTargetHeight(); + // Returns the currently active draw target + Sprite* GetDrawTarget(); + + public: // Draw Routines + // Specify which Sprite should be the target of drawing functions, use nullptr + // to specify the primary screen + void SetDrawTarget(Sprite *target); + // Change the pixel mode for different optimisations + // olc::Pixel::NORMAL = No transparency + // olc::Pixel::MASK = Transparent if alpha is < 255 + // olc::Pixel::ALPHA = Full transparency + void SetPixelMode(Pixel::Mode m); + Pixel::Mode GetPixelMode(); + // Use a custom blend function + void SetPixelMode(std::function pixelMode); + // Change the blend factor form between 0.0f to 1.0f; + void SetPixelBlend(float fBlend); + // Offset texels by sub-pixel amount (advanced, do not use) + void SetSubPixelOffset(float ox, float oy); + + // Draws a single Pixel + virtual bool Draw(int32_t x, int32_t y, Pixel p = olc::WHITE); + // Draws a line from (x1,y1) to (x2,y2) + void DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); + // Draws a circle located at (x,y) with radius + void DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE, uint8_t mask = 0xFF); + // Fills a circle located at (x,y) with radius + void FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE); + // Draws a rectangle at (x,y) to (x+w,y+h) + void DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + // Fills a rectangle at (x,y) to (x+w,y+h) + void FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + // Draws a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + // Flat fills a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + // Draws an entire sprite at location (x,y) + void DrawSprite(int32_t x, int32_t y, Sprite *sprite, uint32_t scale = 1); + // Draws an area of a sprite at location (x,y), where the + // selected area is (ox,oy) to (ox+w,oy+h) + void DrawPartialSprite(int32_t x, int32_t y, Sprite *sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale = 1); + // Draws a single line of text + void DrawString(int32_t x, int32_t y, std::string sText, Pixel col = olc::WHITE, uint32_t scale = 1); + // Clears entire draw target to Pixel + void Clear(Pixel p); + + public: // Branding + std::string sAppName; + + private: // Inner mysterious workings + Sprite *pDefaultDrawTarget = nullptr; + Sprite *pDrawTarget = nullptr; + Pixel::Mode nPixelMode = Pixel::NORMAL; + float fBlendFactor = 1.0f; + uint32_t nScreenWidth = 256; + uint32_t nScreenHeight = 240; + uint32_t nPixelWidth = 4; + uint32_t nPixelHeight = 4; + int32_t nMousePosX = 0; + int32_t nMousePosY = 0; + int32_t nMouseWheelDelta = 0; + int32_t nMousePosXcache = 0; + int32_t nMousePosYcache = 0; + int32_t nMouseWheelDeltaCache = 0; + int32_t nWindowWidth = 0; + int32_t nWindowHeight = 0; + int32_t nViewX = 0; + int32_t nViewY = 0; + int32_t nViewW = 0; + int32_t nViewH = 0; + bool bFullScreen = false; + float fPixelX = 1.0f; + float fPixelY = 1.0f; + float fSubPixelOffsetX = 0.0f; + float fSubPixelOffsetY = 0.0f; + bool bHasInputFocus = false; + bool bHasMouseFocus = false; + float fFrameTimer = 1.0f; + int nFrameCount = 0; + Sprite *fontSprite = nullptr; + std::function funcPixelMode; + + static std::map mapKeys; + bool pKeyNewState[256]{ 0 }; + bool pKeyOldState[256]{ 0 }; + HWButton pKeyboardState[256]; + + bool pMouseNewState[5]{ 0 }; + bool pMouseOldState[5]{ 0 }; + HWButton pMouseState[5]; + +#ifdef _WIN32 + HDC glDeviceContext = nullptr; + HGLRC glRenderContext = nullptr; +#else + GLXContext glDeviceContext = nullptr; + GLXContext glRenderContext = nullptr; +#endif + GLuint glBuffer; + + void EngineThread(); + + // If anything sets this flag to false, the engine + // "should" shut down gracefully + static std::atomic bAtomActive; + + // Common initialisation functions + void olc_UpdateMouse(int32_t x, int32_t y); + void olc_UpdateMouseWheel(int32_t delta); + void olc_UpdateWindowSize(int32_t x, int32_t y); + void olc_UpdateViewport(); + bool olc_OpenGLCreate(); + void olc_ConstructFontSheet(); + + +#ifdef _WIN32 + // Windows specific window handling + HWND olc_hWnd = nullptr; + HWND olc_WindowCreate(); + std::wstring wsAppName; + static LRESULT CALLBACK olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +#else + // Non-Windows specific window handling + Display* olc_Display = nullptr; + Window olc_WindowRoot; + Window olc_Window; + XVisualInfo* olc_VisualInfo; + Colormap olc_ColourMap; + XSetWindowAttributes olc_SetWindowAttribs; + Display* olc_WindowCreate(); +#endif + + }; + + + class PGEX + { + friend class olc::PixelGameEngine; + protected: + static PixelGameEngine* pge; + }; + + //============================================================= +} + +#endif // OLC_PGE_DEF + + + + +/* + Object Oriented Mode + ~~~~~~~~~~~~~~~~~~~~ + + If the olcPixelGameEngine.h is called from several sources it can cause + multiple definitions of objects. To prevent this, ONLY ONE of the pathways + to including this file must have OLC_PGE_APPLICATION defined before it. This prevents + the definitions being duplicated. + + If all else fails, create a file called "olcPixelGameEngine.cpp" with the following + two lines. Then you can just #include "olcPixelGameEngine.h" as normal without worrying + about defining things. Dont forget to include that cpp file as part of your build! + + #define OLC_PGE_APPLICATION + #include "olcPixelGameEngine.h" + +*/ + +#ifdef OLC_PGE_APPLICATION +#undef OLC_PGE_APPLICATION + +namespace olc +{ + Pixel::Pixel() + { + r = 0; g = 0; b = 0; a = 255; + } + + Pixel::Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) + { + r = red; g = green; b = blue; a = alpha; + } + + Pixel::Pixel(uint32_t p) + { + n = p; + } + + //========================================================== + + std::wstring ConvertS2W(std::string s) + { +#ifdef _WIN32 + int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); + wchar_t* buffer = new wchar_t[count]; + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); + std::wstring w(buffer); + delete[] buffer; + return w; +#else + return L"SVN FTW!"; +#endif + } + + Sprite::Sprite() + { + pColData = nullptr; + width = 0; + height = 0; + } + + Sprite::Sprite(std::string sImageFile) + { + LoadFromFile(sImageFile); + } + + Sprite::Sprite(std::string sImageFile, olc::ResourcePack *pack) + { + LoadFromPGESprFile(sImageFile, pack); + } + + Sprite::Sprite(int32_t w, int32_t h) + { + if(pColData) delete[] pColData; + width = w; height = h; + pColData = new Pixel[width * height]; + for (int32_t i = 0; i < width*height; i++) + pColData[i] = Pixel(); + } + + Sprite::~Sprite() + { + if (pColData) delete pColData; + } + + olc::rcode Sprite::LoadFromPGESprFile(std::string sImageFile, olc::ResourcePack *pack) + { + if (pColData) delete[] pColData; + + auto ReadData = [&](std::istream &is) + { + is.read((char*)&width, sizeof(int32_t)); + is.read((char*)&height, sizeof(int32_t)); + pColData = new Pixel[width * height]; + is.read((char*)pColData, width * height * sizeof(uint32_t)); + }; + + // These are essentially Memory Surfaces represented by olc::Sprite + // which load very fast, but are completely uncompressed + if (pack == nullptr) + { + std::ifstream ifs; + ifs.open(sImageFile, std::ifstream::binary); + if (ifs.is_open()) + { + ReadData(ifs); + return olc::OK; + } + else + return olc::FAIL; + } + else + { + auto streamBuffer = pack->GetStreamBuffer(sImageFile); + std::istream is(&streamBuffer); + ReadData(is); + } + + + return olc::FAIL; + } + + olc::rcode Sprite::SaveToPGESprFile(std::string sImageFile) + { + if (pColData == nullptr) return olc::FAIL; + + std::ofstream ofs; + ofs.open(sImageFile, std::ifstream::binary); + if (ofs.is_open()) + { + ofs.write((char*)&width, sizeof(int32_t)); + ofs.write((char*)&height, sizeof(int32_t)); + ofs.write((char*)pColData, width*height*sizeof(uint32_t)); + ofs.close(); + return olc::OK; + } + + return olc::FAIL; + } + + olc::rcode Sprite::LoadFromFile(std::string sImageFile, olc::ResourcePack *pack) + { +#ifdef _WIN32 + // Use GDI+ + std::wstring wsImageFile; +#ifdef __MINGW32__ + wchar_t *buffer = new wchar_t[sImageFile.length() + 1]; + mbstowcs(buffer, sImageFile.c_str(), sImageFile.length()); + buffer[sImageFile.length()] = L'\0'; + wsImageFile = buffer; + delete [] buffer; +#else + wsImageFile = ConvertS2W(sImageFile); +#endif + Gdiplus::Bitmap *bmp = Gdiplus::Bitmap::FromFile(wsImageFile.c_str()); + if (bmp == nullptr) + return olc::NO_FILE; + + width = bmp->GetWidth(); + height = bmp->GetHeight(); + pColData = new Pixel[width * height]; + + for(int x=0; xGetPixel(x, y, &c); + SetPixel(x, y, Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha())); + } + delete bmp; + return olc::OK; +#else + //////////////////////////////////////////////////////////////////////////// + // Use libpng, Thanks to Guillaume Cottenceau + // https://gist.github.com/niw/5963798 + png_structp png; + png_infop info; + + FILE *f = fopen(sImageFile.c_str(), "rb"); + if (!f) return olc::NO_FILE; + + png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) goto fail_load; + + info = png_create_info_struct(png); + if (!info) goto fail_load; + + if (setjmp(png_jmpbuf(png))) goto fail_load; + + png_init_io(png, f); + png_read_info(png, info); + + png_byte color_type; + png_byte bit_depth; + png_bytep *row_pointers; + width = png_get_image_width(png, info); + height = png_get_image_height(png, info); + color_type = png_get_color_type(png, info); + bit_depth = png_get_bit_depth(png, info); + +#ifdef _DEBUG + std::cout << "Loading PNG: " << sImageFile << "\n"; + std::cout << "W:" << width << " H:" << height << " D:" << (int)bit_depth << "\n"; +#endif + + if (bit_depth == 16) png_set_strip_16(png); + if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); + if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); + if (color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_PALETTE) + png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + + png_read_update_info(png, info); + row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height); + for (int y = 0; y < height; y++) { + row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); + } + png_read_image(png, row_pointers); + //////////////////////////////////////////////////////////////////////////// + + // Create sprite array + pColData = new Pixel[width * height]; + + // Iterate through image rows, converting into sprite format + for (int y = 0; y < height; y++) + { + png_bytep row = row_pointers[y]; + for (int x = 0; x < width; x++) + { + png_bytep px = &(row[x * 4]); + SetPixel(x, y, Pixel(px[0], px[1], px[2], px[3])); + } + } + + fclose(f); + return olc::OK; + + fail_load: + width = 0; + height = 0; + fclose(f); + pColData = nullptr; + return olc::FAIL; +#endif + } + + void Sprite::SetSampleMode(olc::Sprite::Mode mode) + { + modeSample = mode; + } + + + Pixel Sprite::GetPixel(int32_t x, int32_t y) + { + if (modeSample == olc::Sprite::Mode::NORMAL) + { + if (x >= 0 && x < width && y >= 0 && y < height) + return pColData[y*width + x]; + else + return Pixel(0, 0, 0, 0); + } + else + { + return pColData[abs(y%height)*width + abs(x%width)]; + } + } + + bool Sprite::SetPixel(int32_t x, int32_t y, Pixel p) + { + +#ifdef OLC_DBG_OVERDRAW + nOverdrawCount++; +#endif + + if (x >= 0 && x < width && y >= 0 && y < height) + { + pColData[y*width + x] = p; + return true; + } + else + return false; + } + + Pixel Sprite::Sample(float x, float y) + { + int32_t sx = std::min((int32_t)((x * (float)width)), width - 1); + int32_t sy = std::min((int32_t)((y * (float)height)), height - 1); + return GetPixel(sx, sy); + } + + Pixel Sprite::SampleBL(float u, float v) + { + u = u * width - 0.5f; + v = v * height - 0.5f; + int x = (int)floor(u); // cast to int rounds toward zero, not downward + int y = (int)floor(v); // Thanks @joshinils + float u_ratio = u - x; + float v_ratio = v - y; + float u_opposite = 1 - u_ratio; + float v_opposite = 1 - v_ratio; + + olc::Pixel p1 = GetPixel(std::max(x, 0), std::max(y, 0)); + olc::Pixel p2 = GetPixel(std::min(x + 1, (int)width - 1), std::max(y, 0)); + olc::Pixel p3 = GetPixel(std::max(x, 0), std::min(y + 1, (int)height - 1)); + olc::Pixel p4 = GetPixel(std::min(x + 1, (int)width - 1), std::min(y + 1, (int)height - 1)); + + return olc::Pixel( + (uint8_t)((p1.r * u_opposite + p2.r * u_ratio) * v_opposite + (p3.r * u_opposite + p4.r * u_ratio) * v_ratio), + (uint8_t)((p1.g * u_opposite + p2.g * u_ratio) * v_opposite + (p3.g * u_opposite + p4.g * u_ratio) * v_ratio), + (uint8_t)((p1.b * u_opposite + p2.b * u_ratio) * v_opposite + (p3.b * u_opposite + p4.b * u_ratio) * v_ratio)); + } + + Pixel* Sprite::GetData() { return pColData; } + + //========================================================== + + ResourcePack::ResourcePack() + { + + } + + ResourcePack::~ResourcePack() + { + ClearPack(); + } + + olc::rcode ResourcePack::AddToPack(std::string sFile) + { + std::ifstream ifs(sFile, std::ifstream::binary); + if (!ifs.is_open()) return olc::FAIL; + + // Get File Size + std::streampos p = 0; + p = ifs.tellg(); + ifs.seekg(0, std::ios::end); + p = ifs.tellg() - p; + ifs.seekg(0, std::ios::beg); + + // Create entry + sEntry e; + e.data = nullptr; + e.nFileSize = (uint32_t)p; + + // Read file into memory + e.data = new uint8_t[(uint32_t)e.nFileSize]; + ifs.read((char*)e.data, e.nFileSize); + ifs.close(); + + // Add To Map + mapFiles[sFile] = e; + return olc::OK; + } + + olc::rcode ResourcePack::SavePack(std::string sFile) + { + std::ofstream ofs(sFile, std::ofstream::binary); + if (!ofs.is_open()) return olc::FAIL; + + // 1) Write Map + size_t nMapSize = mapFiles.size(); + ofs.write((char*)&nMapSize, sizeof(size_t)); + for (auto &e : mapFiles) + { + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(size_t)); + ofs.write(e.first.c_str(), nPathSize); + ofs.write((char*)&e.second.nID, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t)); + } + + // 2) Write Data + std::streampos offset = ofs.tellp(); + for (auto &e : mapFiles) + { + e.second.nFileOffset = (uint32_t)offset; + ofs.write((char*)e.second.data, e.second.nFileSize); + offset += e.second.nFileSize; + } + + // 3) Rewrite Map (it has been updated with offsets now) + ofs.seekp(std::ios::beg); + ofs.write((char*)&nMapSize, sizeof(size_t)); + for (auto &e : mapFiles) + { + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(size_t)); + ofs.write(e.first.c_str(), nPathSize); + ofs.write((char*)&e.second.nID, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nFileOffset, sizeof(uint32_t)); + } + ofs.close(); + + return olc::OK; + } + + olc::rcode ResourcePack::LoadPack(std::string sFile) + { + std::ifstream ifs(sFile, std::ifstream::binary); + if (!ifs.is_open()) return olc::FAIL; + + // 1) Read Map + uint32_t nMapEntries; + ifs.read((char*)&nMapEntries, sizeof(uint32_t)); + for (uint32_t i = 0; i < nMapEntries; i++) + { + uint32_t nFilePathSize = 0; + ifs.read((char*)&nFilePathSize, sizeof(uint32_t)); + + std::string sFileName(nFilePathSize, ' '); + for (uint32_t j = 0; j < nFilePathSize; j++) + sFileName[j] = ifs.get(); + + sEntry e; + e.data = nullptr; + ifs.read((char*)&e.nID, sizeof(uint32_t)); + ifs.read((char*)&e.nFileSize, sizeof(uint32_t)); + ifs.read((char*)&e.nFileOffset, sizeof(uint32_t)); + mapFiles[sFileName] = e; + } + + // 2) Read Data + for (auto &e : mapFiles) + { + e.second.data = new uint8_t[(uint32_t)e.second.nFileSize]; + ifs.seekg(e.second.nFileOffset); + ifs.read((char*)e.second.data, e.second.nFileSize); + e.second._config(); + } + + ifs.close(); + return olc::OK; + } + + olc::ResourcePack::sEntry ResourcePack::GetStreamBuffer(std::string sFile) + { + return mapFiles[sFile]; + } + + olc::rcode ResourcePack::ClearPack() + { + for (auto &e : mapFiles) + { + if (e.second.data != nullptr) + delete[] e.second.data; + } + + mapFiles.clear(); + return olc::OK; + } + + //========================================================== + + PixelGameEngine::PixelGameEngine() + { + sAppName = "Undefined"; + olc::PGEX::pge = this; + } + + olc::rcode PixelGameEngine::Construct(uint32_t screen_w, uint32_t screen_h, uint32_t pixel_w, uint32_t pixel_h, bool full_screen) + { + nScreenWidth = screen_w; + nScreenHeight = screen_h; + nPixelWidth = pixel_w; + nPixelHeight = pixel_h; + bFullScreen = full_screen; + + fPixelX = 2.0f / (float)(nScreenWidth); + fPixelY = 2.0f / (float)(nScreenHeight); + + if (nPixelWidth == 0 || nPixelHeight == 0 || nScreenWidth == 0 || nScreenHeight == 0) + return olc::FAIL; + +#ifdef _WIN32 +#ifdef UNICODE +#ifndef __MINGW32__ + wsAppName = ConvertS2W(sAppName); +#endif +#endif +#endif + // Load the default font sheet + olc_ConstructFontSheet(); + + // Create a sprite that represents the primary drawing target + pDefaultDrawTarget = new Sprite(nScreenWidth, nScreenHeight); + SetDrawTarget(nullptr); + return olc::OK; + } + + olc::rcode PixelGameEngine::Start() + { + // Construct the window + if (!olc_WindowCreate()) + return olc::FAIL; + + // Load libraries required for PNG file interaction +//#ifdef _WIN32 +// // Windows use GDI+ +// Gdiplus::GdiplusStartupInput startupInput; +// ULONG_PTR token; +// Gdiplus::GdiplusStartup(&token, &startupInput, NULL); +//#else +// // Linux use libpng +// +//#endif + // Start the thread + bAtomActive = true; + std::thread t = std::thread(&PixelGameEngine::EngineThread, this); + +#ifdef _WIN32 + // Handle Windows Message Loop + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +#endif + + // Wait for thread to be exited + t.join(); + return olc::OK; + } + + void PixelGameEngine::SetDrawTarget(Sprite *target) + { + if (target) + pDrawTarget = target; + else + pDrawTarget = pDefaultDrawTarget; + } + + Sprite* PixelGameEngine::GetDrawTarget() + { + return pDrawTarget; + } + + int32_t PixelGameEngine::GetDrawTargetWidth() + { + if (pDrawTarget) + return pDrawTarget->width; + else + return 0; + } + + int32_t PixelGameEngine::GetDrawTargetHeight() + { + if (pDrawTarget) + return pDrawTarget->height; + else + return 0; + } + + bool PixelGameEngine::IsFocused() + { + return bHasInputFocus; + } + + HWButton PixelGameEngine::GetKey(Key k) + { + return pKeyboardState[k]; + } + + HWButton PixelGameEngine::GetMouse(uint32_t b) + { + return pMouseState[b]; + } + + int32_t PixelGameEngine::GetMouseX() + { + return nMousePosX; + } + + int32_t PixelGameEngine::GetMouseY() + { + return nMousePosY; + } + + int32_t PixelGameEngine::GetMouseWheel() + { + return nMouseWheelDelta; + } + + int32_t PixelGameEngine::ScreenWidth() + { + return nScreenWidth; + } + + int32_t PixelGameEngine::ScreenHeight() + { + return nScreenHeight; + } + + bool PixelGameEngine::Draw(int32_t x, int32_t y, Pixel p) + { + if (!pDrawTarget) return false; + + + if (nPixelMode == Pixel::NORMAL) + { + return pDrawTarget->SetPixel(x, y, p); + } + + if (nPixelMode == Pixel::MASK) + { + if(p.a == 255) + return pDrawTarget->SetPixel(x, y, p); + } + + if (nPixelMode == Pixel::ALPHA) + { + Pixel d = pDrawTarget->GetPixel(x, y); + float a = (float)(p.a / 255.0f) * fBlendFactor; + float c = 1.0f - a; + float r = a * (float)p.r + c * (float)d.r; + float g = a * (float)p.g + c * (float)d.g; + float b = a * (float)p.b + c * (float)d.b; + return pDrawTarget->SetPixel(x, y, Pixel((uint8_t)r, (uint8_t)g, (uint8_t)b)); + } + + if (nPixelMode == Pixel::CUSTOM) + { + return pDrawTarget->SetPixel(x, y, funcPixelMode(x, y, p, pDrawTarget->GetPixel(x, y))); + } + + return false; + } + + void PixelGameEngine::SetSubPixelOffset(float ox, float oy) + { + fSubPixelOffsetX = ox * fPixelX; + fSubPixelOffsetY = oy * fPixelY; + } + + void PixelGameEngine::DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p, uint32_t pattern) + { + int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; + dx = x2 - x1; dy = y2 - y1; + + auto rol = [&](void) + { + pattern = (pattern << 1) | (pattern >> 31); + return pattern & 1; + }; + + // straight lines idea by gurkanctn + if (dx == 0) // Line is vertical + { + if (y2 < y1) std::swap(y1, y2); + for (y = y1; y <= y2; y++) + if (rol()) Draw(x1, y, p); + return; + } + + if (dy == 0) // Line is horizontal + { + if (x2 < x1) std::swap(x1, x2); + for (x = x1; x <= x2; x++) + if (rol()) Draw(x, y1, p); + return; + } + + // Line is Funk-aye + dx1 = abs(dx); dy1 = abs(dy); + px = 2 * dy1 - dx1; py = 2 * dx1 - dy1; + if (dy1 <= dx1) + { + if (dx >= 0) + { + x = x1; y = y1; xe = x2; + } + else + { + x = x2; y = y2; xe = x1; + } + + if (rol()) Draw(x, y, p); + + for (i = 0; x0 && dy>0)) y = y + 1; else y = y - 1; + px = px + 2 * (dy1 - dx1); + } + if (rol()) Draw(x, y, p); + } + } + else + { + if (dy >= 0) + { + x = x1; y = y1; ye = y2; + } + else + { + x = x2; y = y2; ye = y1; + } + + if (rol()) Draw(x, y, p); + + for (i = 0; y0 && dy>0)) x = x + 1; else x = x - 1; + py = py + 2 * (dx1 - dy1); + } + if (rol()) Draw(x, y, p); + } + } + } + + void PixelGameEngine::DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p, uint8_t mask) + { + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + if (!radius) return; + + while (y0 >= x0) // only formulate 1/8 of circle + { + if (mask & 0x01) Draw(x + x0, y - y0, p); + if (mask & 0x02) Draw(x + y0, y - x0, p); + if (mask & 0x04) Draw(x + y0, y + x0, p); + if (mask & 0x08) Draw(x + x0, y + y0, p); + if (mask & 0x10) Draw(x - x0, y + y0, p); + if (mask & 0x20) Draw(x - y0, y + x0, p); + if (mask & 0x40) Draw(x - y0, y - x0, p); + if (mask & 0x80) Draw(x - x0, y - y0, p); + if (d < 0) d += 4 * x0++ + 6; + else d += 4 * (x0++ - y0--) + 10; + } + } + + void PixelGameEngine::FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p) + { + // Taken from wikipedia + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + if (!radius) return; + + auto drawline = [&](int sx, int ex, int ny) + { + for (int i = sx; i <= ex; i++) + Draw(i, ny, p); + }; + + while (y0 >= x0) + { + // Modified to draw scan-lines instead of edges + drawline(x - x0, x + x0, y - y0); + drawline(x - y0, x + y0, y - x0); + drawline(x - x0, x + x0, y + y0); + drawline(x - y0, x + y0, y + x0); + if (d < 0) d += 4 * x0++ + 6; + else d += 4 * (x0++ - y0--) + 10; + } + } + + void PixelGameEngine::DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + DrawLine(x, y, x+w, y, p); + DrawLine(x+w, y, x+w, y+h, p); + DrawLine(x+w, y+h, x, y+h, p); + DrawLine(x, y+h, x, y, p); + } + + void PixelGameEngine::Clear(Pixel p) + { + int pixels = GetDrawTargetWidth() * GetDrawTargetHeight(); + Pixel* m = GetDrawTarget()->GetData(); + for (int i = 0; i < pixels; i++) + m[i] = p; +#ifdef OLC_DBG_OVERDRAW + olc::Sprite::nOverdrawCount += pixels; +#endif + } + + void PixelGameEngine::FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + int32_t x2 = x + w; + int32_t y2 = y + h; + + if (x < 0) x = 0; + if (x >= (int32_t)nScreenWidth) x = (int32_t)nScreenWidth; + if (y < 0) y = 0; + if (y >= (int32_t)nScreenHeight) y = (int32_t)nScreenHeight; + + if (x2 < 0) x2 = 0; + if (x2 >= (int32_t)nScreenWidth) x2 = (int32_t)nScreenWidth; + if (y2 < 0) y2 = 0; + if (y2 >= (int32_t)nScreenHeight) y2 = (int32_t)nScreenHeight; + + for (int i = x; i < x2; i++) + for (int j = y; j < y2; j++) + Draw(i, j, p); + } + + void PixelGameEngine::DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + DrawLine(x1, y1, x2, y2, p); + DrawLine(x2, y2, x3, y3, p); + DrawLine(x3, y3, x1, y1, p); + } + + // https://www.avrfreaks.net/sites/default/files/triangles.c + void PixelGameEngine::FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + auto SWAP = [](int &x, int &y) { int t = x; x = y; y = t; }; + auto drawline = [&](int sx, int ex, int ny) { for (int i = sx; i <= ex; i++) Draw(i, ny, p); }; + + int t1x, t2x, y, minx, maxx, t1xp, t2xp; + bool changed1 = false; + bool changed2 = false; + int signx1, signx2, dx1, dy1, dx2, dy2; + int e1, e2; + // Sort vertices + if (y1>y2) { SWAP(y1, y2); SWAP(x1, x2); } + if (y1>y3) { SWAP(y1, y3); SWAP(x1, x3); } + if (y2>y3) { SWAP(y2, y3); SWAP(x2, x3); } + + t1x = t2x = x1; y = y1; // Starting points + dx1 = (int)(x2 - x1); if (dx1<0) { dx1 = -dx1; signx1 = -1; } + else signx1 = 1; + dy1 = (int)(y2 - y1); + + dx2 = (int)(x3 - x1); if (dx2<0) { dx2 = -dx2; signx2 = -1; } + else signx2 = 1; + dy2 = (int)(y3 - y1); + + if (dy1 > dx1) { // swap values + SWAP(dx1, dy1); + changed1 = true; + } + if (dy2 > dx2) { // swap values + SWAP(dy2, dx2); + changed2 = true; + } + + e2 = (int)(dx2 >> 1); + // Flat top, just process the second half + if (y1 == y2) goto next; + e1 = (int)(dx1 >> 1); + + for (int i = 0; i < dx1;) { + t1xp = 0; t2xp = 0; + if (t1x= dx1) { + e1 -= dx1; + if (changed1) t1xp = signx1;//t1x += signx1; + else goto next1; + } + if (changed1) break; + else t1x += signx1; + } + // Move line + next1: + // process second line until y value is about to change + while (1) { + e2 += dy2; + while (e2 >= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2;//t2x += signx2; + else goto next2; + } + if (changed2) break; + else t2x += signx2; + } + next2: + if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x; + if (maxx dx1) { // swap values + SWAP(dy1, dx1); + changed1 = true; + } + else changed1 = false; + + e1 = (int)(dx1 >> 1); + + for (int i = 0; i <= dx1; i++) { + t1xp = 0; t2xp = 0; + if (t1x= dx1) { + e1 -= dx1; + if (changed1) { t1xp = signx1; break; }//t1x += signx1; + else goto next3; + } + if (changed1) break; + else t1x += signx1; + if (i= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2; + else goto next4; + } + if (changed2) break; + else t2x += signx2; + } + next4: + + if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x; + if (maxxy3) return; + } + } + + void PixelGameEngine::DrawSprite(int32_t x, int32_t y, Sprite *sprite, uint32_t scale) + { + if (sprite == nullptr) + return; + + if (scale > 1) + { + for (int32_t i = 0; i < sprite->width; i++) + for (int32_t j = 0; j < sprite->height; j++) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i*scale) + is, y + (j*scale) + js, sprite->GetPixel(i, j)); + } + else + { + for (int32_t i = 0; i < sprite->width; i++) + for (int32_t j = 0; j < sprite->height; j++) + Draw(x + i, y + j, sprite->GetPixel(i, j)); + } + } + + void PixelGameEngine::DrawPartialSprite(int32_t x, int32_t y, Sprite *sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale) + { + if (sprite == nullptr) + return; + + if (scale > 1) + { + for (int32_t i = 0; i < w; i++) + for (int32_t j = 0; j < h; j++) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i*scale) + is, y + (j*scale) + js, sprite->GetPixel(i + ox, j + oy)); + } + else + { + for (int32_t i = 0; i < w; i++) + for (int32_t j = 0; j < h; j++) + Draw(x + i, y + j, sprite->GetPixel(i + ox, j + oy)); + } + } + + void PixelGameEngine::DrawString(int32_t x, int32_t y, std::string sText, Pixel col, uint32_t scale) + { + int32_t sx = 0; + int32_t sy = 0; + Pixel::Mode m = nPixelMode; + if(col.ALPHA != 255) SetPixelMode(Pixel::ALPHA); + else SetPixelMode(Pixel::MASK); + for (auto c : sText) + { + if (c == '\n') + { + sx = 0; sy += 8 * scale; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + + if (scale > 1) + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + sx + (i*scale) + is, y + sy + (j*scale) + js, col); + } + else + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) + Draw(x + sx + i, y + sy + j, col); + } + sx += 8 * scale; + } + } + SetPixelMode(m); + } + + void PixelGameEngine::SetPixelMode(Pixel::Mode m) + { + nPixelMode = m; + } + + Pixel::Mode PixelGameEngine::GetPixelMode() + { + return nPixelMode; + } + + void PixelGameEngine::SetPixelMode(std::function pixelMode) + { + funcPixelMode = pixelMode; + nPixelMode = Pixel::Mode::CUSTOM; + } + + void PixelGameEngine::SetPixelBlend(float fBlend) + { + fBlendFactor = fBlend; + if (fBlendFactor < 0.0f) fBlendFactor = 0.0f; + if (fBlendFactor > 1.0f) fBlendFactor = 1.0f; + } + + // User must override these functions as required. I have not made + // them abstract because I do need a default behaviour to occur if + // they are not overwritten + bool PixelGameEngine::OnUserCreate() + { return false; } + bool PixelGameEngine::OnUserUpdate(float fElapsedTime) + { return false; } + bool PixelGameEngine::OnUserDestroy() + { return true; } + ////////////////////////////////////////////////////////////////// + + void PixelGameEngine::olc_UpdateViewport() + { + int32_t ww = nScreenWidth * nPixelWidth; + int32_t wh = nScreenHeight * nPixelHeight; + float wasp = (float)ww / (float)wh; + + nViewW = (int32_t)nWindowWidth; + nViewH = (int32_t)((float)nViewW / wasp); + + if (nViewH > nWindowHeight) + { + nViewH = nWindowHeight; + nViewW = (int32_t)((float)nViewH * wasp); + } + + nViewX = (nWindowWidth - nViewW) / 2; + nViewY = (nWindowHeight - nViewH) / 2; + } + + void PixelGameEngine::olc_UpdateWindowSize(int32_t x, int32_t y) + { + nWindowWidth = x; + nWindowHeight = y; + olc_UpdateViewport(); + + } + + void PixelGameEngine::olc_UpdateMouseWheel(int32_t delta) + { + nMouseWheelDeltaCache += delta; + } + + void PixelGameEngine::olc_UpdateMouse(int32_t x, int32_t y) + { + // Mouse coords come in screen space + // But leave in pixel space + + //if (bFullScreen) + { + // Full Screen mode may have a weird viewport we must clamp to + x -= nViewX; + y -= nViewY; + } + + nMousePosXcache = (int32_t)(((float)x / (float)(nWindowWidth - (nViewX * 2)) * (float)nScreenWidth)); + nMousePosYcache = (int32_t)(((float)y / (float)(nWindowHeight - (nViewY * 2)) * (float)nScreenHeight)); + + if (nMousePosXcache >= (int32_t)nScreenWidth) + nMousePosXcache = nScreenWidth - 1; + if (nMousePosYcache >= (int32_t)nScreenHeight) + nMousePosYcache = nScreenHeight - 1; + + if (nMousePosXcache < 0) + nMousePosXcache = 0; + if (nMousePosYcache < 0) + nMousePosYcache = 0; + } + + void PixelGameEngine::EngineThread() + { + // Start OpenGL, the context is owned by the game thread + olc_OpenGLCreate(); + + // Create Screen Texture - disable filtering + glEnable(GL_TEXTURE_2D); + glGenTextures(1, &glBuffer); + glBindTexture(GL_TEXTURE_2D, glBuffer); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nScreenWidth, nScreenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, pDefaultDrawTarget->GetData()); + + + // Create user resources as part of this thread + if (!OnUserCreate()) + bAtomActive = false; + + auto tp1 = std::chrono::system_clock::now(); + auto tp2 = std::chrono::system_clock::now(); + + while (bAtomActive) + { + // Run as fast as possible + while (bAtomActive) + { + // Handle Timing + tp2 = std::chrono::system_clock::now(); + std::chrono::duration elapsedTime = tp2 - tp1; + tp1 = tp2; + + // Our time per frame coefficient + float fElapsedTime = elapsedTime.count(); + +#ifndef _WIN32 + // Handle Xlib Message Loop - we do this in the + // same thread that OpenGL was created so we dont + // need to worry too much about multithreading with X11 + XEvent xev; + while (XPending(olc_Display)) + { + XNextEvent(olc_Display, &xev); + if (xev.type == Expose) + { + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + nWindowWidth = gwa.width; + nWindowHeight = gwa.height; + olc_UpdateViewport(); + glClear(GL_COLOR_BUFFER_BIT); // Thanks Benedani! + } + else if (xev.type == ConfigureNotify) + { + XConfigureEvent xce = xev.xconfigure; + nWindowWidth = xce.width; + nWindowHeight = xce.height; + } + else if (xev.type == KeyPress) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + pKeyNewState[mapKeys[sym]] = true; + XKeyEvent *e = (XKeyEvent *)&xev; // Because DragonEye loves numpads + XLookupString(e, NULL, 0, &sym, NULL); + pKeyNewState[mapKeys[sym]] = true; + } + else if (xev.type == KeyRelease) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + pKeyNewState[mapKeys[sym]] = false; + XKeyEvent *e = (XKeyEvent *)&xev; + XLookupString(e, NULL, 0, &sym, NULL); + pKeyNewState[mapKeys[sym]] = false; + } + else if (xev.type == ButtonPress) + { + switch (xev.xbutton.button) + { + case 1: pMouseNewState[0] = true; break; + case 2: pMouseNewState[2] = true; break; + case 3: pMouseNewState[1] = true; break; + case 4: olc_UpdateMouseWheel(120); break; + case 5: olc_UpdateMouseWheel(-120); break; + default: break; + } + } + else if (xev.type == ButtonRelease) + { + switch (xev.xbutton.button) + { + case 1: pMouseNewState[0] = false; break; + case 2: pMouseNewState[2] = false; break; + case 3: pMouseNewState[1] = false; break; + default: break; + } + } + else if (xev.type == MotionNotify) + { + olc_UpdateMouse(xev.xmotion.x, xev.xmotion.y); + } + else if (xev.type == FocusIn) + { + bHasInputFocus = true; + } + else if (xev.type == FocusOut) + { + bHasInputFocus = false; + } + else if (xev.type == ClientMessage) + { + bAtomActive = false; + } + } +#endif + + // Handle User Input - Keyboard + for (int i = 0; i < 256; i++) + { + pKeyboardState[i].bPressed = false; + pKeyboardState[i].bReleased = false; + + if (pKeyNewState[i] != pKeyOldState[i]) + { + if (pKeyNewState[i]) + { + pKeyboardState[i].bPressed = !pKeyboardState[i].bHeld; + pKeyboardState[i].bHeld = true; + } + else + { + pKeyboardState[i].bReleased = true; + pKeyboardState[i].bHeld = false; + } + } + + pKeyOldState[i] = pKeyNewState[i]; + } + + // Handle User Input - Mouse + for (int i = 0; i < 5; i++) + { + pMouseState[i].bPressed = false; + pMouseState[i].bReleased = false; + + if (pMouseNewState[i] != pMouseOldState[i]) + { + if (pMouseNewState[i]) + { + pMouseState[i].bPressed = !pMouseState[i].bHeld; + pMouseState[i].bHeld = true; + } + else + { + pMouseState[i].bReleased = true; + pMouseState[i].bHeld = false; + } + } + + pMouseOldState[i] = pMouseNewState[i]; + } + + // Cache mouse coordinates so they remain + // consistent during frame + nMousePosX = nMousePosXcache; + nMousePosY = nMousePosYcache; + + nMouseWheelDelta = nMouseWheelDeltaCache; + nMouseWheelDeltaCache = 0; + +#ifdef OLC_DBG_OVERDRAW + olc::Sprite::nOverdrawCount = 0; +#endif + + // Handle Frame Update + if (!OnUserUpdate(fElapsedTime)) + bAtomActive = false; + + // Display Graphics + glViewport(nViewX, nViewY, nViewW, nViewH); + + // TODO: This is a bit slow (especially in debug, but 100x faster in release mode???) + // Copy pixel array into texture + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, nScreenWidth, nScreenHeight, GL_RGBA, GL_UNSIGNED_BYTE, pDefaultDrawTarget->GetData()); + + // Display texture on screen + glBegin(GL_QUADS); + glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f + (fSubPixelOffsetX), -1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(0.0, 0.0); glVertex3f(-1.0f + (fSubPixelOffsetX), 1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(1.0, 0.0); glVertex3f( 1.0f + (fSubPixelOffsetX), 1.0f + (fSubPixelOffsetY), 0.0f); + glTexCoord2f(1.0, 1.0); glVertex3f( 1.0f + (fSubPixelOffsetX), -1.0f + (fSubPixelOffsetY), 0.0f); + glEnd(); + + // Present Graphics to screen +#ifdef _WIN32 + SwapBuffers(glDeviceContext); +#else + glXSwapBuffers(olc_Display, olc_Window); +#endif + + // Update Title Bar + fFrameTimer += fElapsedTime; + nFrameCount++; + if (fFrameTimer >= 1.0f) + { + fFrameTimer -= 1.0f; + + std::string sTitle = "OneLoneCoder.com - Pixel Game Engine - " + sAppName + " - FPS: " + std::to_string(nFrameCount); +#ifdef _WIN32 +#ifdef UNICODE + SetWindowText(olc_hWnd, ConvertS2W(sTitle).c_str()); +#else + SetWindowText(olc_hWnd, sTitle.c_str()); +#endif +#else + XStoreName(olc_Display, olc_Window, sTitle.c_str()); +#endif + nFrameCount = 0; + } + } + + // Allow the user to free resources if they have overrided the destroy function + if (OnUserDestroy()) + { + // User has permitted destroy, so exit and clean up + } + else + { + // User denied destroy for some reason, so continue running + bAtomActive = true; + } + } + +#ifdef _WIN32 + wglDeleteContext(glRenderContext); + PostMessage(olc_hWnd, WM_DESTROY, 0, 0); +#else + glXMakeCurrent(olc_Display, None, NULL); + glXDestroyContext(olc_Display, glDeviceContext); + XDestroyWindow(olc_Display, olc_Window); + XCloseDisplay(olc_Display); +#endif + + } + +#ifdef _WIN32 + // Thanks @MaGetzUb for this, which allows sprites to be defined + // at construction, by initialising the GDI subsystem + static class GDIPlusStartup + { + public: + GDIPlusStartup() + { + Gdiplus::GdiplusStartupInput startupInput; + ULONG_PTR token; + Gdiplus::GdiplusStartup(&token, &startupInput, NULL); + }; + } gdistartup; +#endif + + + void PixelGameEngine::olc_ConstructFontSheet() + { + std::string data; + data += "?Q`0001oOch0o01o@F40o000000000"; + data += "O000000nOT0063Qo4d8>?7a14Gno94AA4gno94AaOT0>o3`oO400o7QN00000400"; + data += "Of80001oOg<7O7moBGT7O7lABET024@aBEd714AiOdl717a_=TH013Q>00000000"; + data += "720D000V?V5oB3Q_HdUoE7a9@DdDE4A9@DmoE4A;Hg]oM4Aj8S4D84@`00000000"; + data += "OaPT1000Oa`^13P1@AI[?g`1@A=[OdAoHgljA4Ao?WlBA7l1710007l100000000"; + data += "ObM6000oOfMV?3QoBDD`O7a0BDDH@5A0BDD<@5A0BGeVO5ao@CQR?5Po00000000"; + data += "Oc``000?Ogij70PO2D]??0Ph2DUM@7i`2DTg@7lh2GUj?0TO0C1870T?00000000"; + data += "70<4001o?P<7?1QoHg43O;`h@GT0@:@LB@d0>:@hN@L0@?aoN@<0O7ao0000?000"; + data += "OcH0001SOglLA7mg24TnK7ln24US>0PL24U140PnOgl0>7QgOcH0K71S0000A000"; + data += "00H00000@Dm1S007@DUSg00?OdTnH7YhOfTL<7Yh@Cl0700?@Ah0300700000000"; + data += "<008001QL00ZA41a@6HnI<1i@FHLM81M@@0LG81?O`0nC?Y7?`0ZA7Y300080000"; + data += "O`082000Oh0827mo6>Hn?Wmo?6HnMb11MP08@C11H`08@FP0@@0004@000000000"; + data += "00P00001Oab00003OcKP0006@6=PMgl<@440MglH@000000`@000001P00000000"; + data += "Ob@8@@00Ob@8@Ga13R@8Mga172@8?PAo3R@827QoOb@820@0O`0007`0000007P0"; + data += "O`000P08Od400g`<3V=P0G`673IP0`@3>1`00P@6O`P00g`SetPixel(px, py, olc::Pixel(k, k, k, k)); + if (++py == 48) { px++; py = 0; } + } + } + } + +#ifdef _WIN32 + HWND PixelGameEngine::olc_WindowCreate() + { + WNDCLASS wc; + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpfnWndProc = olc_WindowEvent; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.lpszMenuName = nullptr; + wc.hbrBackground = nullptr; +#ifdef UNICODE + wc.lpszClassName = L"OLC_PIXEL_GAME_ENGINE"; +#else + wc.lpszClassName = "OLC_PIXEL_GAME_ENGINE"; +#endif + + RegisterClass(&wc); + + nWindowWidth = (LONG)nScreenWidth * (LONG)nPixelWidth; + nWindowHeight = (LONG)nScreenHeight * (LONG)nPixelHeight; + + // Define window furniture + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE;// | WS_THICKFRAME; + + int nCosmeticOffset = 30; + nViewW = nWindowWidth; + nViewH = nWindowHeight; + + // Handle Fullscreen + if (bFullScreen) + { + dwExStyle = 0; + dwStyle = WS_VISIBLE | WS_POPUP; + nCosmeticOffset = 0; + HMONITOR hmon = MonitorFromWindow(olc_hWnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi = { sizeof(mi) }; + if (!GetMonitorInfo(hmon, &mi)) return NULL; + nWindowWidth = mi.rcMonitor.right; + nWindowHeight = mi.rcMonitor.bottom; + + + } + + olc_UpdateViewport(); + + // Keep client size as requested + RECT rWndRect = { 0, 0, nWindowWidth, nWindowHeight }; + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + +#ifdef UNICODE + olc_hWnd = CreateWindowEx(dwExStyle, L"OLC_PIXEL_GAME_ENGINE", L"", dwStyle, + nCosmeticOffset, nCosmeticOffset, width, height, NULL, NULL, GetModuleHandle(nullptr), this); +#else + olc_hWnd = CreateWindowEx(dwExStyle, "OLC_PIXEL_GAME_ENGINE", "", dwStyle, + nCosmeticOffset, nCosmeticOffset, width, height, NULL, NULL, GetModuleHandle(nullptr), this); +#endif + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys[0x41] = Key::A; mapKeys[0x42] = Key::B; mapKeys[0x43] = Key::C; mapKeys[0x44] = Key::D; mapKeys[0x45] = Key::E; + mapKeys[0x46] = Key::F; mapKeys[0x47] = Key::G; mapKeys[0x48] = Key::H; mapKeys[0x49] = Key::I; mapKeys[0x4A] = Key::J; + mapKeys[0x4B] = Key::K; mapKeys[0x4C] = Key::L; mapKeys[0x4D] = Key::M; mapKeys[0x4E] = Key::N; mapKeys[0x4F] = Key::O; + mapKeys[0x50] = Key::P; mapKeys[0x51] = Key::Q; mapKeys[0x52] = Key::R; mapKeys[0x53] = Key::S; mapKeys[0x54] = Key::T; + mapKeys[0x55] = Key::U; mapKeys[0x56] = Key::V; mapKeys[0x57] = Key::W; mapKeys[0x58] = Key::X; mapKeys[0x59] = Key::Y; + mapKeys[0x5A] = Key::Z; + + mapKeys[VK_F1] = Key::F1; mapKeys[VK_F2] = Key::F2; mapKeys[VK_F3] = Key::F3; mapKeys[VK_F4] = Key::F4; + mapKeys[VK_F5] = Key::F5; mapKeys[VK_F6] = Key::F6; mapKeys[VK_F7] = Key::F7; mapKeys[VK_F8] = Key::F8; + mapKeys[VK_F9] = Key::F9; mapKeys[VK_F10] = Key::F10; mapKeys[VK_F11] = Key::F11; mapKeys[VK_F12] = Key::F12; + + mapKeys[VK_DOWN] = Key::DOWN; mapKeys[VK_LEFT] = Key::LEFT; mapKeys[VK_RIGHT] = Key::RIGHT; mapKeys[VK_UP] = Key::UP; + mapKeys[VK_RETURN] = Key::ENTER; //mapKeys[VK_RETURN] = Key::RETURN; + + mapKeys[VK_BACK] = Key::BACK; mapKeys[VK_ESCAPE] = Key::ESCAPE; mapKeys[VK_RETURN] = Key::ENTER; mapKeys[VK_PAUSE] = Key::PAUSE; + mapKeys[VK_SCROLL] = Key::SCROLL; mapKeys[VK_TAB] = Key::TAB; mapKeys[VK_DELETE] = Key::DEL; mapKeys[VK_HOME] = Key::HOME; + mapKeys[VK_END] = Key::END; mapKeys[VK_PRIOR] = Key::PGUP; mapKeys[VK_NEXT] = Key::PGDN; mapKeys[VK_INSERT] = Key::INS; + mapKeys[VK_SHIFT] = Key::SHIFT; mapKeys[VK_CONTROL] = Key::CTRL; + mapKeys[VK_SPACE] = Key::SPACE; + + mapKeys[0x30] = Key::K0; mapKeys[0x31] = Key::K1; mapKeys[0x32] = Key::K2; mapKeys[0x33] = Key::K3; mapKeys[0x34] = Key::K4; + mapKeys[0x35] = Key::K5; mapKeys[0x36] = Key::K6; mapKeys[0x37] = Key::K7; mapKeys[0x38] = Key::K8; mapKeys[0x39] = Key::K9; + + mapKeys[VK_NUMPAD0] = Key::NP0; mapKeys[VK_NUMPAD1] = Key::NP1; mapKeys[VK_NUMPAD2] = Key::NP2; mapKeys[VK_NUMPAD3] = Key::NP3; mapKeys[VK_NUMPAD4] = Key::NP4; + mapKeys[VK_NUMPAD5] = Key::NP5; mapKeys[VK_NUMPAD6] = Key::NP6; mapKeys[VK_NUMPAD7] = Key::NP7; mapKeys[VK_NUMPAD8] = Key::NP8; mapKeys[VK_NUMPAD9] = Key::NP9; + mapKeys[VK_MULTIPLY] = Key::NP_MUL; mapKeys[VK_ADD] = Key::NP_ADD; mapKeys[VK_DIVIDE] = Key::NP_DIV; mapKeys[VK_SUBTRACT] = Key::NP_SUB; mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; + + return olc_hWnd; + } + + bool PixelGameEngine::olc_OpenGLCreate() + { + // Create Device Context + glDeviceContext = GetDC(olc_hWnd); + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = 0; + if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) return false; + SetPixelFormat(glDeviceContext, pf, &pfd); + + if (!(glRenderContext = wglCreateContext(glDeviceContext))) return false; + wglMakeCurrent(glDeviceContext, glRenderContext); + + glViewport(nViewX, nViewY, nViewW, nViewH); + + // Remove Frame cap + wglSwapInterval = (wglSwapInterval_t*)wglGetProcAddress("wglSwapIntervalEXT"); + if (wglSwapInterval) wglSwapInterval(0); + return true; + } + + // Windows Event Handler + LRESULT CALLBACK PixelGameEngine::olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + static PixelGameEngine *sge; + switch (uMsg) + { + case WM_CREATE: sge = (PixelGameEngine*)((LPCREATESTRUCT)lParam)->lpCreateParams; return 0; + case WM_MOUSEMOVE: + { + uint16_t x = lParam & 0xFFFF; // Thanks @ForAbby (Discord) + uint16_t y = (lParam >> 16) & 0xFFFF; + int16_t ix = *(int16_t*)&x; + int16_t iy = *(int16_t*)&y; + sge->olc_UpdateMouse(ix, iy); + return 0; + } + case WM_SIZE: + { + sge->olc_UpdateWindowSize(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); + return 0; + } + case WM_MOUSEWHEEL: + { + sge->olc_UpdateMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); + return 0; + } + case WM_MOUSELEAVE: sge->bHasMouseFocus = false; return 0; + case WM_SETFOCUS: sge->bHasInputFocus = true; return 0; + case WM_KILLFOCUS: sge->bHasInputFocus = false; return 0; + case WM_KEYDOWN: sge->pKeyNewState[mapKeys[wParam]] = true; return 0; + case WM_KEYUP: sge->pKeyNewState[mapKeys[wParam]] = false; return 0; + case WM_LBUTTONDOWN:sge->pMouseNewState[0] = true; return 0; + case WM_LBUTTONUP: sge->pMouseNewState[0] = false; return 0; + case WM_RBUTTONDOWN:sge->pMouseNewState[1] = true; return 0; + case WM_RBUTTONUP: sge->pMouseNewState[1] = false; return 0; + case WM_MBUTTONDOWN:sge->pMouseNewState[2] = true; return 0; + case WM_MBUTTONUP: sge->pMouseNewState[2] = false; return 0; + case WM_CLOSE: bAtomActive = false; return 0; + case WM_DESTROY: PostQuitMessage(0); return 0; + } + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } +#else + // Do the Linux stuff! + Display* PixelGameEngine::olc_WindowCreate() + { + XInitThreads(); + + // Grab the deafult display and window + olc_Display = XOpenDisplay(NULL); + olc_WindowRoot = DefaultRootWindow(olc_Display); + + // Based on the display capabilities, configure the appearance of the window + GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; + olc_VisualInfo = glXChooseVisual(olc_Display, 0, olc_GLAttribs); + olc_ColourMap = XCreateColormap(olc_Display, olc_WindowRoot, olc_VisualInfo->visual, AllocNone); + olc_SetWindowAttribs.colormap = olc_ColourMap; + + // Register which events we are interested in receiving + olc_SetWindowAttribs.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask | StructureNotifyMask; + + // Create the window + olc_Window = XCreateWindow(olc_Display, olc_WindowRoot, 30, 30, nScreenWidth * nPixelWidth, nScreenHeight * nPixelHeight, 0, olc_VisualInfo->depth, InputOutput, olc_VisualInfo->visual, CWColormap | CWEventMask, &olc_SetWindowAttribs); + + Atom wmDelete = XInternAtom(olc_Display, "WM_DELETE_WINDOW", true); + XSetWMProtocols(olc_Display, olc_Window, &wmDelete, 1); + + XMapWindow(olc_Display, olc_Window); + XStoreName(olc_Display, olc_Window, "OneLoneCoder.com - Pixel Game Engine"); + + if (bFullScreen) // Thanks DragonEye, again :D + { + Atom wm_state; + Atom fullscreen; + wm_state = XInternAtom(olc_Display, "_NET_WM_STATE", False); + fullscreen = XInternAtom(olc_Display, "_NET_WM_STATE_FULLSCREEN", False); + XEvent xev{ 0 }; + xev.type = ClientMessage; + xev.xclient.window = olc_Window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = (bFullScreen ? 1 : 0); // the action (0: off, 1: on, 2: toggle) + xev.xclient.data.l[1] = fullscreen; // first property to alter + xev.xclient.data.l[2] = 0; // second property to alter + xev.xclient.data.l[3] = 0; // source indication + XMapWindow(olc_Display, olc_Window); + XSendEvent(olc_Display, DefaultRootWindow(olc_Display), False, + SubstructureRedirectMask | SubstructureNotifyMask, &xev); + XFlush(olc_Display); + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + nWindowWidth = gwa.width; + nWindowHeight = gwa.height; + olc_UpdateViewport(); + } + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys[0x61] = Key::A; mapKeys[0x62] = Key::B; mapKeys[0x63] = Key::C; mapKeys[0x64] = Key::D; mapKeys[0x65] = Key::E; + mapKeys[0x66] = Key::F; mapKeys[0x67] = Key::G; mapKeys[0x68] = Key::H; mapKeys[0x69] = Key::I; mapKeys[0x6A] = Key::J; + mapKeys[0x6B] = Key::K; mapKeys[0x6C] = Key::L; mapKeys[0x6D] = Key::M; mapKeys[0x6E] = Key::N; mapKeys[0x6F] = Key::O; + mapKeys[0x70] = Key::P; mapKeys[0x71] = Key::Q; mapKeys[0x72] = Key::R; mapKeys[0x73] = Key::S; mapKeys[0x74] = Key::T; + mapKeys[0x75] = Key::U; mapKeys[0x76] = Key::V; mapKeys[0x77] = Key::W; mapKeys[0x78] = Key::X; mapKeys[0x79] = Key::Y; + mapKeys[0x7A] = Key::Z; + + mapKeys[XK_F1] = Key::F1; mapKeys[XK_F2] = Key::F2; mapKeys[XK_F3] = Key::F3; mapKeys[XK_F4] = Key::F4; + mapKeys[XK_F5] = Key::F5; mapKeys[XK_F6] = Key::F6; mapKeys[XK_F7] = Key::F7; mapKeys[XK_F8] = Key::F8; + mapKeys[XK_F9] = Key::F9; mapKeys[XK_F10] = Key::F10; mapKeys[XK_F11] = Key::F11; mapKeys[XK_F12] = Key::F12; + + mapKeys[XK_Down] = Key::DOWN; mapKeys[XK_Left] = Key::LEFT; mapKeys[XK_Right] = Key::RIGHT; mapKeys[XK_Up] = Key::UP; + mapKeys[XK_KP_Enter] = Key::ENTER; mapKeys[XK_Return] = Key::ENTER; + + mapKeys[XK_BackSpace] = Key::BACK; mapKeys[XK_Escape] = Key::ESCAPE; mapKeys[XK_Linefeed] = Key::ENTER; mapKeys[XK_Pause] = Key::PAUSE; + mapKeys[XK_Scroll_Lock] = Key::SCROLL; mapKeys[XK_Tab] = Key::TAB; mapKeys[XK_Delete] = Key::DEL; mapKeys[XK_Home] = Key::HOME; + mapKeys[XK_End] = Key::END; mapKeys[XK_Page_Up] = Key::PGUP; mapKeys[XK_Page_Down] = Key::PGDN; mapKeys[XK_Insert] = Key::INS; + mapKeys[XK_Shift_L] = Key::SHIFT; mapKeys[XK_Shift_R] = Key::SHIFT; mapKeys[XK_Control_L] = Key::CTRL; mapKeys[XK_Control_R] = Key::CTRL; + mapKeys[XK_space] = Key::SPACE; + + mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; + mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; + + mapKeys[XK_KP_0] = Key::NP0; mapKeys[XK_KP_1] = Key::NP1; mapKeys[XK_KP_2] = Key::NP2; mapKeys[XK_KP_3] = Key::NP3; mapKeys[XK_KP_4] = Key::NP4; + mapKeys[XK_KP_5] = Key::NP5; mapKeys[XK_KP_6] = Key::NP6; mapKeys[XK_KP_7] = Key::NP7; mapKeys[XK_KP_8] = Key::NP8; mapKeys[XK_KP_9] = Key::NP9; + mapKeys[XK_KP_Multiply] = Key::NP_MUL; mapKeys[XK_KP_Add] = Key::NP_ADD; mapKeys[XK_KP_Divide] = Key::NP_DIV; mapKeys[XK_KP_Subtract] = Key::NP_SUB; mapKeys[XK_KP_Decimal] = Key::NP_DECIMAL; + + return olc_Display; + } + + bool PixelGameEngine::olc_OpenGLCreate() + { + glDeviceContext = glXCreateContext(olc_Display, olc_VisualInfo, nullptr, GL_TRUE); + glXMakeCurrent(olc_Display, olc_Window, glDeviceContext); + + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + + glSwapIntervalEXT = nullptr; + glSwapIntervalEXT = (glSwapInterval_t*)glXGetProcAddress((unsigned char*)"glXSwapIntervalEXT"); + if (glSwapIntervalEXT) + glSwapIntervalEXT(olc_Display, olc_Window, 0); + else + { + printf("NOTE: Could not disable VSYNC, glXSwapIntervalEXT() was not found!\n"); + printf(" Don't worry though, things will still work, it's just the\n"); + printf(" frame rate will be capped to your monitors refresh rate - javidx9\n"); + } + + return true; + } + +#endif + + // Need a couple of statics as these are singleton instances + // read from multiple locations + std::atomic PixelGameEngine::bAtomActive{ false }; + std::map PixelGameEngine::mapKeys; + olc::PixelGameEngine* olc::PGEX::pge = nullptr; +#ifdef OLC_DBG_OVERDRAW + int olc::Sprite::nOverdrawCount = 0; +#endif + //============================================================= +} + +#endif