Move damage numbers to be rendered on top of the HUD instead of below it. Add in boss indicators that appear while a boss is off-screen. Fix bugs with knockback buffs being applied in the wrong location, making them effectively useless. Fix bugs with player velocity being nan when standing directly on top of a monster spawn. Fix idle animation during stone elemental rock toss cast. Reduce enemy collision hitboxes to more sensible and playable numbers with new collision system. Release Build 9413.

This commit is contained in:
sigonasr2 2024-05-24 13:48:39 -05:00
parent 9e9f46f461
commit 2d2c123c33
24 changed files with 251 additions and 194 deletions

View File

@ -1911,6 +1911,160 @@ void AiL::RenderWorld(float fElapsedTime){
}
#pragma endregion
for(std::unique_ptr<Monster>&m:MONSTER_LIST){
m->strategyDrawOverlay(this,*m,MONSTER_DATA[m->GetName()].GetAIStrategy());
}
#ifdef _DEBUG
if(DEBUG_PATHFINDING){
std::vector<vf2d>pathing=game->pathfinder.Solve_AStar(player.get()->GetPos(),GetWorldMousePos(),8,player.get()->OnUpperLevel());
for(vf2d&square:pathing){
view.FillRectDecal(square*float(game->GetCurrentMapData().tilewidth),{float(game->GetCurrentMapData().tilewidth),float(game->GetCurrentMapData().tilewidth)},DARK_GREEN);
}
}
#endif
}
Player*const AiL::GetPlayer()const{
return player.get();
}
void AiL::RenderHud(){
if(!displayHud)return;
healthCounter.Update();
manaCounter.Update();
auto RenderAimingCursor=[&](){
if(Input::UsingGamepad()&&Input::AxesActive()){
vf2d aimingLocation=player->GetAimingLocation();
float analogStickDistance=geom2d::line<float>(GetScreenSize()/2,aimingLocation).length();
if(analogStickDistance>12.f){
float aimingDist=geom2d::line<float>(view.WorldToScreen(player->GetPos()),aimingLocation).length();
if(aimingDist>"Player.Aiming Cursor Max Distance"_F/100*24.f){
//Clamp the line to the max possible distance.
aimingLocation=geom2d::line<float>(view.WorldToScreen(player->GetPos()),aimingLocation).rpoint("Player.Aiming Cursor Max Distance"_F/100*24.f);
aimingDist=geom2d::line<float>(view.WorldToScreen(player->GetPos()),aimingLocation).length();
}
geom2d::line<float>aimingLine=geom2d::line<float>(view.WorldToScreen(player->GetPos()),aimingLocation);
DrawRotatedDecal(aimingLocation,GFX["aiming_target.png"].Decal(),0.f,GFX["aiming_target.png"].Sprite()->Size()/2,{1.0f,1.0f},{255,255,255,128});
vf2d lineScale=vf2d(GFX["aiming_line.png"].Sprite()->Size())*vf2d{aimingDist/GFX["aiming_line.png"].Sprite()->width,1};
DrawPartialRotatedDecal(view.WorldToScreen(player->GetPos()),GFX["aiming_line.png"].Decal(),aimingLine.vector().polar().y,{0,0},{0,0},lineScale,vf2d{aimingDist,1.f}/lineScale,{255,255,255,192});
}
}
};
#pragma region Boss Indicators
if(bossIndicatorPos.has_value()){
const vf2d&bossIndicator=bossIndicatorPos.value();
const bool BossIsOutsideView=!geom2d::overlaps(geom2d::rect<float>{view.GetWorldTL(),view.GetWorldVisibleArea()},bossIndicator);
if(BossIsOutsideView){
const bool flicker=sinf(GetRunTime())>0.5f&&sinf(GetRunTime())<0.55f;
#pragma region Side Indicators
const float yPos{std::clamp(view.WorldToScreen(bossIndicator).y,0.f,view.GetWorldVisibleArea().y)};
if(bossIndicator.x<view.GetWorldTL().x){ //Left-side indicator
DrawRotatedDecal({0,yPos},GFX["bossIndicator.png"].Decal(),0.f,{0,16},{8,1},flicker?RED:DARK_RED);
}else
if(bossIndicator.x>view.GetWorldBR().x){
DrawRotatedDecal({float(ScreenWidth()-8),yPos},GFX["bossIndicator.png"].Decal(),0.f,{0,16},{8,1},flicker?RED:DARK_RED);
}
#pragma endregion
#pragma region Top+Bottom Indicators
const float xPos{std::clamp(view.WorldToScreen(bossIndicator).x,0.f,view.GetWorldVisibleArea().x)};
if(bossIndicator.y<view.GetWorldTL().y){ //Left-side indicator
DrawRotatedDecal({xPos,4},GFX["bossIndicator.png"].Decal(),PI/2,{0.5f,16.f},{8,1},flicker?RED:DARK_RED);
}else
if(bossIndicator.y>view.GetWorldBR().y){
DrawRotatedDecal({xPos,float(ScreenHeight()-4)},GFX["bossIndicator.png"].Decal(),PI/2,{0.5f,16.f},{8,1},flicker?RED:DARK_RED);
}
#pragma endregion
}
}
#pragma endregion
minimap.Update();
minimap.Draw();
RenderAimingCursor();
ItemOverlay::Draw();
RenderCooldowns();
auto RenderCastbar=[&](const CastInfo&cast){
FillRectDecal(vf2d{ScreenWidth()/2-92.f,ScreenHeight()-90.f},{184,20},BLACK);
FillRectDecal(vf2d{ScreenWidth()/2-90.f,ScreenHeight()-88.f},{180,16},DARK_GREY);
float timer=cast.castTimer;
float totalTime=cast.castTotalTime;
std::string castText=cast.name;
GradientFillRectDecal(vf2d{ScreenWidth()/2-90.f,ScreenHeight()-88.f},{(timer/totalTime)*180,16},{247,125,37},{247,125,37},{247,184,37},{247,184,37});
std::stringstream castTimeDisplay;
castTimeDisplay<<std::fixed<<std::setprecision(1)<<timer;
DrawShadowStringPropDecal(vf2d{ScreenWidth()/2+90.f,ScreenHeight()-80.f}-vf2d{float(GetTextSizeProp(castTimeDisplay.str()).x),0},castTimeDisplay.str(),WHITE,BLACK);
DrawShadowStringPropDecal(vf2d{ScreenWidth()/2.f-GetTextSizeProp(castText).x*2/2,ScreenHeight()-64.f},castText,WHITE,BLACK,{2,3},std::numeric_limits<float>::max(),2.f);
};
if(GetPlayer()->GetCastInfo().castTimer>0){
RenderCastbar(GetPlayer()->GetCastInfo());
}else
if(GetPlayer()->GetEndZoneStandTime()>0){
RenderCastbar(CastInfo{"Exiting Level...",GetPlayer()->GetEndZoneStandTime(),"Player.End Zone Wait Time"_F});
}
Pixel healthOutlineCol=BLACK;
if(player->GetHealth()/player->GetMaxHealth()<="Player.Health Warning Pct"_F/100.f){
float runTimeAmt=fmod(GetRunTime(),"Player.Health Warning Flicker Time"_F*2);
if(runTimeAmt<"Player.Health Warning Flicker Time"_F){
healthOutlineCol="Player.Health Warning Outline Color"_Pixel;
}
Input::SetLightbar(healthOutlineCol);
}
DrawDecal({2,2},GFX["heart_outline.png"].Decal(),{1.f,1.f},healthOutlineCol);
DrawDecal({2,2},GFX["heart.png"].Decal());
DrawDecal({2,20},GFX["mana.png"].Decal());
std::string text=player->GetHealth()>0?std::to_string(healthCounter.GetDisplayValue()):"X";
std::string text_mana=std::to_string(manaCounter.GetDisplayValue());
DrawShadowStringPropDecal({20,3},text,healthCounter.GetDisplayColor(),healthOutlineCol,{2,2},INFINITE);
DrawShadowStringPropDecal({24,23},text_mana,manaCounter.GetDisplayColor(),BLACK,{1.5f,1.5f},INFINITE);
#pragma region Show Max Health/Max Mana
if(GameSettings::ShowMaxHealth()){
vf2d healthTextSize=GetTextSizeProp(text)*vf2d{2.f,2.f};
std::string maxHealthText="/"+std::to_string(int(player->GetMaxHealth()));
float maxHealthTextHeight=GetTextSizeProp(maxHealthText).y;
DrawShadowStringPropDecal(vf2d{20,3}+healthTextSize+vf2d{1.f,-maxHealthTextHeight},maxHealthText,{200,200,200,255},healthOutlineCol,{1.f,1.f},INFINITE);
}
if(GameSettings::ShowMaxMana()){
vf2d manaTextSize=GetTextSizeProp(text_mana)*vf2d{1.5f,1.5f};
std::string maxManaText="/"+std::to_string(player->GetMaxMana());
float maxManaTextHeight=GetTextSizeProp(maxManaText).y;
DrawShadowStringPropDecal(vf2d{24,23}+manaTextSize+vf2d{1.f,-maxManaTextHeight},maxManaText,{200,200,255,255},BLACK,{1.f,1.f},INFINITE);
}
#pragma endregion
if(player->notEnoughManaDisplay.second>0){
std::string displayText="Not enough mana for "+player->notEnoughManaDisplay.first+"!";
DrawShadowStringPropDecal(vf2d{float(ScreenWidth()/2),float(ScreenHeight()/4)}-GetTextSizeProp(displayText)/2,displayText,DARK_RED,VERY_DARK_RED);
}
if(player->notificationDisplay.second>0){
std::string displayText=player->notificationDisplay.first;
DrawShadowStringPropDecal(vf2d{float(ScreenWidth()/2),float(ScreenHeight()/4)-24}-GetTextSizeProp(displayText)/2,displayText,BLUE,VERY_DARK_BLUE);
}
DisplayBossEncounterInfo();
for(std::vector<std::shared_ptr<DamageNumber>>::iterator it=DAMAGENUMBER_LIST.begin();it!=DAMAGENUMBER_LIST.end();++it){
DamageNumber*dn=(*it).get();
@ -1963,127 +2117,6 @@ void AiL::RenderWorld(float fElapsedTime){
}
}
for(std::unique_ptr<Monster>&m:MONSTER_LIST){
m->strategyDrawOverlay(this,*m,MONSTER_DATA[m->GetName()].GetAIStrategy());
}
#ifdef _DEBUG
if(DEBUG_PATHFINDING){
std::vector<vf2d>pathing=game->pathfinder.Solve_AStar(player.get()->GetPos(),GetWorldMousePos(),8,player.get()->OnUpperLevel());
for(vf2d&square:pathing){
view.FillRectDecal(square*float(game->GetCurrentMapData().tilewidth),{float(game->GetCurrentMapData().tilewidth),float(game->GetCurrentMapData().tilewidth)},DARK_GREEN);
}
}
#endif
}
Player*const AiL::GetPlayer()const{
return player.get();
}
void AiL::RenderHud(){
if(!displayHud)return;
healthCounter.Update();
manaCounter.Update();
auto RenderAimingCursor=[&](){
if(Input::UsingGamepad()&&Input::AxesActive()){
vf2d aimingLocation=player->GetAimingLocation();
float analogStickDistance=geom2d::line<float>(GetScreenSize()/2,aimingLocation).length();
if(analogStickDistance>12.f){
float aimingDist=geom2d::line<float>(view.WorldToScreen(player->GetPos()),aimingLocation).length();
if(aimingDist>"Player.Aiming Cursor Max Distance"_F/100*24.f){
//Clamp the line to the max possible distance.
aimingLocation=geom2d::line<float>(view.WorldToScreen(player->GetPos()),aimingLocation).rpoint("Player.Aiming Cursor Max Distance"_F/100*24.f);
aimingDist=geom2d::line<float>(view.WorldToScreen(player->GetPos()),aimingLocation).length();
}
geom2d::line<float>aimingLine=geom2d::line<float>(view.WorldToScreen(player->GetPos()),aimingLocation);
DrawRotatedDecal(aimingLocation,GFX["aiming_target.png"].Decal(),0.f,GFX["aiming_target.png"].Sprite()->Size()/2,{1.0f,1.0f},{255,255,255,128});
vf2d lineScale=vf2d(GFX["aiming_line.png"].Sprite()->Size())*vf2d{aimingDist/GFX["aiming_line.png"].Sprite()->width,1};
DrawPartialRotatedDecal(view.WorldToScreen(player->GetPos()),GFX["aiming_line.png"].Decal(),aimingLine.vector().polar().y,{0,0},{0,0},lineScale,vf2d{aimingDist,1.f}/lineScale,{255,255,255,192});
}
}
};
minimap.Update();
minimap.Draw();
RenderAimingCursor();
ItemOverlay::Draw();
RenderCooldowns();
auto RenderCastbar=[&](const CastInfo&cast){
FillRectDecal(vf2d{ScreenWidth()/2-92.f,ScreenHeight()-90.f},{184,20},BLACK);
FillRectDecal(vf2d{ScreenWidth()/2-90.f,ScreenHeight()-88.f},{180,16},DARK_GREY);
float timer=cast.castTimer;
float totalTime=cast.castTotalTime;
std::string castText=cast.name;
GradientFillRectDecal(vf2d{ScreenWidth()/2-90.f,ScreenHeight()-88.f},{(timer/totalTime)*180,16},{247,125,37},{247,125,37},{247,184,37},{247,184,37});
std::stringstream castTimeDisplay;
castTimeDisplay<<std::fixed<<std::setprecision(1)<<timer;
DrawShadowStringPropDecal(vf2d{ScreenWidth()/2+90.f,ScreenHeight()-80.f}-vf2d{float(GetTextSizeProp(castTimeDisplay.str()).x),0},castTimeDisplay.str(),WHITE,BLACK);
DrawShadowStringPropDecal(vf2d{ScreenWidth()/2.f-GetTextSizeProp(castText).x*2/2,ScreenHeight()-64.f},castText,WHITE,BLACK,{2,3},std::numeric_limits<float>::max(),2.f);
};
if(GetPlayer()->GetCastInfo().castTimer>0){
RenderCastbar(GetPlayer()->GetCastInfo());
}else
if(GetPlayer()->GetEndZoneStandTime()>0){
RenderCastbar(CastInfo{"Exiting Level...",GetPlayer()->GetEndZoneStandTime(),"Player.End Zone Wait Time"_F});
}
Pixel healthOutlineCol=BLACK;
if(player->GetHealth()/player->GetMaxHealth()<="Player.Health Warning Pct"_F/100.f){
float runTimeAmt=fmod(GetRuntime(),"Player.Health Warning Flicker Time"_F*2);
if(runTimeAmt<"Player.Health Warning Flicker Time"_F){
healthOutlineCol="Player.Health Warning Outline Color"_Pixel;
}
Input::SetLightbar(healthOutlineCol);
}
DrawDecal({2,2},GFX["heart_outline.png"].Decal(),{1.f,1.f},healthOutlineCol);
DrawDecal({2,2},GFX["heart.png"].Decal());
DrawDecal({2,20},GFX["mana.png"].Decal());
std::string text=player->GetHealth()>0?std::to_string(healthCounter.GetDisplayValue()):"X";
std::string text_mana=std::to_string(manaCounter.GetDisplayValue());
DrawShadowStringPropDecal({20,3},text,healthCounter.GetDisplayColor(),healthOutlineCol,{2,2},INFINITE);
DrawShadowStringPropDecal({24,23},text_mana,manaCounter.GetDisplayColor(),BLACK,{1.5f,1.5f},INFINITE);
#pragma region Show Max Health/Max Mana
if(GameSettings::ShowMaxHealth()){
vf2d healthTextSize=GetTextSizeProp(text)*vf2d{2.f,2.f};
std::string maxHealthText="/"+std::to_string(int(player->GetMaxHealth()));
float maxHealthTextHeight=GetTextSizeProp(maxHealthText).y;
DrawShadowStringPropDecal(vf2d{20,3}+healthTextSize+vf2d{1.f,-maxHealthTextHeight},maxHealthText,{200,200,200,255},healthOutlineCol,{1.f,1.f},INFINITE);
}
if(GameSettings::ShowMaxMana()){
vf2d manaTextSize=GetTextSizeProp(text_mana)*vf2d{1.5f,1.5f};
std::string maxManaText="/"+std::to_string(player->GetMaxMana());
float maxManaTextHeight=GetTextSizeProp(maxManaText).y;
DrawShadowStringPropDecal(vf2d{24,23}+manaTextSize+vf2d{1.f,-maxManaTextHeight},maxManaText,{200,200,255,255},BLACK,{1.f,1.f},INFINITE);
}
#pragma endregion
if(player->notEnoughManaDisplay.second>0){
std::string displayText="Not enough mana for "+player->notEnoughManaDisplay.first+"!";
DrawShadowStringPropDecal(vf2d{float(ScreenWidth()/2),float(ScreenHeight()/4)}-GetTextSizeProp(displayText)/2,displayText,DARK_RED,VERY_DARK_RED);
}
if(player->notificationDisplay.second>0){
std::string displayText=player->notificationDisplay.first;
DrawShadowStringPropDecal(vf2d{float(ScreenWidth()/2),float(ScreenHeight()/4)-24}-GetTextSizeProp(displayText)/2,displayText,BLUE,VERY_DARK_BLUE);
}
DisplayBossEncounterInfo();
#ifdef _DEBUG
if("debug_player_info"_I){
DrawShadowStringDecal({0,128},player->GetPos().str());
@ -2390,6 +2423,7 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
}
#pragma endregion
bossIndicatorPos.reset();
SPAWNER_LIST.clear();
SPAWNER_CONTROLLER={};
foregroundTileGroups.clear();
@ -3775,7 +3809,7 @@ void AiL::ValidateGameStatus(){
void AiL::RenderVersionInfo(){
saveGameDisplayTime=std::max(0.f,saveGameDisplayTime-game->GetElapsedTime());
if(saveGameDisplayTime>3.f){
DrawShadowStringDecal({4.f,4.f},"Saving Game...",{255,255,255,uint8_t((sin(game->GetRuntime())+1)*127)},{0,0,0,uint8_t((sin(game->GetRuntime())+1)*127)});
DrawShadowStringDecal({4.f,4.f},"Saving Game...",{255,255,255,uint8_t((sin(game->GetRunTime())+1)*127)},{0,0,0,uint8_t((sin(game->GetRunTime())+1)*127)});
}else
if(saveGameDisplayTime>0.f){
uint8_t alpha=uint8_t(util::lerp(0,255,saveGameDisplayTime/3.f));
@ -4252,6 +4286,8 @@ rcode AiL::LoadResource(Renderable&renderable,std::string_view imgPath,bool filt
void AiL::UpdateMonsters(){
for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(m->HasArrowIndicator())bossIndicatorPos=m->GetPos();
if(m->markedForDeletion){
AMonsterIsMarkedForDeletion();
continue;
@ -4289,6 +4325,8 @@ void AiL::ShowDamageVignetteOverlay(){
}
void AiL::GlobalGameUpdates(){
bossIndicatorPos={};
levelTime+=GetElapsedTime();
SteamAPI_RunCallbacks();
STEAMINPUT(
@ -4389,3 +4427,7 @@ void AiL::UpdateEntities(){
void AiL::AMonsterIsMarkedForDeletion(){
aMonsterIsMarkedForDeletion=true;
}
void AiL::SetBossIndicatorPos(const vf2d pos){
bossIndicatorPos=pos;
}

View File

@ -206,6 +206,7 @@ private:
Audio audioEngine;
SteamKeyboardCallbackHandler*steamKeyboardCallbackListener=nullptr;
SteamStatsReceivedHandler*steamStatsReceivedHandlerListener=nullptr;
std::optional<vf2d>bossIndicatorPos{};
public:
AiL();
bool OnUserCreate() override;
@ -345,6 +346,7 @@ public:
void UpdateEntities();
Minimap minimap;
void AMonsterIsMarkedForDeletion(); //The way this is implemented is that monsters marked for deletion will cause the monster update loop to detect there's at least one or more monsters that must be deleted and will call erase_if on the list at the end of the iteration loop.
void SetBossIndicatorPos(const vf2d pos);
struct TileGroupData{
vi2d tilePos;

View File

@ -1015,7 +1015,7 @@ const std::string Stats::GetStatsString(const Stats&maxStats,CompactText compact
std::string col="";
if(maxStats.attributes.count(attr)&&int(val)>=int(maxStats.attributes.at(attr))){
Pixel shimmeringCol=PixelLerp({255,196,60},{254,217,133},sin((70*game->GetRuntime())/2.f+0.5f));
Pixel shimmeringCol=PixelLerp({255,196,60},{254,217,133},sin((70*game->GetRunTime())/2.f+0.5f));
col=util::PixelToHTMLColorCode(shimmeringCol);
}

View File

@ -79,7 +79,7 @@ void LevitatingRock::Update(float fElapsedTime){
if(!RocksHaveLaunched){
deactivated=true;
drawOffsetY=cos(PI*game->GetRuntime())*3.f;
drawOffsetY=cos(PI*game->GetRunTime())*3.f;
}
else if(targetVel.has_value()){
vel=targetVel.value();

View File

@ -90,7 +90,7 @@ void LoadingScreen::Draw(){
}
game->GetPlayer()->GetWalkEAnimation();
Animate2D::FrameSequence&playerWalkE=ANIMATION_DATA[game->GetPlayer()->GetWalkEAnimation()];
game->DrawPartialRotatedDecal({(float(currentProgress)/totalProgress)*(WINDOW_SIZE.x-48.f),WINDOW_SIZE.y-36.f},playerWalkE.GetFrame(game->GetRuntime()).GetSourceImage()->Decal(),game->GetPlayer()->GetSpinAngle(),{12,12},playerWalkE.GetFrame(game->GetRuntime()).GetSourceRect().pos,playerWalkE.GetFrame(game->GetRuntime()).GetSourceRect().size,playerScale*scale,blendCol);
game->DrawPartialRotatedDecal({(float(currentProgress)/totalProgress)*(WINDOW_SIZE.x-48.f),WINDOW_SIZE.y-36.f},playerWalkE.GetFrame(game->GetRunTime()).GetSourceImage()->Decal(),game->GetPlayer()->GetSpinAngle(),{12,12},playerWalkE.GetFrame(game->GetRunTime()).GetSourceRect().pos,playerWalkE.GetFrame(game->GetRunTime()).GetSourceRect().size,playerScale*scale,blendCol);
}
}

View File

@ -271,6 +271,8 @@ bool Monster::Update(float fElapsedTime){
lastFacingDirectionChange+=fElapsedTime;
timeSpentAlive+=fElapsedTime;
if(HasArrowIndicator())game->SetBossIndicatorPos(GetPos());
#pragma region Handle Monster Lifetime and fade timer.
if(fadeTimer>0.f){
fadeTimer=std::max(0.f,fadeTimer-fElapsedTime);
@ -338,9 +340,18 @@ bool Monster::Update(float fElapsedTime){
m->Collision(*this);
geom2d::line line(pos,m->GetPos());
float dist = line.length();
while(dist<=0.001){
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()){
vel=line.vector().norm()*-128;
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);
}
}
}
@ -502,33 +513,11 @@ void Monster::Collision(Player*p){
}
}
#pragma region Knockback due to buffs
vf2d knockbackVecNorm=geom2d::line<float>(GetPos(),p->GetPos()).vector().norm();
float knockbackStrength=0.f;
std::vector<Buff> knockbackBuffs=GetBuffs(COLLISION_KNOCKBACK_STRENGTH);
for(Buff&b:knockbackBuffs){
knockbackStrength+=b.intensity;
}
p->Knockback(knockbackVecNorm*knockbackStrength);
#pragma endregion
B(Attribute::COLLIDED_WITH_PLAYER)=true;
Collision();
}
void Monster::Collision(Monster&m){
#pragma region Knockback due to buffs
vf2d knockbackVecNorm=geom2d::line<float>(GetPos(),m.GetPos()).vector().norm();
float knockbackStrength=0.f;
std::vector<Buff> knockbackBuffs=GetBuffs(COLLISION_KNOCKBACK_STRENGTH);
for(Buff&b:knockbackBuffs){
knockbackStrength+=b.intensity;
}
m.Knockback(knockbackVecNorm*knockbackStrength);
#pragma endregion
Collision();
}
void Monster::Collision(){
@ -892,7 +881,10 @@ geom2d::circle<float>Monster::BulletCollisionHitbox(){
void Monster::Knockback(const vf2d&vel){
//A new angle will be applied, but will be constrained by whichever applied velocity is strongest (either the current velocity, or the new one). This prevents continuous uncapped velocities to knockbacks applied.
float maxVelThreshold=std::max(vel.mag(),this->vel.mag());
if(vel==vf2d{})return;
float maxVelThreshold;
if(this->vel==vf2d{})maxVelThreshold=vel.mag();
else maxVelThreshold=std::max(vel.mag(),this->vel.mag());
this->vel+=vel;
float newVelAngle=this->vel.polar().y;
this->vel=vf2d{maxVelThreshold,newVelAngle}.cart();

View File

@ -437,7 +437,7 @@ const std::optional<float>MonsterData::GetLifetime()const{
return lifetime;
}
const float MonsterData::GetCollisionRadius()const{
return collisionRadius;
return collisionRadius*0.6f;
}
const bool MonsterData::HasArrowIndicator()const{
return hasArrowIndicator;

View File

@ -306,7 +306,10 @@ State::State Player::GetState(){
void Player::Knockback(vf2d vel){
//A new angle will be applied, but will be constrained by whichever applied velocity is strongest (either the current velocity, or the new one). This prevents continuous uncapped velocities to knockbacks applied.
float maxVelThreshold=std::max(vel.mag(),this->vel.mag());
if(vel==vf2d{})return;
float maxVelThreshold;
if(this->vel==vf2d{})maxVelThreshold=vel.mag();
else maxVelThreshold=std::max(vel.mag(),this->vel.mag());
this->vel+=vel;
float newVelAngle=this->vel.polar().y;
this->vel=vf2d{maxVelThreshold,newVelAngle}.cart();
@ -522,18 +525,27 @@ void Player::Update(float fElapsedTime){
}
geom2d::line line(pos,m->GetPos());
float dist = line.length();
while(dist<=0.001){
line={pos+vf2d{util::random(0.2f)-0.1f,util::random(0.2f)-0.1f},m->GetPos()};
dist=line.length();
}
if(!m->Immovable()){
if(dist<=0.001){
m->SetPos(m->GetPos()+vf2d{util::random(2)-1,util::random(2)-1});
}else{
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.
Knockback(line.vector().norm()*-128.f);
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.f*knockbackStrength);
}
}
}
if(!std::isfinite(vel.x)||!std::isfinite(vel.y)){
ERR(std::format("WARNING! The velocity vector for the player is NOT normal! Current vel:{} . Attempting manual resetting of velocity.",vel.str()));
vel={};
}
if(vel.x>0){
vel.x=std::max(0.f,vel.x-friction*fElapsedTime);
} else {

View File

@ -134,7 +134,7 @@ const void SaveFile::SaveGame(){
saveFile["Overworld Map Location"].SetString(State_OverworldMap::GetCurrentConnectionPoint().name);
saveFile["Chapter"].SetInt(game->GetCurrentChapter());
saveFile["Save Name"].SetString(std::string(GetSaveFileName()));
saveFile["Game Time"].SetReal(game->GetRuntime());
saveFile["Game Time"].SetReal(game->GetRunTime());
saveFile["TravelingMerchant"].SetString(std::string(Merchant::GetCurrentTravelingMerchant().GetKeyName()));
saveFile["Minimap Display Mode"].SetInt(int(game->minimap.GetMinimapMode()));
@ -216,7 +216,7 @@ const void SaveFile::SaveGame(){
utils::datafile::Read(metadata,"save_file_path"_S+"metadata.dat");
}
}
metadata.GetProperty(std::format("save{}",saveFileID)).SetReal(game->GetRuntime(),0U);
metadata.GetProperty(std::format("save{}",saveFileID)).SetReal(game->GetRunTime(),0U);
metadata.GetProperty(std::format("save{}",saveFileID)).SetInt(game->GetCurrentChapter(),1U);
metadata.GetProperty(std::format("save{}",saveFileID)).SetInt(game->GetPlayer()->Level(),2U);
metadata.GetProperty(std::format("save{}",saveFileID)).SetString(game->GetPlayer()->GetClassName(),3U);

View File

@ -84,7 +84,7 @@ void State_Death::OnUserUpdate(AiL*game){
}
Input::StopVibration();
game->SetMosaicEffect(uint8_t(util::lerp(9.f,1.f,(gameSlowdownPct-7)/3.f)));
Input::SetLightbar(PixelLerp(BLACK,DARK_RED,sin(1.5f*game->GetRuntime())/2.f+0.5f));
Input::SetLightbar(PixelLerp(BLACK,DARK_RED,sin(1.5f*game->GetRunTime())/2.f+0.5f));
}
if(gameSlowdownPct<10.f){

View File

@ -139,8 +139,8 @@ void State_LevelComplete::DrawOverlay(AiL*game){
game->DrawRotatedDecal(levelUpTextPos+vf2d{2.f,1.f},GFX["overworld_arrow.png"].Decal(),-PI/2,GFX["overworld_arrow.png"].Sprite()->Size(),{1.f,1.f},BLACK);
game->DrawRotatedDecal(levelUpTextPos+vf2d{2.f,0.f},GFX["overworld_arrow.png"].Decal(),-PI/2,GFX["overworld_arrow.png"].Sprite()->Size(),{1.f,1.f},YELLOW);
game->DrawShadowStringPropDecal(levelUpTextPos+vf2d{-69.f,4.f},std::format("HP +{}",int(game->GetPlayer()->GetHealthGrowthRate())),PixelLerp({226,234,244},WHITE,sin((40*game->GetRuntime())/2.f+0.5f)));
game->DrawShadowStringPropDecal(levelUpTextPos+vf2d{-69.f,12.f},std::format("ATK+{}",int(game->GetPlayer()->GetAtkGrowthRate())),PixelLerp({226,234,244},WHITE,sin((40*game->GetRuntime())/2.f+0.5f)));
game->DrawShadowStringPropDecal(levelUpTextPos+vf2d{-69.f,4.f},std::format("HP +{}",int(game->GetPlayer()->GetHealthGrowthRate())),PixelLerp({226,234,244},WHITE,sin((40*game->GetRunTime())/2.f+0.5f)));
game->DrawShadowStringPropDecal(levelUpTextPos+vf2d{-69.f,12.f},std::format("ATK+{}",int(game->GetPlayer()->GetAtkGrowthRate())),PixelLerp({226,234,244},WHITE,sin((40*game->GetRunTime())/2.f+0.5f)));
}
void State_LevelComplete::TurnOffXPSound(){

View File

@ -163,7 +163,7 @@ void State_OverworldMap::Draw(AiL*game){
if(!Unlock::IsUnlocked(cp)){
game->view.FillRectDecal(cp.rect.pos,cp.rect.size,{0,0,0,128});
}else{
float exclamationScale=fmod(game->GetRuntime(),1.0f)<0.2f?1.f:1.2f;
float exclamationScale=fmod(game->GetRunTime(),1.0f)<0.2f?1.f:1.2f;
if(!cp.Visited()){
game->view.DrawDecal(cp.rect.pos+vf2d{-1.f,0.f},GFX["exclamation.png"].Decal(),{exclamationScale,exclamationScale},BLACK);
game->view.DrawDecal(cp.rect.pos+vf2d{-1.f,-1.f},GFX["exclamation.png"].Decal(),{exclamationScale,exclamationScale});
@ -209,7 +209,7 @@ void State_OverworldMap::Draw(AiL*game){
direction++;
}
float arrowDist=fmod(game->GetRuntime(),1.0f)<0.5f?14:18;
float arrowDist=fmod(game->GetRunTime(),1.0f)<0.5f?14:18;
for(auto&[index,medianAngle]:neighbors){
game->view.DrawRotatedDecal(game->GetPlayer()->GetPos()+vf2d{arrowDist,GetAngle(medianAngle)}.cart()+vf2d{0.f,1.f},GFX["overworld_arrow.png"].Decal(),GetAngle(medianAngle),GFX["overworld_arrow.png"].Sprite()->Size()/2,{1.f,1.f},{0,0,0});

View File

@ -96,8 +96,8 @@ void Monster::STRATEGY::STONE_ELEMENTAL(Monster&m,float fElapsedTime,std::string
m.phase=STONE_PILLAR_CAST;
m.F(A::CASTING_TIMER)=ConfigFloat("Stone Pillar Cast Time");
m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos();
game->AddEffect(std::make_unique<Effect>(m.V(A::LOCKON_POS),ConfigFloat("Stone Pillar Cast Time"),"range_indicator.png",m.OnUpperLevel(),vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Pillar").GetSizeMult()/12.f)*1.1f,0.3f,vf2d{},ConfigPixel("Stone Pillar Spell Circle Color"),util::random(2*PI),util::degToRad(ConfigFloat("Stone Pillar Spell Circle Rotation Spd"))),true);
game->AddEffect(std::make_unique<Effect>(m.V(A::LOCKON_POS),ConfigFloat("Stone Pillar Cast Time"),"spell_insignia.png",m.OnUpperLevel(),vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Pillar").GetSizeMult()/12.f)*0.75f,0.3f,vf2d{},ConfigPixel("Stone Pillar Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Stone Pillar Spell Insignia Rotation Spd"))),true);
game->AddEffect(std::make_unique<Effect>(m.V(A::LOCKON_POS),ConfigFloat("Stone Pillar Cast Time"),"range_indicator.png",m.OnUpperLevel(),vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Pillar").GetSizeMult()/12.f)*1.25f,0.3f,vf2d{},ConfigPixel("Stone Pillar Spell Circle Color"),util::random(2*PI),util::degToRad(ConfigFloat("Stone Pillar Spell Circle Rotation Spd"))),true);
game->AddEffect(std::make_unique<Effect>(m.V(A::LOCKON_POS),ConfigFloat("Stone Pillar Cast Time"),"spell_insignia.png",m.OnUpperLevel(),vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Pillar").GetSizeMult()/12.f)*0.9f,0.3f,vf2d{},ConfigPixel("Stone Pillar Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Stone Pillar Spell Insignia Rotation Spd"))),true);
}break;
case 1:{
m.PerformAnimation("ROCK TOSS CAST");
@ -142,7 +142,7 @@ void Monster::STRATEGY::STONE_ELEMENTAL(Monster&m,float fElapsedTime,std::string
case SHOOT_STONE_CAST:{
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)>=ConfigFloat("Rock Toss Wait Time")-ConfigFloat("Rock Toss Track Time")){
m.PerformIdleAnimation();
m.PerformAnimation("STONE PILLAR CAST");
m.UpdateFacingDirection(game->GetPlayer()->GetPos());
}
if(m.F(A::CASTING_TIMER)<=0.f){

View File

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1
#define VERSION_MINOR 2
#define VERSION_PATCH 0
#define VERSION_BUILD 9382
#define VERSION_BUILD 9413
#define stringify(a) stringify_(a)
#define stringify_(a) #a

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="238" height="369" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="53">
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="238" height="369" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="55">
<properties>
<property name="Backdrop" propertytype="Backdrop" value="mountain_day"/>
<property name="Background Music" propertytype="BGM" value="foresty1_1"/>
@ -1897,5 +1897,13 @@
<property name="spawner" type="object" value="15"/>
</properties>
</object>
<object id="53" name="Spawn Group 2" type="SpawnGroup" x="3948" y="7830" width="496" height="424">
<ellipse/>
</object>
<object id="54" template="../maps/Monsters/Boar.tx" x="4080" y="7962">
<properties>
<property name="spawner" type="object" value="53"/>
</properties>
</object>
</objectgroup>
</map>

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

View File

@ -550,7 +550,7 @@ MonsterStrategy
Backpedal Movespeed = 50%
Charge Knockback Amount = 140
Charge Knockback Amount = 1.4
}
Goblin Dagger
{

View File

@ -792,8 +792,8 @@ Monsters
MoveSpd = 0%
# The Pillar is supposed to be 350 radius.
Size = 350%
Collision Radius = 8
Size = 300%
Collision Radius = 7
Lifetime = 5s
XP = 0

View File

@ -96,6 +96,7 @@ Images
GFX_SpellInsignia = spell_insignia.png
GFX_Rock = rock.png
GFX_RockOutline = rock_outline.png
GFX_BossIndicator = bossIndicator.png
# Ability Icons
GFX_Warrior_BattleCry_Icon = Ability Icons/battlecry.png

View File

@ -658,7 +658,7 @@ void olc::ViewPort::DrawStringDecal(const olc::vf2d& pos, std::string_view sText
pge->SetDrawTarget(nullptr);
newDecal->Update();
}
pge->garbageCollector[key].expireTime=pge->GetRuntime()+120.0f;
pge->garbageCollector[key].expireTime=pge->GetRunTime()+120.0f;
DrawDecal(pos,pge->garbageCollector[key].decal,scale,col);
}
@ -673,7 +673,7 @@ void olc::ViewPort::DrawStringDecal(Font&font, const olc::vf2d& pos, const std::
pge->garbageCollector[key].decal=font.RenderStringToDecal(sText,WHITE);
pge->garbageCollector[key].originalStr=std::string(sText.begin(),sText.end());
}
pge->garbageCollector[key].expireTime=pge->GetRuntime()+120.0f;
pge->garbageCollector[key].expireTime=pge->GetRunTime()+120.0f;
DrawDecal(pos,pge->garbageCollector[key].decal,scale/4,col);
}
@ -701,7 +701,7 @@ void olc::ViewPort::DrawStringPropDecal(const olc::vf2d& pos, std::string_view s
pge->SetDrawTarget(nullptr);
newDecal->Update();
}
pge->garbageCollector[key].expireTime=pge->GetRuntime()+120.0f;
pge->garbageCollector[key].expireTime=pge->GetRunTime()+120.0f;
DrawDecal(pos,pge->garbageCollector[key].decal,scale,col);
}
@ -747,8 +747,8 @@ void olc::ViewPort::DrawShadowStringDecal(const olc::vf2d& pos, std::string_view
pge->SetDrawTarget(nullptr);
newShadowDecal->Update();
}
pge->garbageCollector[key].expireTime=pge->GetRuntime()+120.0f;
pge->garbageCollector[key+"_SHADOW"].expireTime=pge->GetRuntime()+120.0f;
pge->garbageCollector[key].expireTime=pge->GetRunTime()+120.0f;
pge->garbageCollector[key+"_SHADOW"].expireTime=pge->GetRunTime()+120.0f;
DrawDecal(pos-vf2d{shadowSizeFactor,shadowSizeFactor},pge->garbageCollector[key+"_SHADOW"].decal,scale/4,shadowCol);
DrawDecal(pos,pge->garbageCollector[key].decal,scale,col);
}
@ -795,8 +795,8 @@ void olc::ViewPort::DrawShadowStringPropDecal(const olc::vf2d& pos, std::string_
pge->SetDrawTarget(nullptr);
newShadowDecal->Update();
}
pge->garbageCollector[key].expireTime=pge->GetRuntime()+120.0f;
pge->garbageCollector[key+"_SHADOW"].expireTime=pge->GetRuntime()+120.0f;
pge->garbageCollector[key].expireTime=pge->GetRunTime()+120.0f;
pge->garbageCollector[key+"_SHADOW"].expireTime=pge->GetRunTime()+120.0f;
DrawDecal(pos-vf2d{shadowSizeFactor,shadowSizeFactor},pge->garbageCollector[key+"_SHADOW"].decal,scale/4,shadowCol);
DrawDecal(pos,pge->garbageCollector[key].decal,scale,col);
}
@ -812,9 +812,9 @@ void olc::ViewPort::DrawShadowStringDecal(Font&font, const olc::vf2d& pos, const
pge->garbageCollector[key].decal=font.RenderStringToDecal(sText,WHITE);
pge->garbageCollector[key].originalStr=std::string(sText.begin(),sText.end());
}
pge->garbageCollector[key].expireTime=pge->GetRuntime()+120.0f;
pge->garbageCollector[key].expireTime=pge->GetRunTime()+120.0f;
std::erase_if(pge->garbageCollector,[&](auto&key){
if(key.second.expireTime<pge->GetRuntime()){
if(key.second.expireTime<pge->GetRunTime()){
delete key.second.decal;
return true;
}
@ -841,7 +841,7 @@ void olc::ViewPort::DrawDropShadowStringDecal(Font&font, const olc::vf2d& pos, c
pge->garbageCollector[key].decal=font.RenderStringToDecal(sText,WHITE);
pge->garbageCollector[key].originalStr=std::string(sText.begin(),sText.end());
}
pge->garbageCollector[key].expireTime=pge->GetRuntime()+120.0f;
pge->garbageCollector[key].expireTime=pge->GetRunTime()+120.0f;
DrawDecal(pos+vf2d{0,0.5f},pge->garbageCollector[key].decal,scale/4,shadowCol);
DrawDecal(pos+vf2d{0.5f,0},pge->garbageCollector[key].decal,scale/4,shadowCol);
DrawDecal(pos+vf2d{0.5f,0.5f},pge->garbageCollector[key].decal,scale/4,shadowCol);

View File

@ -1066,7 +1066,7 @@ namespace olc
// Specify which Sprite should be the target of drawing functions, use nullptr
// to specify the primary screen
void SetDrawTarget(Sprite* target);
double GetRuntime() const;
double GetRunTime() const;
void SetRuntime(const double runTime);
// Gets the current Frames Per Second
uint32_t GetFPS() const;
@ -2257,7 +2257,7 @@ namespace olc
return 0;
}
double PixelGameEngine::GetRuntime() const
double PixelGameEngine::GetRunTime() const
{ return dRunTime; }
void PixelGameEngine::SetRuntime(const double runTime){
@ -2271,7 +2271,7 @@ namespace olc
}
void PixelGameEngine::ClearTimedOutGarbage(){
std::erase_if(garbageCollector,[&](auto&key){
if(key.second.expireTime<GetRuntime()){
if(key.second.expireTime<GetRunTime()){
delete key.second.decal;
return true;
}
@ -3600,7 +3600,7 @@ namespace olc
SetDrawTarget(nullptr);
newDecal->Update();
}
garbageCollector[key].expireTime=GetRuntime()+120.0f;
garbageCollector[key].expireTime=GetRunTime()+120.0f;
DrawDecal(pos,garbageCollector[key].decal,scale,col);
}
@ -3629,7 +3629,7 @@ namespace olc
SetDrawTarget(nullptr);
newDecal->Update();
}
garbageCollector[key].expireTime=GetRuntime()+120.0f;
garbageCollector[key].expireTime=GetRunTime()+120.0f;
DrawDecal(pos,garbageCollector[key].decal,scale,col);
}
@ -3675,8 +3675,8 @@ namespace olc
SetDrawTarget(nullptr);
newShadowDecal->Update();
}
garbageCollector[key].expireTime=GetRuntime()+120.0f;
garbageCollector[key+"_SHADOW"].expireTime=GetRuntime()+120.0f;
garbageCollector[key].expireTime=GetRunTime()+120.0f;
garbageCollector[key+"_SHADOW"].expireTime=GetRunTime()+120.0f;
DrawDecal(pos-vf2d{shadowSizeFactor,shadowSizeFactor},garbageCollector[key+"_SHADOW"].decal,scale/4,shadowCol);
DrawDecal(pos,garbageCollector[key].decal,scale,col);
}
@ -3692,7 +3692,7 @@ namespace olc
garbageCollector[key].decal=font.RenderStringToDecal(sText,WHITE);
garbageCollector[key].originalStr=std::string(sText.begin(),sText.end());
}
garbageCollector[key].expireTime=GetRuntime()+120.0f;
garbageCollector[key].expireTime=GetRunTime()+120.0f;
DrawDecal(pos,garbageCollector[key].decal,scale/4,col);
}
@ -3707,7 +3707,7 @@ namespace olc
garbageCollector[key].decal=font.RenderStringToDecal(sText,WHITE);
garbageCollector[key].originalStr=std::string(sText.begin(),sText.end());
}
garbageCollector[key].expireTime=GetRuntime()+120.0f;
garbageCollector[key].expireTime=GetRunTime()+120.0f;
for(float y=-shadowSizeFactor;y<=shadowSizeFactor+0.1;y+=shadowSizeFactor/2){
for(float x=-shadowSizeFactor;x<=shadowSizeFactor+0.1;x+=shadowSizeFactor/2){
if(x!=0||y!=0){
@ -3729,7 +3729,7 @@ namespace olc
garbageCollector[key].decal=font.RenderStringToDecal(sText,WHITE);
garbageCollector[key].originalStr=std::string(sText.begin(),sText.end());
}
garbageCollector[key].expireTime=GetRuntime()+120.0f;
garbageCollector[key].expireTime=GetRunTime()+120.0f;
DrawDecal(pos+vf2d{0,0.5f},garbageCollector[key].decal,scale/4,shadowCol);
DrawDecal(pos+vf2d{0.5f,0},garbageCollector[key].decal,scale/4,shadowCol);
DrawDecal(pos+vf2d{0.5f,0.5f},garbageCollector[key].decal,scale/4,shadowCol);
@ -3778,8 +3778,8 @@ namespace olc
SetDrawTarget(nullptr);
newShadowDecal->Update();
}
garbageCollector[key].expireTime=GetRuntime()+120.0f;
garbageCollector[key+"_SHADOW"].expireTime=GetRuntime()+120.0f;
garbageCollector[key].expireTime=GetRunTime()+120.0f;
garbageCollector[key+"_SHADOW"].expireTime=GetRunTime()+120.0f;
DrawDecal(pos-vf2d{shadowSizeFactor,shadowSizeFactor},garbageCollector[key+"_SHADOW"].decal,scale/4,shadowCol);
DrawDecal(pos,garbageCollector[key].decal,scale,col);
}