Fix boss text display for longer names. Add in GameEvent handling class. Finish second boss AI. Release build 6380.

pull/35/head
sigonasr2 10 months ago
parent f4700dc31d
commit fed07eddd6
  1. 8
      Adventures in Lestoria/Adventures in Lestoria.vcxproj
  2. 6
      Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters
  3. 5
      Adventures in Lestoria/AdventuresInLestoria.cpp
  4. 6
      Adventures in Lestoria/Bear.cpp
  5. 73
      Adventures in Lestoria/GameEvent.cpp
  6. 70
      Adventures in Lestoria/GameEvent.h
  7. 15
      Adventures in Lestoria/Monster.cpp
  8. 11
      Adventures in Lestoria/Monster.h
  9. 1
      Adventures in Lestoria/MonsterAttribute.h
  10. 2
      Adventures in Lestoria/MonsterData.cpp
  11. 18
      Adventures in Lestoria/SaveFile.cpp
  12. 4
      Adventures in Lestoria/SlimeKing.cpp
  13. 2
      Adventures in Lestoria/State_GameRun.cpp
  14. 63
      Adventures in Lestoria/Ursule.cpp
  15. 2
      Adventures in Lestoria/Version.h
  16. 2
      Adventures in Lestoria/assets/Campaigns/Boss_1_v2.tmx
  17. 18
      Adventures in Lestoria/assets/config/MonsterStrategies.txt
  18. BIN
      x64/Release/Adventures in Lestoria.exe

@ -369,6 +369,10 @@
</SubType>
</ClInclude>
<ClInclude Include="Error.h" />
<ClInclude Include="GameEvent.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="FunctionPriming.h">
<SubType>
</SubType>
@ -672,6 +676,10 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="GameEvent.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="State_GameRun.cpp" />
<ClCompile Include="State_LevelComplete.cpp">
<SubType>

@ -438,6 +438,9 @@
<ClInclude Include="DynamicCounter.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="GameEvent.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Player.cpp">
@ -752,6 +755,9 @@
<ClCompile Include="Wisp.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="GameEvent.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="cpp.hint" />

@ -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<float>::max(),2);
}
if(InBossEncounter()){
@ -2492,7 +2494,6 @@ void AiL::DisplayBossEncounterInfo(){
}
}
void AiL::BossDamageDealt(int damage){
totalDamageDealt+=damage;
}

@ -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<float>(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;
}

@ -0,0 +1,73 @@
#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 © 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 <map>
std::vector<std::unique_ptr<GameEvent>>GameEvent::events;
GameEvent::GameEvent(){}
GameEvent::~GameEvent(){}
GameEvent::GameEvent(std::function<bool(GameEvent&)>func)
:runFunc(func){}
void GameEvent::Update(){
isActive=runFunc(*this);
}
void GameEvent::UpdateEvents(){
for(const std::unique_ptr<GameEvent>&event:events){
event->Update();
}
std::erase_if(events,[](const std::unique_ptr<GameEvent>&ev){return !ev->isActive;});
}
void GameEvent::AddEvent(std::unique_ptr<GameEvent>event){
events.push_back(std::move(event));
}
MonsterStrategyGameEvent::MonsterStrategyGameEvent(std::function<bool(GameEvent&,Monster&,const std::string&)>func,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);
}

@ -0,0 +1,70 @@
#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 © 2023 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#pragma once
#include <functional>
#include <memory>
#include <string>
class GameEvent{
friend class AiL;
std::function<bool(GameEvent&)>runFunc;
static std::vector<std::unique_ptr<GameEvent>>events;
protected:
bool isActive=true;
virtual void Update();
public:
GameEvent();
GameEvent(std::function<bool(GameEvent&)>func);
virtual ~GameEvent();
static void UpdateEvents();
static void AddEvent(std::unique_ptr<GameEvent>event);
};
class Monster;
class MonsterStrategyGameEvent:public GameEvent{
friend class AiL;
protected:
Monster&m;
const std::string&strategy;
std::function<bool(GameEvent&,Monster&,const std::string&)>runFunc;
virtual void Update()final;
public:
MonsterStrategyGameEvent(std::function<bool(GameEvent&,Monster&,const std::string&)>func,Monster&m,const std::string&strategy);
};

