diff --git a/Adventures in Lestoria/Adventures in Lestoria.vcxproj b/Adventures in Lestoria/Adventures in Lestoria.vcxproj index fd8c5bb6..1c34f050 100644 --- a/Adventures in Lestoria/Adventures in Lestoria.vcxproj +++ b/Adventures in Lestoria/Adventures in Lestoria.vcxproj @@ -369,6 +369,10 @@ + + + + @@ -672,6 +676,10 @@ + + + + diff --git a/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters b/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters index 09b06248..adc7031d 100644 --- a/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters +++ b/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters @@ -438,6 +438,9 @@ Header Files + + Header Files + @@ -752,6 +755,9 @@ Source Files\Bullet Types + + Source Files + diff --git a/Adventures in Lestoria/AdventuresInLestoria.cpp b/Adventures in Lestoria/AdventuresInLestoria.cpp index f750f62d..77e13220 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.cpp +++ b/Adventures in Lestoria/AdventuresInLestoria.cpp @@ -1051,7 +1051,7 @@ void AiL::RenderWorld(float fElapsedTime){ #pragma endregion for(Monster&m:MONSTER_LIST){ - m.strategyDraw(this,m); + m.strategyDraw(this,m,MONSTER_DATA[m.GetName()].GetAIStrategy()); } if(player->GetZ()>0){ @@ -1744,6 +1744,7 @@ void AiL::LoadLevel(MapName map){ foregroundTileGroups.clear(); upperForegroundTileGroups.clear(); MONSTER_LIST.clear(); + GameEvent::events.clear(); worldColor=WHITE; worldColorFunc=[&](vi2d pos){return game->worldColor;}; currentLevel=map; @@ -2475,6 +2476,7 @@ void AiL::DisplayBossEncounterInfo(){ alpha=uint8_t((bossDisplayTimer)*255); } vf2d textScale={3,5}; + textScale.x=std::min(3.f,float(ScreenWidth())/GetTextSizeProp(displayText).x); DrawShadowStringPropDecal(vf2d{float(ScreenWidth()/2),float(ScreenHeight()/2)}-vf2d{GetTextSizeProp(displayText)}*textScale/2,displayText,{252, 186, 3, alpha},{128,0,0,alpha},textScale,std::numeric_limits::max(),2); } if(InBossEncounter()){ @@ -2492,7 +2494,6 @@ void AiL::DisplayBossEncounterInfo(){ } } - void AiL::BossDamageDealt(int damage){ totalDamageDealt+=damage; } diff --git a/Adventures in Lestoria/Bear.cpp b/Adventures in Lestoria/Bear.cpp index ba17f568..f74923ce 100644 --- a/Adventures in Lestoria/Bear.cpp +++ b/Adventures in Lestoria/Bear.cpp @@ -60,9 +60,9 @@ void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){ m.PerformShootAnimation(); m.F(A::CASTING_TIMER)=ConfigFloat("Chargeup Time"); m.V(A::LOCKON_POS)=geom2d::line(m.GetPos(),game->GetPlayer()->GetPos()).vector(); - m.SetStrategyDrawFunction([](AiL*game,Monster&m){ + m.SetStrategyDrawFunction([](AiL*game,Monster&m,const std::string&strategy){ if(m.IsAlive()){ - game->view.DrawRotatedDecal(m.GetPos()+m.V(A::LOCKON_POS),GFX["range_indicator.png"].Decal(),0.f,{12.f,12.f},vf2d{_GetFloat(m,"Smash Attack Diameter",MONSTER_DATA[m.GetName()].GetAIStrategy()),_GetFloat(m,"Smash Attack Diameter",MONSTER_DATA[m.GetName()].GetAIStrategy())}/100.f,{255,255,0,160}); + game->view.DrawRotatedDecal(m.GetPos()+m.V(A::LOCKON_POS),GFX["range_indicator.png"].Decal(),0.f,{12.f,12.f},vf2d{ConfigFloat("Smash Attack Diameter"),ConfigFloat("Smash Attack Diameter")}/100.f,{255,255,0,160}); } }); m.RotateTowardsPos(m.GetPos()+m.V(A::LOCKON_POS)); @@ -99,7 +99,7 @@ void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){ } m.spriteRot=0.f; game->SetupWorldShake(0.2f); - m.SetStrategyDrawFunction([&](AiL*game,Monster&m){}); + m.SetStrategyDrawFunction([&](AiL*game,Monster&m,const std::string&strategy){}); } }break; } diff --git a/Adventures in Lestoria/GameEvent.cpp b/Adventures in Lestoria/GameEvent.cpp new file mode 100644 index 00000000..75ef7e64 --- /dev/null +++ b/Adventures in Lestoria/GameEvent.cpp @@ -0,0 +1,73 @@ +#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 © 2023 The FreeType +Project (www.freetype.org). Please see LICENSE_FT.txt for more information. +All rights reserved. +*/ +#pragma endregion + +#include "GameEvent.h" +#include "Monster.h" +#include + +std::vector>GameEvent::events; + +GameEvent::GameEvent(){} + +GameEvent::~GameEvent(){} + +GameEvent::GameEvent(std::functionfunc) + :runFunc(func){} + +void GameEvent::Update(){ + isActive=runFunc(*this); +} + +void GameEvent::UpdateEvents(){ + for(const std::unique_ptr&event:events){ + event->Update(); + } + std::erase_if(events,[](const std::unique_ptr&ev){return !ev->isActive;}); +} + +void GameEvent::AddEvent(std::unique_ptrevent){ + events.push_back(std::move(event)); +} + +MonsterStrategyGameEvent::MonsterStrategyGameEvent(std::functionfunc,Monster&m,const std::string&strategy) + :runFunc(func),m(m),strategy(strategy){} + +void MonsterStrategyGameEvent::Update(){ + INCLUDE_MONSTER_DATA + isActive=runFunc(*this,this->m,this->strategy); +} \ No newline at end of file diff --git a/Adventures in Lestoria/GameEvent.h b/Adventures in Lestoria/GameEvent.h new file mode 100644 index 00000000..b76f7850 --- /dev/null +++ b/Adventures in Lestoria/GameEvent.h @@ -0,0 +1,70 @@ +#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 © 2023 The FreeType +Project (www.freetype.org). Please see LICENSE_FT.txt for more information. +All rights reserved. +*/ +#pragma endregion +#pragma once + +#include +#include +#include + +class GameEvent{ + friend class AiL; + std::functionrunFunc; + static std::vector>events; +protected: + bool isActive=true; + virtual void Update(); +public: + GameEvent(); + GameEvent(std::functionfunc); + virtual ~GameEvent(); + static void UpdateEvents(); + static void AddEvent(std::unique_ptrevent); +}; + +class Monster; + +class MonsterStrategyGameEvent:public GameEvent{ + friend class AiL; +protected: + Monster&m; + const std::string&strategy; + std::functionrunFunc; + virtual void Update()final; +public: + MonsterStrategyGameEvent(std::functionfunc,Monster&m,const std::string&strategy); +}; \ No newline at end of file diff --git a/Adventures in Lestoria/Monster.cpp b/Adventures in Lestoria/Monster.cpp index ba2b89bf..d59b8d1d 100644 --- a/Adventures in Lestoria/Monster.cpp +++ b/Adventures in Lestoria/Monster.cpp @@ -133,7 +133,7 @@ bool Monster::_SetX(float x,const bool monsterInvoked){ vf2d newPos={x,pos.y}; vi2d tilePos=vi2d(newPos/float(game->GetCurrentMapData().tilewidth))*game->GetCurrentMapData().tilewidth; geom2d::rectcollisionRect=game->GetTileCollision(game->GetCurrentLevel(),newPos,upperLevel); - if(collisionRect==game->NO_COLLISION){ + if(isBoss||collisionRect==game->NO_COLLISION){ pos.x=std::clamp(x,game->GetCurrentMapData().tilewidth/2.f*GetSizeMult(),float(game->GetCurrentMapData().width*game->GetCurrentMapData().tilewidth-game->GetCurrentMapData().tilewidth/2.f*GetSizeMult())); Moved(); return true; @@ -163,7 +163,7 @@ bool Monster::_SetY(float y,const bool monsterInvoked){ vf2d newPos={pos.x,y}; vi2d tilePos=vi2d(newPos/float(game->GetCurrentMapData().tilewidth))*game->GetCurrentMapData().tilewidth; geom2d::rectcollisionRect=game->GetTileCollision(game->GetCurrentLevel(),newPos,upperLevel); - if(collisionRect==game->NO_COLLISION){ + if(isBoss||collisionRect==game->NO_COLLISION){ pos.y=std::clamp(y,game->GetCurrentMapData().tilewidth/2.f*GetSizeMult(),float(game->GetCurrentMapData().height*game->GetCurrentMapData().tilewidth-game->GetCurrentMapData().tilewidth/2.f*GetSizeMult())); Moved(); return true; @@ -646,7 +646,7 @@ void Monster::SetZ(float z){ this->z=z; } -void Monster::SetStrategyDrawFunction(std::functionfunc){ +void Monster::SetStrategyDrawFunction(std::functionfunc){ strategyDraw=func; } @@ -671,6 +671,10 @@ void Monster::OnDeath(){ game->ReduceBossEncounterMobCount(); } + if(hasStrategyDeathFunction){ + GameEvent::AddEvent(std::make_unique(strategyDeathFunc,*this,MONSTER_DATA[name].GetAIStrategy())); + } + SpawnDrops(); game->GetPlayer()->AddAccumulatedXP(MONSTER_DATA.at(name).GetXP()); @@ -751,4 +755,9 @@ const float Monster::GetCollisionDamage()const{ } if(collisionDmg>0)return collisionDmg; else return MONSTER_DATA[name].GetCollisionDmg(); +} + +void Monster::SetStrategyDeathFunction(std::functionfunc){ + hasStrategyDeathFunction=true; + strategyDeathFunc=func; } \ No newline at end of file diff --git a/Adventures in Lestoria/Monster.h b/Adventures in Lestoria/Monster.h index 60fc978c..4a055408 100644 --- a/Adventures in Lestoria/Monster.h +++ b/Adventures in Lestoria/Monster.h @@ -45,6 +45,7 @@ All rights reserved. #include "Item.h" #include "safemap.h" #include "Pathfinding.h" +#include "GameEvent.h" INCLUDE_ITEM_DATA @@ -95,7 +96,7 @@ struct MonsterData{ const uint32_t GetXP()const; float GetMoveSpdMult(); float GetSizeMult(); - std::string GetAIStrategy(); + const std::string&GetAIStrategy()const; int GetCollisionDmg(); std::string GetIdleAnimation(); std::string GetJumpAnimation(); @@ -113,6 +114,7 @@ struct MonsterData{ static std::mapimgs; }; +class GameEvent; class Monster:IAttributable{ friend struct STRATEGY; @@ -171,8 +173,8 @@ public: const std::function&GetStrategy()const; void SetSize(float newSize,bool immediate=true); geom2d::circleHitbox(); - void SetStrategyDrawFunction(std::functionfunc); - std::functionstrategyDraw=[](AiL*pge,Monster&m){}; + void SetStrategyDrawFunction(std::functionfunc); + std::functionstrategyDraw=[](AiL*pge,Monster&m,const std::string&strategy){}; const ItemAttributable&GetStats()const; const EventName&GetHurtSound(); const EventName&GetDeathSound(); @@ -228,6 +230,9 @@ private: float targetSize=0; bool isBoss=false; void OnDeath(); + bool hasStrategyDeathFunction=false; + std::functionstrategyDeathFunc; + void SetStrategyDeathFunction(std::functionfunc); ItemAttribute&Get(std::string_view attr); //Returns false if the monster could not be moved to the requested location due to collision. //If monsterInvoked is true, this means the monster was the one that instantiated this input, and it's not an extra movement done via collision. diff --git a/Adventures in Lestoria/MonsterAttribute.h b/Adventures in Lestoria/MonsterAttribute.h index 66383db8..7cc265d6 100644 --- a/Adventures in Lestoria/MonsterAttribute.h +++ b/Adventures in Lestoria/MonsterAttribute.h @@ -89,4 +89,5 @@ enum class Attribute{ ENVIRONMENT_TIMER, ENVIRONMENT_PHASE, CHARGE_COOLDOWN, + BULLETS_REMOVED, }; \ No newline at end of file diff --git a/Adventures in Lestoria/MonsterData.cpp b/Adventures in Lestoria/MonsterData.cpp index 2cdb60f8..c8cbae1b 100644 --- a/Adventures in Lestoria/MonsterData.cpp +++ b/Adventures in Lestoria/MonsterData.cpp @@ -221,7 +221,7 @@ float MonsterData::GetSizeMult(){ int MonsterData::GetCollisionDmg(){ return collisionDmg; } -std::string MonsterData::GetAIStrategy(){ +const std::string&MonsterData::GetAIStrategy()const{ return strategy; } std::string MonsterData::GetDisplayName(){ diff --git a/Adventures in Lestoria/SaveFile.cpp b/Adventures in Lestoria/SaveFile.cpp index 901bbc16..3682f0d0 100644 --- a/Adventures in Lestoria/SaveFile.cpp +++ b/Adventures in Lestoria/SaveFile.cpp @@ -133,9 +133,9 @@ const void SaveFile::SaveGame(){ } std::string contents=fileContents.str(); emscripten_idb_async_store("/assets",("save_file_path"_S+std::format("save.{:04}",saveFileID)).c_str(),contents.data(),contents.length(),0,[](void*arg){ - std::cout<<"Success!"<SendRequest("save_server"_S,CreateServerRequest(SaveFileOperation::SAVE_METADATA_FILE,fileContents.str())); game->responseCallback=respCallbackFunc; } diff --git a/Adventures in Lestoria/SlimeKing.cpp b/Adventures in Lestoria/SlimeKing.cpp index 4d9b38dd..55a6db5c 100644 --- a/Adventures in Lestoria/SlimeKing.cpp +++ b/Adventures in Lestoria/SlimeKing.cpp @@ -212,10 +212,10 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat } m.SetZ(0); Landed(m.phase); - m.SetStrategyDrawFunction([](AiL*game,Monster&m){}); + m.SetStrategyDrawFunction([](AiL*game,Monster&m,const std::string&strategy){}); } else if(m.F(A::JUMP_LANDING_TIMER)<=ConfigFloat("JumpWarningIndicatorTime")){ - m.SetStrategyDrawFunction([](AiL*game,Monster&m){ + m.SetStrategyDrawFunction([](AiL*game,Monster&m,const std::string&strategy){ Decal*dec=GFX["range_indicator.png"].Decal(); game->view.DrawRotatedDecal(m.GetPos(),dec,0,dec->sprite->Size()/2,vf2d{m.GetSizeMult(),m.GetSizeMult()},RED); }); diff --git a/Adventures in Lestoria/State_GameRun.cpp b/Adventures in Lestoria/State_GameRun.cpp index 859d87fa..e2620819 100644 --- a/Adventures in Lestoria/State_GameRun.cpp +++ b/Adventures in Lestoria/State_GameRun.cpp @@ -42,6 +42,7 @@ All rights reserved. #include "ItemDrop.h" #include "VisualNovel.h" #include "State_OverworldMap.h" +#include "GameEvent.h" INCLUDE_MONSTER_LIST INCLUDE_game @@ -81,6 +82,7 @@ void State_GameRun::OnUserUpdate(AiL*game){ game->HandleUserInput(game->GetElapsedTime()); game->UpdateEffects(game->GetElapsedTime()); + GameEvent::UpdateEvents(); game->GetPlayer()->Update(game->GetElapsedTime()); for(Monster&m:MONSTER_LIST){ m.Update(game->GetElapsedTime()); diff --git a/Adventures in Lestoria/Ursule.cpp b/Adventures in Lestoria/Ursule.cpp index fc7169d4..b4dfd941 100644 --- a/Adventures in Lestoria/Ursule.cpp +++ b/Adventures in Lestoria/Ursule.cpp @@ -53,15 +53,47 @@ INCLUDE_DATA using A=Attribute; void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy){ - switch(m.phase){ case 0:{ m.phase=ConfigInt("StartPhase"); m.overlaySprite=ConfigString("Overlay Sprite"); m.overlaySpriteTransparency=0U; + + #pragma region Setup On Death Function + m.SetStrategyDeathFunction([&](GameEvent&event,Monster&m,const std::string&strategy){ + if(!m.B(A::BULLETS_REMOVED)){ + for(const std::unique_ptr&b:BULLET_LIST){ + b->deactivated=true; + b->fadeOutTime=ConfigFloat("Phase 4.End Wisp Fadeout Time"); + } + m.B(A::BULLETS_REMOVED)=true; + } + m.F(A::ENVIRONMENT_TIMER)=std::max(0.f,m.F(A::ENVIRONMENT_TIMER)-game->GetElapsedTime()); + switch(m.I(A::ENVIRONMENT_PHASE)){ + case 0:{ //Fade out. Use the phase 2 environment fade-in color as the previous color to lerp out from. + game->SetWorldColor(ConfigPixel("Phase 4.Environment Fade-in Color")*util::lerp(0.f,1.0f,m.F(A::ENVIRONMENT_TIMER)/ConfigFloat("Phase 4.Environment Fade-out Time"))); + if(m.F(A::ENVIRONMENT_TIMER)==0.f){ + game->SetWorldColor({0,0,0,255}); + m.F(A::ENVIRONMENT_TIMER)=ConfigFloat("Phase 4.Environment Fade-in Time"); + m.I(A::ENVIRONMENT_PHASE)++; + game->SetWorldColorFunc([&](vi2d pos){return game->GetWorldColor();}); + } + }break; + case 1:{ //Fade in. + Pixel fadeInCol=ConfigPixel("Phase 3.Environment Fade-in Color"); + + game->SetWorldColor(fadeInCol*util::lerp(1.f,0.f,m.F(A::ENVIRONMENT_TIMER)/ConfigFloat("Phase 4.Environment Fade-in Time"))); + if(m.F(A::ENVIRONMENT_TIMER)==0.f){ + game->SetWorldColor(fadeInCol); + return false; + } + }break; + } + return true; + }); + #pragma endregion }break; case 1:{ //Run bear strategy in phase 1. - auto TransitionToPhase2=[&](){ m.phase=2; m.PerformOtherAnimation(1); @@ -125,14 +157,14 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy m.F(A::ENVIRONMENT_TIMER)=ConfigFloat("Phase 2.Environment Fade-in Time"); m.I(A::ENVIRONMENT_PHASE)++; game->SetWorldColorFunc([&](vi2d pos){ - float fadeInRange=DATA["MonsterStrategy"]["Ursule"]["Phase 2"]["Environment Fade-In Range"].GetReal()/100.f*24.f; + float fadeInRange=DATA["MonsterStrategy"]["Ursule"]["Phase 2"]["Environment Fade-in Range"].GetReal()/100.f*24.f; float distToPlayer=geom2d::line(game->GetPlayer()->GetPos(),pos).length(); return game->GetWorldColor()*std::min(1.0f,fadeInRange/distToPlayer); }); } }break; case 1:{ //Fade in. - Pixel fadeInCol=ConfigPixel("Phase 2.Environment Fade-In Color"); + Pixel fadeInCol=ConfigPixel("Phase 2.Environment Fade-in Color"); game->SetWorldColor(fadeInCol*util::lerp(1.f,0.f,m.F(A::ENVIRONMENT_TIMER)/ConfigFloat("Phase 2.Environment Fade-in Time"))); if(m.F(A::ENVIRONMENT_TIMER)==0.f){ @@ -180,7 +212,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy auto TransitionToPhase3=[&](){ m.phase=3; - game->SetWorldColor(ConfigPixel("Phase 3.Environment Fade-In Color")); + game->SetWorldColor(ConfigPixel("Phase 3.Environment Fade-in Color")); m.F(A::ENVIRONMENT_TIMER)=ConfigFloat("Phase 3.Environment Fade-in Time"); m.I(A::ENVIRONMENT_PHASE)=0; m.RemoveBuff(BARRIER_DAMAGE_REDUCTION); @@ -204,7 +236,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy m.F(A::ENVIRONMENT_TIMER)=std::max(0.f,m.F(A::ENVIRONMENT_TIMER)-fElapsedTime); switch(m.I(A::ENVIRONMENT_PHASE)){ case 0:{ //Fade out. Use the phase 2 environment fade-in color as the previous color to lerp out from. - game->SetWorldColor(ConfigPixel("Phase 2.Environment Fade-In Color")*util::lerp(0.f,1.0f,m.F(A::ENVIRONMENT_TIMER)/ConfigFloat("Phase 3.Environment Fade-out Time"))); + game->SetWorldColor(ConfigPixel("Phase 2.Environment Fade-in Color")*util::lerp(0.f,1.0f,m.F(A::ENVIRONMENT_TIMER)/ConfigFloat("Phase 3.Environment Fade-out Time"))); if(m.F(A::ENVIRONMENT_TIMER)==0.f){ game->SetWorldColor({0,0,0,255}); m.F(A::ENVIRONMENT_TIMER)=ConfigFloat("Phase 3.Environment Fade-in Time"); @@ -213,7 +245,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy } }break; case 1:{ //Fade in. - Pixel fadeInCol=ConfigPixel("Phase 3.Environment Fade-In Color"); + Pixel fadeInCol=ConfigPixel("Phase 3.Environment Fade-in Color"); game->SetWorldColor(fadeInCol*util::lerp(1.f,0.f,m.F(A::ENVIRONMENT_TIMER)/ConfigFloat("Phase 3.Environment Fade-in Time"))); if(m.F(A::ENVIRONMENT_TIMER)==0.f){ @@ -233,7 +265,6 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy auto TransitionToPhase5=[&](){ m.phase=5; m.PerformOtherAnimation(1); - m.I(A::PHASE_REPEAT_COUNT)=ConfigInt("Phase 4.Wisp Pattern Spawn Count"); SoundEffect::PlaySFX("Ursule Phase Transition",SoundEffect::CENTERED); m.F(A::ENVIRONMENT_TIMER)=ConfigFloat("Phase 4.Environment Fade-out Time"); m.I(A::ENVIRONMENT_PHASE)=0; @@ -273,6 +304,10 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy m.AddBuff(FIXED_COLLISION_DMG,10.f,ConfigFloat("Phase 3.Charge Attack Damage")); m.AddBuff(COLLISION_KNOCKBACK_STRENGTH,10.f,ConfigFloat("Phase 3.Charge Attack Knockback Strength")); m.target=geom2d::line(m.GetPos(),game->GetPlayer()->GetPos()).upoint(2.0f); + //It's possible the charge forces the bear outside the map, so it will attempt to run forever on the clamped edges. + m.target.x=std::clamp(m.target.x,0.f,float(game->GetCurrentMap().MapData.width*game->GetCurrentMap().MapData.tilewidth)); + m.target.y=std::clamp(m.target.y,0.f,float(game->GetCurrentMap().MapData.height*game->GetCurrentMap().MapData.tileheight)); + m.F(A::TARGET_TIMER)=ConfigFloat("Phase 3.Charge Max Run Time"); m.F(A::CHARGE_COOLDOWN)=ConfigFloat("Phase 3.Charge Attack Cooldown"); m.PerformOtherAnimation(3); break; @@ -284,6 +319,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy BEAR(m,fElapsedTime,"Bear"); }break; case 4:{ //A charging phase. + m.F(A::TARGET_TIMER)=std::max(0.f,m.F(A::TARGET_TIMER)-fElapsedTime); if(geom2d::line(m.pos,m.target).length()>100*fElapsedTime*m.GetMoveSpdMult()){ vf2d newPos=m.pos+geom2d::line(m.pos,m.target).vector().norm()*100*fElapsedTime*m.GetMoveSpdMult(); @@ -292,7 +328,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy float distToTarget=geom2d::line(m.target,m.GetPos()).length(); - if(distToTarget<=4.f){ + if(distToTarget<=4.f||m.F(A::TARGET_TIMER)==0.f){ m.phase=3; m.RemoveBuff(SPEEDBOOST); m.RemoveBuff(FIXED_COLLISION_DMG); @@ -301,24 +337,27 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy } }break; case 5:{ //Final boss phase. + m.PerformOtherAnimation(2); + m.F(A::SHOOT_TIMER)=std::max(0.f,m.F(A::SHOOT_TIMER)-fElapsedTime); + #pragma region Environment Color Change Handling m.F(A::ENVIRONMENT_TIMER)=std::max(0.f,m.F(A::ENVIRONMENT_TIMER)-fElapsedTime); switch(m.I(A::ENVIRONMENT_PHASE)){ case 0:{ //Fade out. Use the phase 3 environment fade-in color as the previous color to lerp out from. - game->SetWorldColor(ConfigPixel("Phase 3.Environment Fade-In Color")*util::lerp(0.f,1.0f,m.F(A::ENVIRONMENT_TIMER)/ConfigFloat("Phase 4.Environment Fade-out Time"))); + game->SetWorldColor(ConfigPixel("Phase 3.Environment Fade-in Color")*util::lerp(0.f,1.0f,m.F(A::ENVIRONMENT_TIMER)/ConfigFloat("Phase 4.Environment Fade-out Time"))); if(m.F(A::ENVIRONMENT_TIMER)==0.f){ game->SetWorldColor({0,0,0,255}); m.F(A::ENVIRONMENT_TIMER)=ConfigFloat("Phase 4.Environment Fade-in Time"); m.I(A::ENVIRONMENT_PHASE)++; game->SetWorldColorFunc([&](vi2d pos){ - float fadeInRange=DATA["MonsterStrategy"]["Ursule"]["Phase 4"]["Environment Fade-In Range"].GetReal()/100.f*24.f; + float fadeInRange=DATA["MonsterStrategy"]["Ursule"]["Phase 4"]["Environment Fade-in Range"].GetReal()/100.f*24.f; float distToPlayer=geom2d::line(game->GetPlayer()->GetPos(),pos).length(); return game->GetWorldColor()*std::min(1.0f,fadeInRange/distToPlayer); }); } }break; case 1:{ //Fade in. - Pixel fadeInCol=ConfigPixel("Phase 4.Environment Fade-In Color"); + Pixel fadeInCol=ConfigPixel("Phase 4.Environment Fade-in Color"); game->SetWorldColor(fadeInCol*util::lerp(1.f,0.f,m.F(A::ENVIRONMENT_TIMER)/ConfigFloat("Phase 4.Environment Fade-in Time"))); if(m.F(A::ENVIRONMENT_TIMER)==0.f){ game->SetWorldColor(fadeInCol); diff --git a/Adventures in Lestoria/Version.h b/Adventures in Lestoria/Version.h index 91430954..394ae556 100644 --- a/Adventures in Lestoria/Version.h +++ b/Adventures in Lestoria/Version.h @@ -39,7 +39,7 @@ All rights reserved. #define VERSION_MAJOR 0 #define VERSION_MINOR 3 #define VERSION_PATCH 0 -#define VERSION_BUILD 6336 +#define VERSION_BUILD 6380 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/Adventures in Lestoria/assets/Campaigns/Boss_1_v2.tmx b/Adventures in Lestoria/assets/Campaigns/Boss_1_v2.tmx index 2439f901..f3ce5373 100644 --- a/Adventures in Lestoria/assets/Campaigns/Boss_1_v2.tmx +++ b/Adventures in Lestoria/assets/Campaigns/Boss_1_v2.tmx @@ -264,7 +264,7 @@ - + diff --git a/Adventures in Lestoria/assets/config/MonsterStrategies.txt b/Adventures in Lestoria/assets/config/MonsterStrategies.txt index 5445ff7c..42748c8d 100644 --- a/Adventures in Lestoria/assets/config/MonsterStrategies.txt +++ b/Adventures in Lestoria/assets/config/MonsterStrategies.txt @@ -347,10 +347,10 @@ MonsterStrategy Environment Fade-in Time = 2.0s # New fade-in environment color. - Environment Fade-In Color = 87, 82, 255, 255 + Environment Fade-in Color = 87, 82, 255, 255 # The amount of range sight the player has with the new environment. - Environment Fade-In Range = 400 + Environment Fade-in Range = 400 # Wisp size in pixels. Wisp Size = 24,24 @@ -389,13 +389,16 @@ MonsterStrategy Environment Fade-in Time = 2.0s # New fade-in environment color. - Environment Fade-In Color = 255, 255, 255, 255 + Environment Fade-in Color = 255, 255, 255, 255 # Minimum range the bear will decide to charge the player. Charge Range = 450 # Amount of time the bear spends preparing to charge. - Charge Cast Time = 0.1s + Charge Cast Time = 0.3s + + # If for some reason the charge takes longer than this to reach the target, the charge will end. + Charge Max Run Time = 5.0s # Amount of speed to gain during the charge attack. Charge Speed Boost = 200% @@ -424,10 +427,10 @@ MonsterStrategy Environment Fade-in Time = 2.0s # New fade-in environment color. - Environment Fade-In Color = 255, 82, 82, 255 + Environment Fade-in Color = 255, 82, 82, 255 # The amount of range sight the player has with the new environment. - Environment Fade-In Range = 400 + Environment Fade-in Range = 400 # Wisp size in pixels. Wisp Size = 24,24 @@ -450,6 +453,9 @@ MonsterStrategy # This value is either Bag or Random. Bag means every pattern gets selected once before re-cycling. Random is truly random with potential repeats. Wisp Pattern Random Selection = Random + + # How long it takes for remaining wisps on the field to fade out when the boss dies. + End Wisp Fadeout Time = 0.4s } } } \ No newline at end of file diff --git a/x64/Release/Adventures in Lestoria.exe b/x64/Release/Adventures in Lestoria.exe index 976ee4fe..639d23c0 100644 Binary files a/x64/Release/Adventures in Lestoria.exe and b/x64/Release/Adventures in Lestoria.exe differ