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. 47
      Adventures in Lestoria/Monster.cpp
  2. 2
      Adventures in Lestoria/Monster.h
  3. 12
      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__
#include "steam/isteamuserstats.h"
#endif
#include "GameSettings.h"
INCLUDE_ANIMATION_DATA
INCLUDE_MONSTER_DATA
@ -265,6 +266,10 @@ bool Monster::Update(float fElapsedTime){
lastPathfindingCooldown=std::max(0.f,lastPathfindingCooldown-fElapsedTime);
lastFacingDirectionChange+=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());
@ -330,8 +335,10 @@ bool Monster::Update(float fElapsedTime){
}
if(!HasIframes()){
for(std::unique_ptr<Monster>&m:MONSTER_LIST){
const float monsterRadius{GetCollisionRadius()};
const float otherMonsterRadius{m->GetCollisionRadius()};
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);
geom2d::line line(pos,m->GetPos());
float dist = line.length();
@ -339,23 +346,20 @@ bool Monster::Update(float fElapsedTime){
line={pos+vf2d{util::random(0.2f)-0.1f,util::random(0.2f)-0.1f},m->GetPos()};
dist=line.length();
}
m->SetPos(line.rpoint(dist*1.1f));
if(!Immovable()&&m->IsAlive()){
const float displacementDist=(otherMonsterRadius+monsterRadius)-dist;
if(m->IsAlive()){
if(!m->IsSolid()){
float knockbackStrength=1.f;
std::vector<Buff> knockbackBuffs=m->GetBuffs(COLLISION_KNOCKBACK_STRENGTH);
for(Buff&b:knockbackBuffs){
knockbackStrength+=b.intensity;
}
Knockback(line.vector().norm()*-128*knockbackStrength);
}else{
SetPos(line.rpoint(-displacementDist));
}
}
}
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){
@ -439,12 +443,14 @@ void Monster::Draw()const{
}
const bool NotOnTitleScreen=GameState::STATE!=GameState::states[States::MAIN_MENU];
uint8_t blendColAlpha=255U;
if(NotOnTitleScreen
&&(game->GetPlayer()->HasIframes()||OnUpperLevel()!=game->GetPlayer()->OnUpperLevel()||abs(GetZ()-game->GetPlayer()->GetZ())>1))blendColAlpha=160;
uint8_t blendColAlpha=blendCol.a;
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;
@ -459,6 +465,17 @@ void Monster::Draw()const{
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
#ifdef _DEBUG
if("debug_pathfinding"_I){
@ -1091,3 +1108,7 @@ const bool Monster::ReachedTargetPos(const float maxDistanceFromTarget)const{
const float Monster::GetHealthRatio()const{
return GetHealth()/float(GetMaxHealth());
}
const bool Monster::IsSolid()const{
return Immovable();
}

@ -169,6 +169,7 @@ public:
const bool IgnoresTerrainCollision()const;
const float TimeSpentAlive()const;
const bool Immovable()const;
const bool IsSolid()const;
const bool Invulnerable()const;
//If an object has a lifetime set, returns it.
const std::optional<float>GetLifetime()const;
@ -256,6 +257,7 @@ private:
std::optional<float>lifetime{};
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!!
float solidFadeTimer{0.f};
private:
struct STRATEGY{
static std::string ERR;

@ -521,7 +521,9 @@ void Player::Update(float fElapsedTime){
item3.cooldown=0;
}
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()){
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()};
dist=line.length();
}
if(!m->Immovable()){
const float displacementDist=(playerRadius+monsterRadius)-dist;
if(!m->IsSolid()){
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->IsSolid()){
float knockbackStrength=1.f;
std::vector<Buff>knockbackBuffs=m->GetBuffs(COLLISION_KNOCKBACK_STRENGTH);
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));
}
}
}
}

@ -64,7 +64,7 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
m.F(A::RECOVERY_TIME)-=fElapsedTime;
if(m.F(A::RECOVERY_TIME)<=0.f){
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"),"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");
@ -75,6 +75,8 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f){
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){
m.phase=STANDARD;
}else{

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

@ -988,7 +988,7 @@ Monsters
CollisionDmg = 40
MoveSpd = 180%
MoveSpd = 50%
Size = 400%
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.
IDLE = 2, 0.6, Repeat
WALK = 4, 0.2, Repeat
TOSS ROCK = 4, 0.2, OneShot
CAST = 2, 0.3, Repeat
DEATH = 4, 0.15, OneShot
BURROW UNDERGROUND = 5, 0.15, OneShot
RISE FROM UNDERGROUND = 5, 0.15, OneShot
ROCK TOSS CAST = 2, 0.3, Repeat
STONE PILLAR CAST = 2, 0.3, Repeat
TOSS ROCK Cast = 2, 0.2, Repeat
SLAM = 3, 0.2, OneShot
TOSS ROCK = 4, 0.2, OneShot
}
Ignore Collisions = False
@ -1040,7 +1041,7 @@ Monsters
MoveSpd = 0%
# The Pillar is supposed to be 350 radius.
Size = 300%
Size = 600%
Collision Radius = 7
XP = 0

Loading…
Cancel
Save