diff --git a/assets/hamster.xcf b/assets/hamster.xcf index fe7aa13..a0bf016 100644 Binary files a/assets/hamster.xcf and b/assets/hamster.xcf differ diff --git a/assets/hamster1.png b/assets/hamster1.png index 257a715..45de653 100644 Binary files a/assets/hamster1.png and b/assets/hamster1.png differ diff --git a/assets/hamster2.png b/assets/hamster2.png index 78e3a2e..da9787e 100644 Binary files a/assets/hamster2.png and b/assets/hamster2.png differ diff --git a/assets/hamster3.png b/assets/hamster3.png index b905ab2..03abe22 100644 Binary files a/assets/hamster3.png and b/assets/hamster3.png differ diff --git a/assets/hamster4.png b/assets/hamster4.png index 9664abf..4dead94 100644 Binary files a/assets/hamster4.png and b/assets/hamster4.png differ diff --git a/assets/hamster5.png b/assets/hamster5.png index e245c71..8e7e132 100644 Binary files a/assets/hamster5.png and b/assets/hamster5.png differ diff --git a/assets/hamster6.png b/assets/hamster6.png index 1b759ba..9e50c34 100644 Binary files a/assets/hamster6.png and b/assets/hamster6.png differ diff --git a/assets/hamster7.png b/assets/hamster7.png index db42a86..7fc123d 100644 Binary files a/assets/hamster7.png and b/assets/hamster7.png differ diff --git a/assets/hamster8.png b/assets/hamster8.png index f7acb25..66dae03 100644 Binary files a/assets/hamster8.png and b/assets/hamster8.png differ diff --git a/assets/raceprogress.png b/assets/raceprogress.png new file mode 100644 index 0000000..4154437 Binary files /dev/null and b/assets/raceprogress.png differ diff --git a/src/Checkpoint.cpp b/src/Checkpoint.cpp index ef61dc5..27d29b3 100644 --- a/src/Checkpoint.cpp +++ b/src/Checkpoint.cpp @@ -86,7 +86,10 @@ void Checkpoint::DrawCheckpoints(TransformedView&tv){ if(screenDistance>226){ const vf2d dirVec{playerToCheckpointLine.vector().norm()}; const float dir{dirVec.polar().y}; - std::optionalprojCircle{geom2d::project(geom2d::circle({},16),HamsterGame::SCREEN_FRAME,geom2d::ray(HamsterGame::SCREEN_FRAME.middle(),dirVec))}; + geom2d::rectscreenBounds{HamsterGame::SCREEN_FRAME}; + screenBounds.pos.x+=16.f; + screenBounds.size.x-=16.f; + std::optionalprojCircle{geom2d::project(geom2d::circle({},16),screenBounds,geom2d::ray(HamsterGame::SCREEN_FRAME.middle(),dirVec))}; if(projCircle.has_value()){ Pixel arrowCol{PixelLerp(GREEN,BLACK,std::clamp((screenDistance-226)/1000.f,0.f,1.f))}; uint8_t iconAlpha{uint8_t(util::lerp(255.f,0.f,std::clamp((screenDistance-226)/1000.f,0.f,1.f)))}; diff --git a/src/Hamster.cpp b/src/Hamster.cpp index 5fd5471..d5f78ae 100644 --- a/src/Hamster.cpp +++ b/src/Hamster.cpp @@ -422,8 +422,8 @@ void Hamster::DrawOverlay(){ if(GetPlayer().HasPowerup(Powerup::JET))HamsterGame::Game().DrawDecal(jetDisplayOffset+vf2d{22.f,137.f},HamsterGame::GetGFX("fuelbar_outline.png").Decal(),{1.f,1.f},GetPlayer().jetFuel<=0.2f?(fmod(GetPlayer().readyFlashTimer,1.f)<=0.5f?RED:BLACK):BLACK); if(GetPlayer().HasPowerup(Powerup::WHEEL)){ for(int i:std::ranges::iota_view(0,3)){ - if(fmod(HamsterGame::Game().GetRuntime(),2.f)<1.f&&GetPlayer().boostCounter>i)HamsterGame::Game().DrawDecal(HamsterGame::SCREEN_FRAME.pos+vf2d{i*16.f+4.f,HamsterGame::SCREEN_FRAME.size.y-20.f},HamsterGame::GetGFX("boost_outline.png").Decal(),{0.125f,0.125f},GetPlayer().boostCounter>i?WHITE:BLACK); - else HamsterGame::Game().DrawDecal(HamsterGame::SCREEN_FRAME.pos+vf2d{i*16.f+4.f,HamsterGame::SCREEN_FRAME.size.y-20.f},HamsterGame::GetGFX("boost.png").Decal(),{0.125f,0.125f},GetPlayer().boostCounter>i?WHITE:BLACK); + if(fmod(HamsterGame::Game().GetRuntime(),2.f)<1.f&&GetPlayer().boostCounter>i)HamsterGame::Game().DrawDecal(HamsterGame::SCREEN_FRAME.pos+vf2d{i*16.f+4.f,HamsterGame::SCREEN_FRAME.size.y-18.f},HamsterGame::GetGFX("boost_outline.png").Decal(),{0.125f,0.125f},GetPlayer().boostCounter>i?WHITE:BLACK); + else HamsterGame::Game().DrawDecal(HamsterGame::SCREEN_FRAME.pos+vf2d{i*16.f+4.f,HamsterGame::SCREEN_FRAME.size.y-18.f},HamsterGame::GetGFX("boost.png").Decal(),{0.125f,0.125f},GetPlayer().boostCounter>i?WHITE:BLACK); } for(int y:std::ranges::iota_view(-1,2)){ for(int x:std::ranges::iota_view(-1,2)){ @@ -561,6 +561,7 @@ void Hamster::HandleCollision(){ if(IsPlayerControlled)HamsterAI::OnCheckpointCollected(this->pos); if(IsPlayerControlled)checkpoint.OnCheckpointCollect(); if(CollectedAllCheckpoints()){finishedRaceTime=HamsterGame::Game().GetRaceTime();} + lastObtainedCheckpointPos=checkpoint.GetPos(); } } if(GetState()==NORMAL){ @@ -842,7 +843,7 @@ const bool Hamster::CollectedAllCheckpoints()const{ const bool Hamster::HasCollectedCheckpoint(const Checkpoint&cp)const{ return checkpointsCollected.contains(cp.GetPos()); } -const std::vector&Hamster::GetHamsters(){ +std::vector&Hamster::GetHamsters(){ return HAMSTER_LIST; } const Hamster::HamsterState&Hamster::GetState()const{ @@ -1029,4 +1030,12 @@ const vf2d Hamster::GetAINodePositionVariance()const{ }break; } return finalOffset; +} + +const size_t Hamster::GetCheckpointsCollectedCount()const{ + return checkpointsCollected.size(); +} + +const std::optionalHamster::GetLastCollectedCheckpoint()const{ + return lastObtainedCheckpointPos; } \ No newline at end of file diff --git a/src/Hamster.h b/src/Hamster.h index 030a007..59265bc 100644 --- a/src/Hamster.h +++ b/src/Hamster.h @@ -104,7 +104,6 @@ class Hamster{ std::string img; Animate2D::Animationanimations; Animate2D::AnimationState internalAnimState; - PlayerControlled IsPlayerControlled; static std::optionalplayerHamster; HamsterState state{NORMAL}; std::unordered_setpowerups; @@ -132,6 +131,7 @@ class Hamster{ std::string colorFilename; int points{}; std::optionalfinishedRaceTime; + std::optionallastObtainedCheckpointPos; HamsterAI::AIType aiLevel{HamsterAI::AIType::NORMAL}; public: Hamster(const vf2d spawnPos,const std::string&img,const PlayerControlled IsPlayerControlled=NPC); @@ -178,7 +178,7 @@ public: void SetState(const HamsterState state); const bool CollectedAllCheckpoints()const; const bool HasCollectedCheckpoint(const Checkpoint&cp)const; - static const std::vector&GetHamsters(); + static std::vector&GetHamsters(); const HamsterState&GetState()const; const bool BurnedOrDrowned()const; const bool CanMove()const; @@ -188,4 +188,7 @@ public: const float GetAINodeDistanceVariance()const; const vf2d GetAINodePositionVariance()const; const bool IsBurrowed()const; + const size_t GetCheckpointsCollectedCount()const; + const std::optionalGetLastCollectedCheckpoint()const; + PlayerControlled IsPlayerControlled; }; \ No newline at end of file diff --git a/src/HamsterGame.cpp b/src/HamsterGame.cpp index 024a96d..0279471 100644 --- a/src/HamsterGame.cpp +++ b/src/HamsterGame.cpp @@ -101,6 +101,7 @@ void HamsterGame::LoadGraphics(){ _LoadImage("background3.png"); _LoadImage("background4.png"); _LoadImage("background5.png"); + _LoadImage("raceprogress.png"); } void HamsterGame::LoadAnimations(){ @@ -203,7 +204,13 @@ void HamsterGame::LoadLevel(const std::string&mapName){ } void HamsterGame::UpdateGame(const float fElapsedTime){ - countdownTimer=std::max(0.f,countdownTimer-fElapsedTime); + if(countdownTimer>0.f){ + countdownTimer-=fElapsedTime; + if(countdownTimer<=0.f){ + countdownTimer=0.f; + leaderboard.OnRaceStart(); + } + } vEye.z+=(Hamster::GetPlayer().GetZ()+zoom-vEye.z)*fLazyFollowRate*fElapsedTime; speedometerDisplayAmt+=(Hamster::GetPlayer().GetSpeed()-speedometerDisplayAmt)*fLazyFollowRate*fElapsedTime; @@ -222,6 +229,7 @@ void HamsterGame::UpdateGame(const float fElapsedTime){ Powerup::UpdatePowerups(fElapsedTime); Checkpoint::UpdateCheckpoints(fElapsedTime); FloatingText::UpdateFloatingText(fElapsedTime); + leaderboard.Update(); border.Update(fElapsedTime); } @@ -310,6 +318,7 @@ void HamsterGame::DrawGame(){ DrawStringDecal(SCREEN_FRAME.pos+SCREEN_FRAME.size-speedometerStrSize-vf2d{4.f,4.f},speedometerStr,speedometerCol); DrawDecal({2.f,4.f},GetGFX("radar.png").Decal()); DrawRadar(); + leaderboard.Draw(*this); DrawStringDecal({0,8.f},std::to_string(GetFPS())); if(countdownTimer>0.f){ Pixel timerColor{fmod(countdownTimer,1.f)<0.5f?GREEN:WHITE}; @@ -677,6 +686,10 @@ const bool HamsterGame::RaceCountdownCompleted(){ return countdownTimer==0.f; } +const geom2d::rectHamsterGame::GetMapSpawnRect()const{ + return currentMap.value().GetData().GetSpawnZone(); +} + int main() { HamsterGame game("Project Hamster"); diff --git a/src/HamsterGame.h b/src/HamsterGame.h index cc8076c..39eda15 100644 --- a/src/HamsterGame.h +++ b/src/HamsterGame.h @@ -52,6 +52,7 @@ All rights reserved. #include "olcPGEX_MiniAudio.h" #include "HamsterNet.h" #include "olcPGEX_SplashScreen.h" +#include "HamsterLeaderboard.h" struct Letter{ vf2d pos; @@ -103,6 +104,7 @@ public: static void LoadPBs(); const int GetRaceTime(); const bool RaceCountdownCompleted(); + const geom2d::rectGetMapSpawnRect()const; private: void UpdateGame(const float fElapsedTime); void DrawGame(); @@ -177,4 +179,5 @@ private: "Red", "Blue", }; + HamsterLeaderboard leaderboard; }; \ No newline at end of file diff --git a/src/HamsterLeaderboard.cpp b/src/HamsterLeaderboard.cpp new file mode 100644 index 0000000..d10d12f --- /dev/null +++ b/src/HamsterLeaderboard.cpp @@ -0,0 +1,114 @@ +#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 "HamsterLeaderboard.h" +#include "Checkpoint.h" +#include "Hamster.h" +#include + +void HamsterLeaderboard::OnRaceStart(){ + hamsterRanking.clear(); + for(Hamster&hamster:Hamster::GetHamsters())hamsterRanking.emplace_back(hamster); +} + +HamsterLeaderboard::HamsterRanking::HamsterRanking(Hamster&hamsterRef) +:hamster(hamsterRef){} + +void HamsterLeaderboard::Update(){ + for(HamsterRanking&hamsterRank:hamsterRanking){ + Hamster&hamster{hamsterRank.hamster.get()}; + float finalRanking{float(hamster.GetCheckpointsCollectedCount())}; + std::optional>closestCheckpoint; + for(Checkpoint&cp:Checkpoint::GetCheckpoints()){ + float distToHamster{geom2d::line(hamster.GetPos(),cp.GetPos()).length()}; + if(hamster.HasCollectedCheckpoint(cp))continue; + if(!closestCheckpoint.has_value())closestCheckpoint=cp; + else if(distToHamster(hamster.GetPos(),closestCheckpoint.value().get().GetPos()).length())closestCheckpoint=cp; + } + if(closestCheckpoint.has_value()){ + vf2d lastCollectedPos{}; + if(hamster.GetLastCollectedCheckpoint().has_value())lastCollectedPos=hamster.GetLastCollectedCheckpoint().value(); + else lastCollectedPos=HamsterGame::Game().GetMapSpawnRect().middle(); + vf2d closestCheckpointPos{closestCheckpoint.value().get().GetPos()}; + float totalDist{geom2d::line(lastCollectedPos,closestCheckpointPos).length()}; + float distFromHamsterToClosestCheckpoint{geom2d::line(hamster.GetPos(),closestCheckpointPos).length()}; + float additionalRanking{std::clamp((totalDist-distFromHamsterToClosestCheckpoint)/totalDist,0.0001f,0.9999f)}; + finalRanking+=additionalRanking; + } + hamsterRank.ranking=std::min(finalRanking,float(Checkpoint::GetCheckpoints().size())); + } + std::sort(hamsterRanking.begin(),hamsterRanking.end(),[](const HamsterRanking&rank1,const HamsterRanking&rank2){return rank1.rankingSize()/2,{1.f,1.f},WHITE); + vf2d progressBarBottomPos{HamsterGame::SCREEN_FRAME.pos+vf2d{8.f,HamsterGame::SCREEN_FRAME.size.y/2.f+game.GetGFX("raceprogress.png").Sprite()->height/2.f}}; + for(int i:std::ranges::iota_view(0U,Checkpoint::GetCheckpoints().size()-1)){ + game.FillRectDecal(progressBarBottomPos+vf2d{-5.f,-float((game.GetGFX("raceprogress.png").Sprite()->height/(Checkpoint::GetCheckpoints().size()))*(i+1))},{10.f,1.f},WHITE); + } + int playerPlacement{}; + std::optional>playerHamsterRanking{}; + for(int placement{};HamsterRanking&ranking:hamsterRanking){ + if(ranking.hamster.get().IsPlayerControlled){ + playerHamsterRanking=ranking; + playerPlacement=hamsterRanking.size()-placement; + game.DrawPartialRotatedDecal(progressBarBottomPos+vf2d{0.f,-(float(ranking.ranking)/Checkpoint::GetCheckpoints().size())*game.GetGFX("raceprogress.png").Sprite()->height},ranking.hamster.get().GetCurrentAnimation().GetSourceImage()->Decal(),0.f,{8.f,6.f},{64.f,64.f},{16.f,12.f}); + }else game.DrawPartialRotatedDecal(progressBarBottomPos+vf2d{0.f,-(float(ranking.ranking)/Checkpoint::GetCheckpoints().size())*game.GetGFX("raceprogress.png").Sprite()->height},ranking.hamster.get().GetCurrentAnimation().GetSourceImage()->Decal(),0.f,{8.f,6.f},{64.f,64.f},{16.f,12.f}); + placement++; + } + + if(playerHamsterRanking.has_value()){ + std::string addonStr{"th"}; + if(playerPlacement==1)addonStr="st"; + else if(playerPlacement==2)addonStr="nd"; + std::string placementStr{std::format("{}{}",playerPlacement,addonStr)}; + vi2d placementStrSize{game.GetTextSizeProp(placementStr)}; + Pixel blinkCol{DARK_RED}; + if(playerPlacement==1)blinkCol=CYAN; + else if(playerPlacement<=3)blinkCol=DARK_GREEN; + for(int y:std::ranges::iota_view(-1,2)){ + for(int x:std::ranges::iota_view(-1,2)){ + game.DrawRotatedStringPropDecal(progressBarBottomPos+vf2d{-4.f,8.f}+vi2d{x,y},placementStr,0.f,{},BLACK,{3.f,3.f}); + } + } + game.DrawRotatedStringPropDecal(progressBarBottomPos+vf2d{-4.f,8.f},placementStr,0.f,{},blinkCol,{3.f,3.f}); + } +} + +const float HamsterLeaderboard::HamsterRanking::GetRanking()const{ + return ranking; +} \ No newline at end of file diff --git a/src/HamsterLeaderboard.h b/src/HamsterLeaderboard.h index 67bbe96..5bb5e97 100644 --- a/src/HamsterLeaderboard.h +++ b/src/HamsterLeaderboard.h @@ -36,15 +36,22 @@ All rights reserved. */ #pragma endregion #pragma once -#include "Hamster.h" +#include +class HamsterGame; +class Hamster; class HamsterLeaderboard{ class HamsterRanking{ + friend class HamsterLeaderboard; std::reference_wrapperhamster; - float ranking; //The higher the ranking, the higher the hamster is placed. + float ranking{}; //The higher the ranking, the higher the hamster is placed. + public: + HamsterRanking(Hamster&hamsterRef); + const float GetRanking()const; }; std::vectorhamsterRanking; public: void OnRaceStart(); - void OnRaceFinished(); + void Update(); + void Draw(HamsterGame&game); }; \ No newline at end of file diff --git a/src/TODO.txt b/src/TODO.txt index 2dad0a9..9389dd3 100644 --- a/src/TODO.txt +++ b/src/TODO.txt @@ -155,9 +155,9 @@ Stage 12: Boss Battle #5 V2 - nene Live Placement Tracking -Pulsating Animation based on terrain walking across +Pulsating Animation based on terrain walking across (30 min) -Sound Effects +Sound Effects (1 hour) Footstep sounds(Grass, Sand, Rock, Shore (Shared with Lava and Swamp), Ocean) Hamster Wheel sounds (Rolling) @@ -184,6 +184,7 @@ Menu Navigations (2 hours) Grand Prix Management (1 hour) Unlocks (1 hour) Leaderboard Management (1 hour) +Tutorial Stuff (3 hours) Menus ===========