@ -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::rect<float>collisionRect=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::rect<float>collisionRect=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::function<void(AiL*,Monster&)>func){
void Monster::SetStrategyDrawFunction(std::function<void(AiL*,Monster&,const std::string&)>func){
strategyDraw=func;
}
@ -671,6 +671,10 @@ void Monster::OnDeath(){
game->ReduceBossEncounterMobCount();
}
if(hasStrategyDeathFunction){
GameEvent::AddEvent(std::make_unique<MonsterStrategyGameEvent>(strategyDeathFunc,*this,MONSTER_DATA[name].GetAIStrategy()));
}
SpawnDrops();
game->GetPlayer()->AddAccumulatedXP(MONSTER_DATA.at(name).GetXP());
@ -752,3 +756,8 @@ const float Monster::GetCollisionDamage()const{
if(collisionDmg>0)return collisionDmg;
else return MONSTER_DATA[name].GetCollisionDmg();
}
void Monster::SetStrategyDeathFunction(std::function<bool(GameEvent&,Monster&,const std::string&)>func){
hasStrategyDeathFunction=true;
strategyDeathFunc=func;
}

@ -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::map<std::string,Renderable*>imgs;
};
class GameEvent;
class Monster:IAttributable{
friend struct STRATEGY;
@ -171,8 +173,8 @@ public:
const std::function<void(Monster&,float,std::string)>&GetStrategy()const;
void SetSize(float newSize,bool immediate=true);
geom2d::circle<float>Hitbox();
void SetStrategyDrawFunction(std::function<void(AiL*,Monster&)>func);
std::function<void(AiL*,Monster&)>strategyDraw=[](AiL*pge,Monster&m){};
void SetStrategyDrawFunction(std::function<void(AiL*,Monster&,const std::string&)>func);
std::function<void(AiL*,Monster&,const std::string&)>strategyDraw=[](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::function<bool(GameEvent&,Monster&,const std::string&)>strategyDeathFunc;
void SetStrategyDeathFunction(std::function<bool(GameEvent&,Monster&,const std::string&)>func);
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.

@ -89,4 +89,5 @@ enum class Attribute{
ENVIRONMENT_TIMER,
ENVIRONMENT_PHASE,
CHARGE_COOLDOWN,
BULLETS_REMOVED,
};

@ -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(){

@ -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!"<<std::endl;
std::cout<<"Successfully saved save file "<<saveFileID<<"!"<<std::endl;
},[](void*arg){
std::cout<<"Failed!"<<std::endl;
std::cout<<"Failed to save save file "<<saveFileID<<"!"<<std::endl;
});
file.close();
@ -355,12 +355,14 @@ const void SaveFile::Server_SaveMetadataFile(std::function<void(std::string_view
fileContents<<char(val);
}
}
std::string contents=fileContents.str();
emscripten_idb_async_store("/assets",("save_file_path"_S+"metadata.dat").c_str(),contents.data(),contents.length(),0,[](void*arg){
std::cout<<"Success 2!"<<std::endl;
},[](void*arg){
std::cout<<"Failed 2!"<<std::endl;
});
#ifdef __EMSCRIPTEN__
std::string contents=fileContents.str();
emscripten_idb_async_store("/assets",("save_file_path"_S+"metadata.dat").c_str(),contents.data(),contents.length(),0,[](void*arg){
std::cout<<"Saved metadata successfully!"<<std::endl;
},[](void*arg){
std::cout<<"Failed to save metadata!"<<std::endl;
});
#endif
game->SendRequest("save_server"_S,CreateServerRequest(SaveFileOperation::SAVE_METADATA_FILE,fileContents.str()));
game->responseCallback=respCallbackFunc;
}

@ -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);
});

@ -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());

@ -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<Bullet>&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<float>(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<float>(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<float>(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<float>(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);

@ -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

@ -264,7 +264,7 @@
<object id="2" name="Player Spawn" type="PlayerSpawnLocation" x="792" y="1248" width="24" height="24"/>
<object id="3" name="Slime King Spawn Area" type="SpawnGroup" x="306" y="384" width="1110" height="834">
<properties>
<property name="Boss Title Display" value="Slime King"/>
<property name="Boss Title Display" value="Ursule, Mother of Bears"/>
</properties>
<ellipse/>
</object>

@ -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
}
}
}
Loading…
Cancel
Save