Revamp Player vs Monster collision code. Add in handling for solid objects causing the player to run against the object instead of bouncing off of it. Make solid monsters (pillars) have transparency like foreground terrain when looking behind them. Add terrain collision boxes for these as well. Release Build 9610.

mac-build
sigonasr2 6 months ago
parent b06199efe0
commit bcbe58eebd
  1. 59
      Adventures in Lestoria/Monster.cpp
  2. 2
      Adventures in Lestoria/Monster.h
  3. 22
      Adventures in Lestoria/Player.cpp
  4. 4
      Adventures in Lestoria/StoneGolem.cpp
  5. 2
      Adventures in Lestoria/Version.h
  6. 11
      Adventures in Lestoria/assets/config/Monsters.txt
  7. BIN
      Adventures in Lestoria/assets/gamepack.pak
  8. BIN
      x64/Release/Adventures in Lestoria.exe

@ -51,6 +51,7 @@ All rights reserved.
#ifndef __EMSCRIPTEN__ #ifndef __EMSCRIPTEN__
#include "steam/isteamuserstats.h" #include "steam/isteamuserstats.h"
#endif #endif
#include "GameSettings.h"
INCLUDE_ANIMATION_DATA INCLUDE_ANIMATION_DATA
INCLUDE_MONSTER_DATA INCLUDE_MONSTER_DATA
@ -265,6 +266,10 @@ bool Monster::Update(float fElapsedTime){
lastPathfindingCooldown=std::max(0.f,lastPathfindingCooldown-fElapsedTime); lastPathfindingCooldown=std::max(0.f,lastPathfindingCooldown-fElapsedTime);
lastFacingDirectionChange+=fElapsedTime; lastFacingDirectionChange+=fElapsedTime;
timeSpentAlive+=fElapsedTime; timeSpentAlive+=fElapsedTime;
if(IsSolid()){
if(GetPos().y>=game->GetPlayer()->GetPos().y)solidFadeTimer=std::min(TileGroup::FADE_TIME,solidFadeTimer+game->GetElapsedTime());
else solidFadeTimer=std::max(0.f,solidFadeTimer-game->GetElapsedTime());
}
if(HasArrowIndicator()&&IsAlive())game->SetBossIndicatorPos(GetPos()); if(HasArrowIndicator()&&IsAlive())game->SetBossIndicatorPos(GetPos());
@ -330,8 +335,10 @@ bool Monster::Update(float fElapsedTime){
} }
if(!HasIframes()){ if(!HasIframes()){
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::unique_ptr<Monster>&m:MONSTER_LIST){
const float monsterRadius{GetCollisionRadius()};
const float otherMonsterRadius{m->GetCollisionRadius()};
if(&*m==this)continue; if(&*m==this)continue;
if(!m->HasIframes()&&OnUpperLevel()==m->OnUpperLevel()&&abs(m->GetZ()-GetZ())<=1&&geom2d::overlaps(geom2d::circle(pos,GetCollisionRadius()),geom2d::circle(m->GetPos(),m->GetCollisionRadius()))){ if(!m->HasIframes()&&OnUpperLevel()==m->OnUpperLevel()&&abs(m->GetZ()-GetZ())<=1&&geom2d::overlaps(geom2d::circle(pos,monsterRadius),geom2d::circle(m->GetPos(),otherMonsterRadius))){
m->Collision(*this); m->Collision(*this);
geom2d::line line(pos,m->GetPos()); geom2d::line line(pos,m->GetPos());
float dist = line.length(); float dist = line.length();
@ -339,24 +346,21 @@ bool Monster::Update(float fElapsedTime){
line={pos+vf2d{util::random(0.2f)-0.1f,util::random(0.2f)-0.1f},m->GetPos()}; line={pos+vf2d{util::random(0.2f)-0.1f,util::random(0.2f)-0.1f},m->GetPos()};
dist=line.length(); dist=line.length();
} }
m->SetPos(line.rpoint(dist*1.1f)); const float displacementDist=(otherMonsterRadius+monsterRadius)-dist;
if(!Immovable()&&m->IsAlive()){ if(m->IsAlive()){
float knockbackStrength=1.f; if(!m->IsSolid()){
std::vector<Buff> knockbackBuffs=m->GetBuffs(COLLISION_KNOCKBACK_STRENGTH); float knockbackStrength=1.f;
for(Buff&b:knockbackBuffs){ std::vector<Buff> knockbackBuffs=m->GetBuffs(COLLISION_KNOCKBACK_STRENGTH);
knockbackStrength+=b.intensity; for(Buff&b:knockbackBuffs){
knockbackStrength+=b.intensity;
}
Knockback(line.vector().norm()*-128*knockbackStrength);
}else{
SetPos(line.rpoint(-displacementDist));
} }
Knockback(line.vector().norm()*-128*knockbackStrength);
} }
} }
} }
if(!Immovable()&&
!game->GetPlayer()->HasIframes()&&abs(game->GetPlayer()->GetZ()-GetZ())<=1&&game->GetPlayer()->OnUpperLevel()==OnUpperLevel()&&geom2d::overlaps(geom2d::circle(pos,GetCollisionRadius()),geom2d::circle(game->GetPlayer()->GetPos(),12*game->GetPlayer()->GetSizeMult()/2))){
geom2d::line line(pos,game->GetPlayer()->GetPos());
float dist = line.length();
SetPos(line.rpoint(-0.1f));
vel=line.vector().norm()*-128;
}
} }
if(GetState()==State::NORMAL){ if(GetState()==State::NORMAL){
UpdateFacingDirection(game->GetPlayer()->GetPos()); UpdateFacingDirection(game->GetPlayer()->GetPos());
@ -439,12 +443,14 @@ void Monster::Draw()const{
} }
const bool NotOnTitleScreen=GameState::STATE!=GameState::states[States::MAIN_MENU]; const bool NotOnTitleScreen=GameState::STATE!=GameState::states[States::MAIN_MENU];
uint8_t blendColAlpha=255U; uint8_t blendColAlpha=blendCol.a;
if(NotOnTitleScreen
&&(game->GetPlayer()->HasIframes()||OnUpperLevel()!=game->GetPlayer()->OnUpperLevel()||abs(GetZ()-game->GetPlayer()->GetZ())>1))blendColAlpha=160;
if(fadeTimer>0.f)blendColAlpha=uint8_t(util::lerp(0,blendCol.a,fadeTimer)); //Fade timer goes from 1 to 0 seconds. if(fadeTimer>0.f)blendColAlpha=uint8_t(util::lerp(0,blendCol.a,fadeTimer)); //Fade timer goes from 1 to 0 seconds.
else
if(NotOnTitleScreen
&&(game->GetPlayer()->HasIframes()||OnUpperLevel()!=game->GetPlayer()->OnUpperLevel()||abs(GetZ()-game->GetPlayer()->GetZ())>1))blendColAlpha=blendCol.a*0.62f;
else
if(IsSolid()&&solidFadeTimer>0.f)blendColAlpha=uint8_t(util::lerp(blendCol.a,255-TileGroup::FADE_AMT,solidFadeTimer/TileGroup::FADE_TIME));
blendCol.a=blendColAlpha; blendCol.a=blendColAlpha;
@ -458,6 +464,17 @@ void Monster::Draw()const{
if(shieldBuffs.size()>0){ if(shieldBuffs.size()>0){
game->view.DrawRotatedDecal(drawPos,GFX["block.png"].Decal(),0.f,GFX["block.png"].Sprite()->Size()/2,{GetSizeMult(),GetSizeMult()}); game->view.DrawRotatedDecal(drawPos,GFX["block.png"].Decal(),0.f,GFX["block.png"].Sprite()->Size()/2,{GetSizeMult(),GetSizeMult()});
} }
if(GameSettings::TerrainCollisionBoxesEnabled()&&IsSolid()&&solidFadeTimer>0.f){
float distToPlayer=geom2d::line<float>(game->GetPlayer()->GetPos(),GetPos()).length();
const float collisionRadiusFactor=GetCollisionRadius()/12.f;
if(distToPlayer<24*3*collisionRadiusFactor){
game->DrawPie(game->view.WorldToScreen(GetPos()),GetCollisionRadius(),0.f,{255,0,0,uint8_t(128*(blendColAlpha/255.f)/sqrt(distToPlayer*collisionRadiusFactor))});
game->SetDecalMode(DecalMode::WIREFRAME);
game->DrawPie(game->view.WorldToScreen(GetPos()),GetCollisionRadius(),0.f,{128,0,0,255});
game->SetDecalMode(DecalMode::NORMAL);
}
}
#pragma region Debug Pathfinding #pragma region Debug Pathfinding
#ifdef _DEBUG #ifdef _DEBUG
@ -1090,4 +1107,8 @@ const bool Monster::ReachedTargetPos(const float maxDistanceFromTarget)const{
const float Monster::GetHealthRatio()const{ const float Monster::GetHealthRatio()const{
return GetHealth()/float(GetMaxHealth()); return GetHealth()/float(GetMaxHealth());
}
const bool Monster::IsSolid()const{
return Immovable();
} }

@ -169,6 +169,7 @@ public:
const bool IgnoresTerrainCollision()const; const bool IgnoresTerrainCollision()const;
const float TimeSpentAlive()const; const float TimeSpentAlive()const;
const bool Immovable()const; const bool Immovable()const;
const bool IsSolid()const;
const bool Invulnerable()const; const bool Invulnerable()const;
//If an object has a lifetime set, returns it. //If an object has a lifetime set, returns it.
const std::optional<float>GetLifetime()const; const std::optional<float>GetLifetime()const;
@ -256,6 +257,7 @@ private:
std::optional<float>lifetime{}; std::optional<float>lifetime{};
float fadeTimer{0.f}; float fadeTimer{0.f};
bool markedForDeletion{false}; //DO NOT MODIFY DIRECTLY. Use MarkForDeletion() if this monster needs to be marked. NOTE: Marking a monster for deletion does not trigger any death events. It just simply removes the monster from the field!! bool markedForDeletion{false}; //DO NOT MODIFY DIRECTLY. Use MarkForDeletion() if this monster needs to be marked. NOTE: Marking a monster for deletion does not trigger any death events. It just simply removes the monster from the field!!
float solidFadeTimer{0.f};
private: private:
struct STRATEGY{ struct STRATEGY{
static std::string ERR; static std::string ERR;

@ -521,7 +521,9 @@ void Player::Update(float fElapsedTime){
item3.cooldown=0; item3.cooldown=0;
} }
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(!HasIframes()&&abs(m->GetZ()-GetZ())<=1&&OnUpperLevel()==m->OnUpperLevel()&&geom2d::overlaps(geom2d::circle(pos,12*size/2),geom2d::circle(m->GetPos(),m->GetCollisionRadius()))){ const float playerRadius{12*GetSizeMult()/2};
const float monsterRadius{m->GetCollisionRadius()};
if(!HasIframes()&&abs(m->GetZ()-GetZ())<=1&&OnUpperLevel()==m->OnUpperLevel()&&geom2d::overlaps(geom2d::circle(pos,playerRadius),geom2d::circle(m->GetPos(),monsterRadius))){
if(m->IsAlive()){ if(m->IsAlive()){
m->Collision(this); m->Collision(this);
} }
@ -531,16 +533,22 @@ void Player::Update(float fElapsedTime){
line={pos+vf2d{util::random(0.2f)-0.1f,util::random(0.2f)-0.1f},m->GetPos()}; line={pos+vf2d{util::random(0.2f)-0.1f,util::random(0.2f)-0.1f},m->GetPos()};
dist=line.length(); dist=line.length();
} }
if(!m->Immovable()){ const float displacementDist=(playerRadius+monsterRadius)-dist;
if(!m->IsSolid()){
m->SetPos(line.rpoint(dist*1.1f)); m->SetPos(line.rpoint(dist*1.1f));
} }
if(m->IsAlive()&&!m->IsNPC()){ //Don't set the knockback if this monster is actually an NPC. Let's just push them around. if(m->IsAlive()&&!m->IsNPC()){ //Don't set the knockback if this monster is actually an NPC. Let's just push them around.
float knockbackStrength=1.f; if(!m->IsSolid()){
std::vector<Buff>knockbackBuffs=m->GetBuffs(COLLISION_KNOCKBACK_STRENGTH); float knockbackStrength=1.f;
for(Buff&b:knockbackBuffs){ std::vector<Buff>knockbackBuffs=m->GetBuffs(COLLISION_KNOCKBACK_STRENGTH);
knockbackStrength+=b.intensity; for(Buff&b:knockbackBuffs){
knockbackStrength+=b.intensity;
}
m->Knockback(line.vector().norm()*128.f);
Knockback(line.vector().norm()*-128.f*knockbackStrength);
}else{
SetPos(line.rpoint(-displacementDist));
} }
Knockback(line.vector().norm()*-128.f*knockbackStrength);
} }
} }
} }

