Codename Hamster - For olcCodeJam 2024
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hamster/src/Hamster.cpp

336 lines
13 KiB

#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.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.
Portions of this software are copyright <EFBFBD> 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "HamsterGame.h"
#include "Hamster.h"
#include "util.h"
#include <ranges>
std::vector<Hamster>Hamster::HAMSTER_LIST;
const uint8_t Hamster::MAX_HAMSTER_COUNT{100U};
const uint8_t Hamster::NPC_HAMSTER_COUNT{5U};
const std::vector<std::string>Hamster::NPC_HAMSTER_IMAGES{
"hamster.png",
};
const std::string Hamster::PLAYER_HAMSTER_IMAGE{"hamster.png"};
std::optional<Hamster*>Hamster::playerHamster;
Hamster::Hamster(const vf2d spawnPos,const std::string_view img,const PlayerControlled IsPlayerControlled)
:pos(spawnPos),IsPlayerControlled(IsPlayerControlled){
animations=HamsterGame::GetAnimations(img);
animations.ChangeState(internalAnimState,HamsterGame::DEFAULT);
}
void Hamster::UpdateHamsters(const float fElapsedTime){
for(Hamster&h:HAMSTER_LIST){
h.lastSafeLocationTimer=std::max(0.f,h.lastSafeLocationTimer-fElapsedTime);
h.animations.UpdateState(h.internalAnimState,fElapsedTime);
h.frictionEnabled=true;
h.bumpTimer-=fElapsedTime;
h.HandleCollision();
switch(h.state){
case NORMAL:{
//TODO: NPC controls.
if(h.IsPlayerControlled){
h.HandlePlayerControls();
}
}break;
case BUMPED:{
if(h.bumpTimer<=0.f){
h.state=NORMAL;
}
}break;
case DROWNING:{
if(h.imgScale>0.f){
h.shrinkEffectColor=BLACK;
h.imgScale=std::max(0.f,h.imgScale-0.5f*fElapsedTime);
}
else{
h.waitTimer=4.f;
h.state=WAIT;
}
}break;
case WAIT:{
h.waitTimer=std::max(0.f,h.waitTimer-fElapsedTime);
if(h.waitTimer<=0.f){
h.imgScale=1.f;
h.drownTimer=0.f;
h.pos=h.lastSafeLocation;
h.state=NORMAL;
h.RemoveAllPowerups();
}
}break;
case BURNING:{
if(h.imgScale>0.f){
h.shrinkEffectColor=RED;
h.imgScale=std::max(0.f,h.imgScale-0.5f*fElapsedTime);
}else{
h.waitTimer=4.f;
h.state=WAIT;
}
}break;
}
if((h.GetTerrainStandingOn()==Terrain::OCEAN||!h.HasPowerup(Powerup::SWAMP)&&h.GetTerrainStandingOn()==Terrain::SWAMP)&&h.state!=DROWNING&&h.state!=WAIT)h.drownTimer+=fElapsedTime;
else if((!h.HasPowerup(Powerup::LAVA)&&h.GetTerrainStandingOn()==Terrain::LAVA)&&h.state!=BURNING&&h.state!=WAIT)h.burnTimer+=fElapsedTime;
else if(h.lastSafeLocationTimer<=0.f&&h.state==NORMAL&&!h.StandingOnLethalTerrain()){
h.lastSafeLocationTimer=0.5f;
h.drownTimer=0.f;
h.burnTimer=0.f;
h.lastSafeLocation=h.GetPos();
}
if(h.drownTimer>=h.DEFAULT_DROWN_TIME&&h.state!=DROWNING&&h.state!=WAIT){
h.state=DROWNING;
}
if(h.burnTimer>=h.DEFAULT_BURN_TIME&&h.state!=BURNING&&h.state!=WAIT){
h.state=BURNING;
}
h.TurnTowardsTargetDirection();
h.MoveHamster();
}
}
void Hamster::LoadHamsters(const vf2d startingLoc){
HAMSTER_LIST.clear();
playerHamster.reset();
HAMSTER_LIST.reserve(MAX_HAMSTER_COUNT);
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);
}
}
void Hamster::DrawHamsters(TransformedView&tv){
for(Hamster&h:HAMSTER_LIST){
const Animate2D::FrameSequence&anim{h.animations.GetFrames(h.internalAnimState)};
const Animate2D::FrameSequence&wheelTopAnim{h.animations.GetFrames(HamsterGame::WHEEL_TOP)};
const Animate2D::FrameSequence&wheelBottomAnim{h.animations.GetFrames(HamsterGame::WHEEL_BOTTOM)};
const Animate2D::Frame&img{h.animations.GetState(h.internalAnimState)==HamsterGame::DEFAULT?anim.GetFrame(h.distanceTravelled/100.f):h.GetCurrentAnimation()};
const Animate2D::Frame&wheelTopImg{wheelTopAnim.GetFrame(h.distanceTravelled/80.f)};
const Animate2D::Frame&wheelBottomImg{wheelBottomAnim.GetFrame(h.distanceTravelled/80.f)};
if(h.HasPowerup(Powerup::WHEEL))tv.DrawPartialRotatedDecal(h.pos,wheelBottomImg.GetSourceImage()->Decal(),h.rot,wheelBottomImg.GetSourceRect().size/2,wheelBottomImg.GetSourceRect().pos,wheelBottomImg.GetSourceRect().size,vf2d{1.f,1.f}*h.imgScale,PixelLerp(h.shrinkEffectColor,WHITE,h.imgScale));
tv.DrawPartialRotatedDecal(h.pos,img.GetSourceImage()->Decal(),h.rot,img.GetSourceRect().size/2,img.GetSourceRect().pos,img.GetSourceRect().size,vf2d{1.f,1.f}*h.imgScale,PixelLerp(h.shrinkEffectColor,WHITE,h.imgScale));
if(h.HasPowerup(Powerup::WHEEL))tv.DrawPartialRotatedDecal(h.pos,wheelTopImg.GetSourceImage()->Decal(),h.rot,wheelTopImg.GetSourceRect().size/2,wheelTopImg.GetSourceRect().pos,wheelTopImg.GetSourceRect().size,vf2d{1.f,1.f}*h.imgScale,PixelLerp(h.shrinkEffectColor,{255,255,255,192},h.imgScale));
}
}
const Animate2D::Frame&Hamster::GetCurrentAnimation()const{
return animations.GetFrame(internalAnimState);
}
const Hamster&Hamster::GetPlayer(){
if(!playerHamster.has_value())throw std::runtime_error{std::format("WARNING! Player is not created at this time! There should not be any code referencing the player at this moment!")};
return *playerHamster.value();
}
const vf2d&Hamster::GetPos()const{
return pos;
}
void Hamster::HandlePlayerControls(){
vf2d aimingDir{};
if(HamsterGame::Game().GetKey(W).bHeld){
aimingDir+=vf2d{0,-1};
}
if(HamsterGame::Game().GetKey(D).bHeld){
aimingDir+=vf2d{1,0};
}
if(HamsterGame::Game().GetKey(S).bHeld){
aimingDir+=vf2d{0,1};
}
if(HamsterGame::Game().GetKey(A).bHeld){
aimingDir+=vf2d{-1,0};
}
if(HamsterGame::Game().GetKey(PGUP).bPressed){
HamsterGame::Game().tv.ZoomAtScreenPos(0.95f,HamsterGame::Game().tv.WorldToScreen(GetPos()));
z+=HamsterGame::Game().GetElapsedTime();
}
if(HamsterGame::Game().GetKey(PGDN).bPressed){
z=std::max(0.f,z-HamsterGame::Game().GetElapsedTime());
HamsterGame::Game().tv.ZoomAtScreenPos(1.05f,HamsterGame::Game().tv.WorldToScreen(GetPos()));
}
if(aimingDir!=vf2d{}){
targetRot=aimingDir.norm().polar().y;
const vf2d currentVel{vel};
vel+=vf2d{currentVel.polar().x+(GetMaxSpeed()*HamsterGame::Game().GetElapsedTime())/GetTimeToMaxSpeed(),rot}.cart();
vel=vf2d{std::min(GetMaxSpeed(),vel.polar().x),vel.polar().y}.cart();
frictionEnabled=false;
}
}
void Hamster::TurnTowardsTargetDirection(){
util::turn_towards_direction(rot,targetRot,GetTurnSpeed()*HamsterGame::Game().GetElapsedTime());
}
void Hamster::MoveHamster(){
pos+=vel*HamsterGame::Game().GetElapsedTime();
distanceTravelled+=vel.mag()*HamsterGame::Game().GetElapsedTime();
#pragma region Handle Friction
if(frictionEnabled){
const vf2d currentVel{vel};
vel=vf2d{std::max(0.f,currentVel.polar().x-GetFriction()*HamsterGame::Game().GetElapsedTime()),currentVel.polar().y}.cart();
}
#pragma endregion
}
void Hamster::HandleCollision(){
for(Hamster&h:HAMSTER_LIST){
if(this==&h)continue;
if(geom2d::overlaps(geom2d::circle<float>(GetPos(),GetRadius()),geom2d::circle<float>(h.GetPos(),h.GetRadius()))){
if(geom2d::line<float>(GetPos(),h.GetPos()).length()==0.f){ //Push these two in random directions, they are on top of each other!
float randDir{util::random(2*geom2d::pi)};
vf2d collisionResolve1{GetPos()+vf2d{GetRadius(),randDir}.cart()};
vf2d collisionResolve2{h.GetPos()+vf2d{h.GetRadius(),float(randDir+geom2d::pi)}.cart()};
pos=collisionResolve1;
h.pos=collisionResolve2;
vel=vf2d{GetBumpAmount(),randDir}.cart();
h.vel=vf2d{GetBumpAmount(),float(randDir+geom2d::pi)}.cart();
}else{
geom2d::line<float>collisionLine{geom2d::line<float>(GetPos(),h.GetPos())};
float distance{collisionLine.length()};
float totalRadii{GetRadius()+h.GetRadius()};
float bumpDistance{totalRadii-distance};
vf2d collisionResolve1{GetPos()+vf2d{bumpDistance/2.f,float(collisionLine.vector().polar().y+geom2d::pi)}.cart()};
vf2d collisionResolve2{h.GetPos()+vf2d{bumpDistance/2.f,collisionLine.vector().polar().y}.cart()};
pos=collisionResolve1;
h.pos=collisionResolve2;
vel=vf2d{GetBumpAmount(),float(collisionLine.vector().polar().y+geom2d::pi)}.cart();
h.vel=vf2d{GetBumpAmount(),collisionLine.vector().polar().y}.cart();
}
state=h.state=BUMPED;
bumpTimer=h.bumpTimer=0.12f;
}
}
for(Powerup&powerup:Powerup::GetPowerups()){
if(!HasPowerup(powerup.GetType())&&geom2d::overlaps(geom2d::circle<float>(GetPos(),collisionRadius),geom2d::circle<float>(powerup.GetPos(),20.f))){
ObtainPowerup(powerup.GetType());
powerup.OnPowerupObtain();
}
}
}
const float Hamster::GetRadius()const{
return collisionRadius;
}
const Terrain::TerrainType Hamster::GetTerrainStandingOn()const{
return HamsterGame::Game().GetTerrainTypeAtPos(GetPos());
}
const float Hamster::GetTimeToMaxSpeed()const{
float finalTimeToMaxSpd{DEFAULT_TIME_TO_MAX_SPD};
if(!HasPowerup(Powerup::ICE)&&GetTerrainStandingOn()==Terrain::ICE)finalTimeToMaxSpd*=3;
else if(!HasPowerup(Powerup::SWAMP)&&GetTerrainStandingOn()==Terrain::SWAMP)finalTimeToMaxSpd*=1.25;
return finalTimeToMaxSpd;
}
const float Hamster::GetMaxSpeed()const{
float finalMaxSpd{DEFAULT_MAX_SPD};
switch(GetTerrainStandingOn()){
case Terrain::GRASS:{
if(!HasPowerup(Powerup::GRASS))finalMaxSpd*=0.80f;
}break;
case Terrain::SAND:{
if(!HasPowerup(Powerup::SAND))finalMaxSpd*=0.60f;
}break;
case Terrain::SWAMP:{
if(HasPowerup(Powerup::SWAMP))finalMaxSpd*=0.80f;
else finalMaxSpd*=0.50f;
}break;
case Terrain::SHORE:{
finalMaxSpd*=0.80f;
}break;
case Terrain::OCEAN:{
finalMaxSpd*=0.10f;
}break;
case Terrain::FOREST:{
if(!HasPowerup(Powerup::FOREST))finalMaxSpd*=0.50f;
}break;
}
if(HasPowerup(Powerup::WHEEL))finalMaxSpd*=1.5f;
return finalMaxSpd;
}
const float Hamster::GetFriction()const{
float finalFriction{DEFAULT_FRICTION};
if(!HasPowerup(Powerup::ICE)&&GetTerrainStandingOn()==Terrain::ICE)finalFriction*=0.1f;
else if(!HasPowerup(Powerup::SWAMP)&&GetTerrainStandingOn()==Terrain::SWAMP)finalFriction*=0.6f;
return finalFriction;
}
const float Hamster::GetTurnSpeed()const{
float finalTurnSpd{DEFAULT_TURN_SPD};
if(!HasPowerup(Powerup::ICE)&&GetTerrainStandingOn()==Terrain::ICE)finalTurnSpd*=0.6f;
return finalTurnSpd;
}
const float Hamster::GetBumpAmount()const{
float finalBumpAmt{DEFAULT_BUMP_AMT};
return finalBumpAmt*GetMaxSpeed()/DEFAULT_MAX_SPD;
}
void Hamster::ObtainPowerup(const Powerup::PowerupType powerup){
powerups.insert(powerup);
}
const bool Hamster::HasPowerup(const Powerup::PowerupType powerup)const{
return powerups.count(powerup);
}
void Hamster::RemoveAllPowerups(){
powerups.clear();
}
const bool Hamster::StandingOnLethalTerrain()const{
return GetTerrainStandingOn()==Terrain::LAVA||GetTerrainStandingOn()==Terrain::OCEAN||GetTerrainStandingOn()==Terrain::SWAMP;
}
const bool Hamster::IsDrowning()const{
return drownTimer>0.f;
}
const bool Hamster::IsBurning()const{
return burnTimer>0.f;
}
const float Hamster::GetDrownRatio()const{
return drownTimer/DEFAULT_DROWN_TIME;
}
const float Hamster::GetBurnRatio()const{
return burnTimer/DEFAULT_BURN_TIME;
}
const float&Hamster::GetZ()const{
return z;
}