diff --git a/TestLevel.tmx.0 b/TestLevel.tmx.0 new file mode 100644 index 0000000..1ba79a4 --- /dev/null +++ b/TestLevel.tmx.0 @@ -0,0 +1 @@ +97 384 0 97 400 0 97 416 0 97 432 0 97 448 0 102 464 0 112 473 0 115 480 0 115 496 0 115 512 0 111 526 0 110 528 0 95 543 0 95 544 0 89 560 0 90 576 0 96 587 0 101 592 0 109 608 0 109 624 0 109 640 0 109 656 0 109 672 0 109 688 0 109 691 1 112 701 0 114 704 0 128 718 0 130 720 0 144 733 0 146 736 0 160 750 0 163 752 0 176 754 0 192 753 0 194 751 0 208 738 0 211 735 0 224 723 0 227 719 0 240 704 0 240 703 0 243 687 0 243 671 0 243 655 0 243 639 0 243 623 0 243 607 0 243 591 0 243 575 0 243 559 0 243 543 0 243 527 0 243 511 0 247 495 0 256 484 0 261 479 0 272 468 0 277 463 0 288 454 0 304 451 0 308 448 1 309 447 0 312 431 0 303 421 0 298 415 0 287 405 0 283 400 1 282 399 0 271 388 0 265 383 0 255 372 0 250 367 0 239 356 0 233 351 0 223 340 0 218 335 0 207 328 0 190 327 0 174 327 0 159 327 0 144 319 0 143 318 0 128 303 0 127 302 0 112 287 0 111 287 0 95 281 0 79 281 0 63 281 0 47 278 0 40 271 0 31 261 0 29 255 0 28 239 0 28 223 0 28 206 0 32 198 0 37 191 0 48 182 0 64 177 0 80 178 0 96 188 0 99 192 0 112 200 0 128 201 0 144 201 0 161 201 0 176 201 0 192 201 0 209 201 0 224 201 0 \ No newline at end of file diff --git a/TestLevel.tmx.1 b/TestLevel.tmx.1 new file mode 100644 index 0000000..1ba79a4 --- /dev/null +++ b/TestLevel.tmx.1 @@ -0,0 +1 @@ +97 384 0 97 400 0 97 416 0 97 432 0 97 448 0 102 464 0 112 473 0 115 480 0 115 496 0 115 512 0 111 526 0 110 528 0 95 543 0 95 544 0 89 560 0 90 576 0 96 587 0 101 592 0 109 608 0 109 624 0 109 640 0 109 656 0 109 672 0 109 688 0 109 691 1 112 701 0 114 704 0 128 718 0 130 720 0 144 733 0 146 736 0 160 750 0 163 752 0 176 754 0 192 753 0 194 751 0 208 738 0 211 735 0 224 723 0 227 719 0 240 704 0 240 703 0 243 687 0 243 671 0 243 655 0 243 639 0 243 623 0 243 607 0 243 591 0 243 575 0 243 559 0 243 543 0 243 527 0 243 511 0 247 495 0 256 484 0 261 479 0 272 468 0 277 463 0 288 454 0 304 451 0 308 448 1 309 447 0 312 431 0 303 421 0 298 415 0 287 405 0 283 400 1 282 399 0 271 388 0 265 383 0 255 372 0 250 367 0 239 356 0 233 351 0 223 340 0 218 335 0 207 328 0 190 327 0 174 327 0 159 327 0 144 319 0 143 318 0 128 303 0 127 302 0 112 287 0 111 287 0 95 281 0 79 281 0 63 281 0 47 278 0 40 271 0 31 261 0 29 255 0 28 239 0 28 223 0 28 206 0 32 198 0 37 191 0 48 182 0 64 177 0 80 178 0 96 188 0 99 192 0 112 200 0 128 201 0 144 201 0 161 201 0 176 201 0 192 201 0 209 201 0 224 201 0 \ No newline at end of file diff --git a/TestLevel.tmx.2 b/TestLevel.tmx.2 new file mode 100644 index 0000000..1ba79a4 --- /dev/null +++ b/TestLevel.tmx.2 @@ -0,0 +1 @@ +97 384 0 97 400 0 97 416 0 97 432 0 97 448 0 102 464 0 112 473 0 115 480 0 115 496 0 115 512 0 111 526 0 110 528 0 95 543 0 95 544 0 89 560 0 90 576 0 96 587 0 101 592 0 109 608 0 109 624 0 109 640 0 109 656 0 109 672 0 109 688 0 109 691 1 112 701 0 114 704 0 128 718 0 130 720 0 144 733 0 146 736 0 160 750 0 163 752 0 176 754 0 192 753 0 194 751 0 208 738 0 211 735 0 224 723 0 227 719 0 240 704 0 240 703 0 243 687 0 243 671 0 243 655 0 243 639 0 243 623 0 243 607 0 243 591 0 243 575 0 243 559 0 243 543 0 243 527 0 243 511 0 247 495 0 256 484 0 261 479 0 272 468 0 277 463 0 288 454 0 304 451 0 308 448 1 309 447 0 312 431 0 303 421 0 298 415 0 287 405 0 283 400 1 282 399 0 271 388 0 265 383 0 255 372 0 250 367 0 239 356 0 233 351 0 223 340 0 218 335 0 207 328 0 190 327 0 174 327 0 159 327 0 144 319 0 143 318 0 128 303 0 127 302 0 112 287 0 111 287 0 95 281 0 79 281 0 63 281 0 47 278 0 40 271 0 31 261 0 29 255 0 28 239 0 28 223 0 28 206 0 32 198 0 37 191 0 48 182 0 64 177 0 80 178 0 96 188 0 99 192 0 112 200 0 128 201 0 144 201 0 161 201 0 176 201 0 192 201 0 209 201 0 224 201 0 \ No newline at end of file diff --git a/src/Hamster.cpp b/src/Hamster.cpp index 4b5f7eb..ceffa1a 100644 --- a/src/Hamster.cpp +++ b/src/Hamster.cpp @@ -42,6 +42,7 @@ All rights reserved. #include #include "AnimationState.h" #include "FloatingText.h" +#include "HamsterAI.h" std::vectorHamster::HAMSTER_LIST; const uint8_t Hamster::MAX_HAMSTER_COUNT{100U}; @@ -71,14 +72,6 @@ void Hamster::UpdateHamsters(const float fElapsedTime){ if(h.IsPlayerControlled){ h.HandlePlayerControls(); }else{ - if(!h.hamsterJet.has_value()){ - h.ObtainPowerup(Powerup::JET); - Powerup tempJetPowerup{{},Powerup::JET}; - tempJetPowerup.OnPowerupObtain(h); - h.SetState(FLYING); - h.lastSafeLocation.reset(); - h.hamsterJet.emplace(h); - } //TODO: NPC controls. } } @@ -212,7 +205,8 @@ void Hamster::LoadHamsters(const vf2d startingLoc){ if(NPC_HAMSTER_COUNT+1>MAX_HAMSTER_COUNT)throw std::runtime_error{std::format("WARNING! Max hamster count is too high! Please expand the MAX_HAMSTER_COUNT if you want more hamsters. Requested {} hamsters.",MAX_HAMSTER_COUNT)}; playerHamster=&HAMSTER_LIST.emplace_back(startingLoc,PLAYER_HAMSTER_IMAGE,PLAYER_CONTROLLED); for(int i:std::ranges::iota_view(0U,NPC_HAMSTER_COUNT)){ - HAMSTER_LIST.emplace_back(startingLoc,NPC_HAMSTER_IMAGES.at(util::random()%NPC_HAMSTER_IMAGES.size()),NPC); + Hamster&npcHamster{HAMSTER_LIST.emplace_back(startingLoc,NPC_HAMSTER_IMAGES.at(util::random()%NPC_HAMSTER_IMAGES.size()),NPC)}; + npcHamster.ai.LoadAI(HamsterGame::Game().GetCurrentMapName(),HamsterAI::AIType(util::random()%int(HamsterAI::AIType::END))); } } @@ -335,6 +329,7 @@ void Hamster::HandlePlayerControls(){ if(lastTappedSpace<=0.6f&&HasPowerup(Powerup::JET)){ SetState(FLYING); lastSafeLocation.reset(); + if(IsPlayerControlled)HamsterAI::OnJetLaunch(this->pos); hamsterJet.emplace(*this); } lastTappedSpace=0.f; @@ -399,6 +394,7 @@ void Hamster::HandleCollision(){ (!HasPowerup(powerup.GetType())||HasPowerup(Powerup::JET)&&powerup.GetType()==Powerup::JET&&jetFuel!=1.f) &&geom2d::overlaps(geom2d::circle(GetPos(),collisionRadius),geom2d::circle(powerup.GetPos(),20.f))){ ObtainPowerup(powerup.GetType()); + if(IsPlayerControlled)HamsterAI::OnPowerupCollection(this->pos); powerup.OnPowerupObtain(*this); } } @@ -406,6 +402,7 @@ void Hamster::HandleCollision(){ if(z<=0.1f&&geom2d::overlaps(geom2d::rect(checkpoint.GetPos()-vf2d{62,60},{122.f,113.f}),geom2d::circle(GetPos(),collisionRadius))&&!checkpointsCollected.count(checkpoint.GetPos())){ checkpointsCollected.insert(checkpoint.GetPos()); FloatingText::CreateFloatingText(pos,std::format("{} / {}",checkpointsCollected.size(),Checkpoint::GetCheckpoints().size()),{WHITE,GREEN},{1.5f,2.f}); + if(IsPlayerControlled)HamsterAI::OnCheckpointCollected(this->pos); checkpoint.OnCheckpointCollect(); } } @@ -416,6 +413,7 @@ void Hamster::HandleCollision(){ burrowTimer=1.f; enteredTunnel=id; burrowImgShrinkTimer=0.5f; + if(IsPlayerControlled)HamsterAI::OnTunnelEnter(this->pos); } } } @@ -539,12 +537,20 @@ const float&Hamster::GetZ()const{ void Hamster::SetPos(const vf2d pos){ bool movedY{false}; + bool movedX{false}; if(state==FLYING&&HamsterGame::Game().IsInBounds(vf2d{this->pos.x,pos.y})||!HamsterGame::Game().IsTerrainSolid(vf2d{this->pos.x,pos.y})){ this->pos=vf2d{this->pos.x,pos.y}; movedY=true; } - if(state==FLYING&&HamsterGame::Game().IsInBounds(vf2d{pos.x,this->pos.y})||!HamsterGame::Game().IsTerrainSolid(vf2d{pos.x,this->pos.y}))this->pos=vf2d{pos.x,this->pos.y}; - if (!movedY&&(state==FLYING&&HamsterGame::Game().IsInBounds(vf2d{this->pos.x,pos.y})||!HamsterGame::Game().IsTerrainSolid(vf2d{this->pos.x,pos.y})))this->pos=vf2d{this->pos.x,pos.y}; + if(state==FLYING&&HamsterGame::Game().IsInBounds(vf2d{pos.x,this->pos.y})||!HamsterGame::Game().IsTerrainSolid(vf2d{pos.x,this->pos.y})){ + this->pos=vf2d{pos.x,this->pos.y}; + movedX=true; + } + if (!movedY&&(state==FLYING&&HamsterGame::Game().IsInBounds(vf2d{this->pos.x,pos.y})||!HamsterGame::Game().IsTerrainSolid(vf2d{this->pos.x,pos.y}))){ + this->pos=vf2d{this->pos.x,pos.y}; + movedY=true; + } + if(IsPlayerControlled&&(movedX||movedY))HamsterAI::OnMove(this->pos); } void Hamster::SetZ(const float z){ diff --git a/src/Hamster.h b/src/Hamster.h index 8f8adfd..75030dd 100644 --- a/src/Hamster.h +++ b/src/Hamster.h @@ -45,6 +45,7 @@ All rights reserved. #include "HamsterJet.h" #include "HamsterGame.h" #include "Checkpoint.h" +#include "HamsterAI.h" using Timer=float; @@ -120,6 +121,7 @@ class Hamster{ float burrowTimer{}; float burrowImgShrinkTimer{}; uint16_t enteredTunnel{}; + HamsterAI ai; public: Hamster(const vf2d spawnPos,const std::string&img,const PlayerControlled IsPlayerControlled=NPC); static const Hamster&GetPlayer(); diff --git a/src/HamsterAI.cpp b/src/HamsterAI.cpp new file mode 100644 index 0000000..1a269c8 --- /dev/null +++ b/src/HamsterAI.cpp @@ -0,0 +1,133 @@ +#pragma region License +/* +License (OLC-3) +~~~~~~~~~~~~~~~ + +Copyright 2024 Joshua Sigona + +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. + +Portions of this software are copyright © 2024 The FreeType +Project (www.freetype.org). Please see LICENSE_FT.txt for more information. +All rights reserved. +*/ +#pragma endregion +#include "HamsterAI.h" +#include "HamsterGame.h" +#include + +bool HamsterAI::recordingMode{false}; +std::optionalHamsterAI::lastTileWalkedOn; +std::vectorHamsterAI::recordedActions; + +void HamsterAI::Update(const float fElapsedTime){ + if(HamsterGame::Game().GetKey(HOME).bPressed){ + if(!recordingMode){ + recordingMode=true; + recordedActions.clear(); + }else{ + HamsterGame::Game().TextEntryEnable(true,"0"); + } + } +} + +void HamsterAI::DrawOverlay(){ + if(recordingMode){ + if(HamsterGame::Game().IsTextEntryEnabled()){ + std::string displayStr{std::format("{}\n{}","0=SMART 1=NORMAL 2=DUMB",HamsterGame::Game().TextEntryGetString())}; + for(int y:std::ranges::iota_view(-1,2)){ + for(int x:std::ranges::iota_view(-1,2)){ + HamsterGame::Game().DrawStringDecal({2.f+x,2.f+y},displayStr,BLACK); + } + } + HamsterGame::Game().DrawStringDecal({2.f,2.f},displayStr,YELLOW); + }else if(fmod(HamsterGame::Game().GetRuntime(),1.f)<0.5f){ + for(int y:std::ranges::iota_view(-1,2)){ + for(int x:std::ranges::iota_view(-1,2)){ + HamsterGame::Game().DrawStringDecal({2.f+x,2.f+y},"RECORDING",BLACK); + } + } + HamsterGame::Game().DrawStringDecal({2.f,2.f},"RECORDING",RED); + } + } +} +void HamsterAI::OnTextEntryComplete(const std::string&enteredText){ + if(recordingMode){ + std::ofstream file{std::format("{}.{}",HamsterGame::Game().GetCurrentMapName(),stoi(enteredText))}; + for(const Action&action:recordedActions){ + file<>newAction.pos.x; + file>>newAction.pos.y; + int typeNum; + file>>typeNum; + newAction.type=HamsterAI::Action::ActionType(typeNum); + actionsToPerform.emplace_back(newAction); + } + file.close(); +} \ No newline at end of file diff --git a/src/HamsterAI.h b/src/HamsterAI.h new file mode 100644 index 0000000..a503a5b --- /dev/null +++ b/src/HamsterAI.h @@ -0,0 +1,83 @@ +#pragma region License +/* +License (OLC-3) +~~~~~~~~~~~~~~~ + +Copyright 2024 Joshua Sigona + +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. + +Portions of this software are copyright © 2024 The FreeType +Project (www.freetype.org). Please see LICENSE_FT.txt for more information. +All rights reserved. +*/ +#pragma endregion +#pragma once +#include +#include "olcUTIL_Geometry2D.h" + +class HamsterAI{ +public: + class Action{ + public: + enum ActionType{ + MOVE, + COLLECT_POWERUP, + LAUNCH_JET, + ENTER_TUNNEL, + CHECKPOINT_COLLECTED, + }; + vi2d pos; + ActionType type; + }; + enum AIType{ + SMART, + NORMAL, + DUMB, + END, //NOTE: Not at AI type, just used for iteration detection. + }; + using ActionOptRef=std::optional>; + const ActionOptRef GetCurrentAction(); + const ActionOptRef AdvanceToNextAction(); + void LoadAI(const std::string&mapName,AIType type); + + static void OnMove(const vi2d pos); + static void OnPowerupCollection(const vi2d pos); + static void OnJetLaunch(const vi2d pos); + static void OnTunnelEnter(const vi2d pos); + static void OnCheckpointCollected(const vi2d pos); + + static void OnTextEntryComplete(const std::string&enteredText); + + static void Update(const float fElapsedTime); + static void DrawOverlay(); +private: + static bool recordingMode; + static std::vectorrecordedActions; + std::vectoractionsToPerform; + size_t actionInd{}; + static std::optionallastTileWalkedOn; //In World Coords +}; \ No newline at end of file diff --git a/src/HamsterGame.cpp b/src/HamsterGame.cpp index a798871..b8a0d2a 100644 --- a/src/HamsterGame.cpp +++ b/src/HamsterGame.cpp @@ -5,6 +5,7 @@ #include "util.h" #include "Checkpoint.h" #include "FloatingText.h" +#include "HamsterAI.h" geom2d::rectHamsterGame::SCREEN_FRAME{{96,0},{320,288}}; std::unordered_map>HamsterGame::ANIMATIONS; @@ -120,6 +121,7 @@ void HamsterGame::LoadLevel(const std::string&mapName){ const vf2d levelSpawnLoc{50,50}; //TEMPORARY currentMap=TMXParser{ASSETS_DIR+mapName}; + currentMapName=mapName; cloudSpd.x=util::random_range(-12.f,12.f); cloudSpd.y=util::random_range(-0.3f,0.3f); cloudOffset.x=util::random(); @@ -181,6 +183,7 @@ void HamsterGame::UpdateGame(const float fElapsedTime){ camera.SetLazyFollowRate(4.f*Hamster::GetPlayer().GetMaxSpeed()/128.f); tv.SetWorldOffset(tv.ScaleToWorld(-SCREEN_FRAME.pos)+camera.GetViewPosition()); Hamster::UpdateHamsters(fElapsedTime); + HamsterAI::Update(fElapsedTime); Powerup::UpdatePowerups(fElapsedTime); Checkpoint::UpdateCheckpoints(fElapsedTime); FloatingText::UpdateFloatingText(fElapsedTime); @@ -209,6 +212,7 @@ void HamsterGame::DrawGame(){ FloatingText::DrawFloatingText(tv); border.Draw(); Hamster::DrawOverlay(); + HamsterAI::DrawOverlay(); #pragma region Powerup Display for(int y:std::ranges::iota_view(0,4)){ for(int x:std::ranges::iota_view(0,2)){ @@ -528,6 +532,14 @@ const Terrain::Direction&HamsterGame::GetTileFacingDirection(const vf2d worldPos return tileFacingDir; } +const std::string&HamsterGame::GetCurrentMapName()const{ + return currentMapName; +} + +void HamsterGame::OnTextEntryComplete(const std::string& sText){ + HamsterAI::OnTextEntryComplete(sText); +} + int main() { HamsterGame game("Project Hamster"); diff --git a/src/HamsterGame.h b/src/HamsterGame.h index aa1afdd..4a9d2dc 100644 --- a/src/HamsterGame.h +++ b/src/HamsterGame.h @@ -81,6 +81,8 @@ public: const float GetCameraZ()const; const std::unordered_map&GetTunnels()const; const Terrain::Direction&GetTileFacingDirection(const vf2d worldPos)const; + const std::string&GetCurrentMapName()const; + virtual void OnTextEntryComplete(const std::string& sText)override; private: void UpdateGame(const float fElapsedTime); void DrawGame(); @@ -114,4 +116,5 @@ private: void DrawRadar(); float radarScale{48.f}; std::vectorwaterTiles; + std::string currentMapName; }; \ No newline at end of file diff --git a/src/TODO.txt b/src/TODO.txt index f485a7a..6115882 100644 --- a/src/TODO.txt +++ b/src/TODO.txt @@ -79,6 +79,15 @@ AI will move towards those nodes as best they can. If they get stuck for some am Record each collection of a powerup/launch moment as these are important nodes for AI to be on. (Maybe "Cheat" the AI a little if thier stuck node counte i really high by teleporting them when they are off-screen) +Important Events: + +Powerup Collection +Jet Launch +Tunnel Enter +New Tile Traversal + +Be careful about changing maps as AI must then be re-recorded!!! + LORE ===================