@ -64,7 +64,7 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
m.F(A::RECOVERY_TIME)-=fElapsedTime; m.F(A::RECOVERY_TIME)-=fElapsedTime;
if(m.F(A::RECOVERY_TIME)<=0.f){ if(m.F(A::RECOVERY_TIME)<=0.f){
m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos(); m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos();
m.PerformAnimation("STONE PILLAR CAST",m.GetFacingDirectionToTarget(m.V(A::LOCKON_POS))); m.PerformAnimation("CAST",m.GetFacingDirectionToTarget(m.V(A::LOCKON_POS)));
game->AddEffect(std::make_unique<Effect>(m.V(A::LOCKON_POS),ConfigFloat("Beginning Phase.Pillar Cast Time"),"range_indicator.png",m.OnUpperLevel(),vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Golem Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Golem Pillar").GetSizeMult()/12.f)*1.25f,0.3f,vf2d{},ConfigPixel("Beginning Phase.Pillar Spell Circle Color"),util::random(2*PI),util::degToRad(ConfigFloat("Beginning Phase.Pillar Spell Circle Rotation Spd"))),true); game->AddEffect(std::make_unique<Effect>(m.V(A::LOCKON_POS),ConfigFloat("Beginning Phase.Pillar Cast Time"),"range_indicator.png",m.OnUpperLevel(),vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Golem Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Golem Pillar").GetSizeMult()/12.f)*1.25f,0.3f,vf2d{},ConfigPixel("Beginning Phase.Pillar Spell Circle Color"),util::random(2*PI),util::degToRad(ConfigFloat("Beginning Phase.Pillar Spell Circle Rotation Spd"))),true);
game->AddEffect(std::make_unique<Effect>(m.V(A::LOCKON_POS),ConfigFloat("Beginning Phase.Pillar Cast Time"),"spell_insignia.png",m.OnUpperLevel(),vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Golem Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Golem Pillar").GetSizeMult()/12.f)*0.9f,0.3f,vf2d{},ConfigPixel("Beginning Phase.Pillar Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Beginning Phase.Pillar Spell Insignia Rotation Spd"))),true); game->AddEffect(std::make_unique<Effect>(m.V(A::LOCKON_POS),ConfigFloat("Beginning Phase.Pillar Cast Time"),"spell_insignia.png",m.OnUpperLevel(),vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Golem Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Golem Pillar").GetSizeMult()/12.f)*0.9f,0.3f,vf2d{},ConfigPixel("Beginning Phase.Pillar Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Beginning Phase.Pillar Spell Insignia Rotation Spd"))),true);
m.F(A::CASTING_TIMER)=ConfigFloat("Beginning Phase.Pillar Cast Time"); m.F(A::CASTING_TIMER)=ConfigFloat("Beginning Phase.Pillar Cast Time");
@ -75,6 +75,8 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
m.F(A::CASTING_TIMER)-=fElapsedTime; m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f){ if(m.F(A::CASTING_TIMER)<=0.f){
m.I(A::PATTERN_REPEAT_COUNT)--; m.I(A::PATTERN_REPEAT_COUNT)--;
game->SpawnMonster(m.V(A::LOCKON_POS),MONSTER_DATA.at("Stone Golem Pillar"),m.OnUpperLevel());
game->Hurt(m.V(A::LOCKON_POS),MONSTER_DATA.at("Stone Golem Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Golem Pillar").GetSizeMult(),m.GetAttack(),m.OnUpperLevel(),0.f,HurtType::PLAYER);
if(m.I(A::PATTERN_REPEAT_COUNT)<=0){ if(m.I(A::PATTERN_REPEAT_COUNT)<=0){
m.phase=STANDARD; m.phase=STANDARD;
}else{ }else{

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1 #define VERSION_MAJOR 1
#define VERSION_MINOR 2 #define VERSION_MINOR 2
#define VERSION_PATCH 3 #define VERSION_PATCH 3
#define VERSION_BUILD 9585 #define VERSION_BUILD 9610
#define stringify(a) stringify_(a) #define stringify(a) stringify_(a)
#define stringify_(a) #a #define stringify_(a) #a

@ -988,7 +988,7 @@ Monsters
CollisionDmg = 40 CollisionDmg = 40
MoveSpd = 180% MoveSpd = 50%
Size = 400% Size = 400%
XP = 5 XP = 5
@ -1011,12 +1011,13 @@ Monsters
# The First Four animations must represent a standing, walking, attack, and death animation. Their names are up to the creator. # The First Four animations must represent a standing, walking, attack, and death animation. Their names are up to the creator.
IDLE = 2, 0.6, Repeat IDLE = 2, 0.6, Repeat
WALK = 4, 0.2, Repeat WALK = 4, 0.2, Repeat
TOSS ROCK = 4, 0.2, OneShot CAST = 2, 0.3, Repeat
DEATH = 4, 0.15, OneShot DEATH = 4, 0.15, OneShot
BURROW UNDERGROUND = 5, 0.15, OneShot BURROW UNDERGROUND = 5, 0.15, OneShot
RISE FROM UNDERGROUND = 5, 0.15, OneShot RISE FROM UNDERGROUND = 5, 0.15, OneShot
ROCK TOSS CAST = 2, 0.3, Repeat TOSS ROCK Cast = 2, 0.2, Repeat
STONE PILLAR CAST = 2, 0.3, Repeat SLAM = 3, 0.2, OneShot
TOSS ROCK = 4, 0.2, OneShot
} }
Ignore Collisions = False Ignore Collisions = False
@ -1040,7 +1041,7 @@ Monsters
MoveSpd = 0% MoveSpd = 0%
# The Pillar is supposed to be 350 radius. # The Pillar is supposed to be 350 radius.
Size = 300% Size = 600%
Collision Radius = 7 Collision Radius = 7
XP = 0 XP = 0

Loading…
Cancel
Save