Add in Curse and coin debuffs. Add in Pirate's Treasure spawn and collision check for placing and removing the curse. Adding buffs to the player will not immediately apply them but put them in a buff list to be applied on the next tick. AddBuff now returns the buff. GetOrAddBuff now properly uses the AddBuff function to add a buff to avoid any behavior discrepancies. Added a new test to check for buffs to be added. Fixed unit tests that broke due to waiting an extra game tick to apply buffs. 222/222 Tests Passing. Release Build 12049.

master
sigonasr2 1 day ago
parent ded7e53fd7
commit e1575081ce
  1. 16
      Adventures in Lestoria Tests/BuffTests.cpp
  2. 9
      Adventures in Lestoria Tests/EnchantTests.cpp
  3. 15
      Adventures in Lestoria Tests/GameHelper.h
  4. 6
      Adventures in Lestoria Tests/PlayerTests.cpp
  5. 4
      Adventures in Lestoria/Adventures in Lestoria.vcxproj
  6. 3
      Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters
  7. 7
      Adventures in Lestoria/AdventuresInLestoria.cpp
  8. 8
      Adventures in Lestoria/Buff.h
  9. 12
      Adventures in Lestoria/GhostOfPirateCaptain.cpp
  10. 4
      Adventures in Lestoria/Monster.cpp
  11. 1
      Adventures in Lestoria/Monster.h
  12. 1
      Adventures in Lestoria/MonsterAttribute.h
  13. 52
      Adventures in Lestoria/PiratesTreasure.cpp
  14. 45
      Adventures in Lestoria/Player.cpp
  15. 17
      Adventures in Lestoria/Player.h
  16. 1
      Adventures in Lestoria/RUN_STRATEGY.cpp
  17. 2
      Adventures in Lestoria/Version.h
  18. 7
      Adventures in Lestoria/assets/Campaigns/Boss_3.tmx
  19. BIN
      Adventures in Lestoria/assets/coin.png
  20. 11
      Adventures in Lestoria/assets/config/MonsterStrategies.txt
  21. 45
      Adventures in Lestoria/assets/config/Monsters.txt
  22. 1
      Adventures in Lestoria/assets/config/gfx/gfx.txt
  23. BIN
      Adventures in Lestoria/assets/gamepack.pak
  24. 3
      Adventures in Lestoria/assets/maps/Monster_Presets.tmx
  25. 5
      Adventures in Lestoria/assets/maps/Monsters/Pirate's Treasure.tx
  26. BIN
      Adventures in Lestoria/assets/maps/monsters-tileset.png
  27. BIN
      x64/Release/Adventures in Lestoria.exe

@ -120,6 +120,7 @@ namespace BuffTests
TEST_METHOD(AddBuffPlayerCallbackExpireFunctionTest){
Assert::AreEqual(size_t(0),game->GetPlayer()->GetBuffs(BuffType::AFFECTED_BY_LIGHTNING_BOLT).size(),L"Player does not have any lightning bolt affected buffs when spawning.");
game->GetPlayer()->AddBuff(BuffType::AFFECTED_BY_LIGHTNING_BOLT,3.f,1,[](Player*attachedTarget,Buff&b){attachedTarget->Hurt(5,attachedTarget->OnUpperLevel(),attachedTarget->GetZ());});
Game::Update(0.f);
Assert::AreEqual(size_t(1),game->GetPlayer()->GetBuffs(BuffType::AFFECTED_BY_LIGHTNING_BOLT).size(),L"Player is now affected By Lightning Bolt buff. Should get hurt for 5 damage in 3 seconds...");
Game::Update(0.f);
Game::Update(3.f);
@ -146,20 +147,27 @@ namespace BuffTests
}
TEST_METHOD(PlayerHasBuffFunctionTest){
Assert::AreEqual(false,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should not have a speedboost buff before being given one.");
game->GetPlayer()->AddBuff(BuffType::ADRENALINE_RUSH,1.f,1.f);
Game::AddBuffToPlayer(BuffType::ADRENALINE_RUSH,1.f,1.f);
Assert::AreEqual(false,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should not have a speedboost buff when given an unrelated buff.");
game->GetPlayer()->AddBuff(BuffType::SPEEDBOOST,1.f,1.f);
Game::AddBuffToPlayer(BuffType::SPEEDBOOST,1.f,1.f);
Assert::AreEqual(true,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should now have a speedboost buff.");
game->GetPlayer()->AddBuff(BuffType::SPEEDBOOST,2.f,1.f);
Game::AddBuffToPlayer(BuffType::SPEEDBOOST,2.f,1.f);
Assert::AreEqual(true,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should still report having a speedboost buff.");
Game::Update(0.f);
Game::Update(1.f);
Assert::AreEqual(true,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should still have one speedboost buff.");
Game::Update(1.f);
Assert::AreEqual(false,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should no longer have a speedboost buff.");
game->GetPlayer()->AddBuff(BuffType::SPEEDBOOST,1.f,1.f);
Game::AddBuffToPlayer(BuffType::SPEEDBOOST,1.f,1.f);
game->GetPlayer()->RemoveBuff(BuffType::SPEEDBOOST);
Assert::AreEqual(false,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should no longer have a speedboost buff.");
}
TEST_METHOD(PlayerDoesNotImmediatelyReceiveBuffTest){
Assert::AreEqual(false,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should not have a speedboost buff before being given one.");
game->GetPlayer()->AddBuff(BuffType::SPEEDBOOST,1.f,1.f);
Assert::AreEqual(false,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should still not have a speedboost buff since it is in the add queue.");
Game::Update(0.f);
Assert::AreEqual(true,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should now have a speedboost buff.");
}
};
}

@ -275,6 +275,7 @@ namespace EnchantTests
Assert::AreEqual(size_t(0),player->GetBuffs(BuffType::LETHAL_TEMPO).size(),L"Lethal Tempo does not stack up without the enchant.");
Game::GiveAndEquipEnchantedRing("Lethal Tempo");
testMonster.Hurt(0,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::PLAYER_ABILITY);
Game::Update(0.f);
Assert::AreEqual(size_t(1),player->GetBuffs(BuffType::LETHAL_TEMPO).size(),L"Lethal Tempo buff is active after attacking with the enchant..");
Assert::AreEqual(0.0175f,player->GetAttackRecoveryRateReduction(),L"Lethal Tempo buff reduced attack Recovery Rate by 0.0175 (5% of 0.35).");
for(int i:std::ranges::iota_view(0,10)){
@ -561,6 +562,7 @@ namespace EnchantTests
TEST_METHOD(TumbleCheck){
Game::ChangeClass(player,THIEF);
player->CheckAndPerformAbility(player->GetRightClickAbility(),testKeyboardInput);
Game::Update(0.f);
const float originalIframeTime{"Thief.Right Click Ability.Iframe Time"_F};
Assert::AreEqual(originalIframeTime,player->GetIframeTime(),L"Iframe time should be normal.");
const float originalMovespdIntensity{"Thief.Right Click Ability.Movespeed Buff"_f[0]/100.f};
@ -569,6 +571,7 @@ namespace EnchantTests
player->GetRightClickAbility().charges=1;
Game::GiveAndEquipEnchantedRing("Tumble");
player->CheckAndPerformAbility(player->GetRightClickAbility(),testKeyboardInput);
Game::Update(0.f);
Assert::AreEqual(originalIframeTime+"Thief.Right Click Ability.Iframe Time"_F*"Tumble"_ENC["BOOST PERCENTAGE"]/100.f,player->GetIframeTime(),L"Iframe time should be longer.");
Assert::AreEqual(originalMovespdIntensity+originalMovespdIntensity*"Tumble"_ENC["BOOST PERCENTAGE"]/100.f,player->GetBuffs(BuffType::SPEEDBOOST)[0].intensity,L"Player should have a movespeed buff with greater intensity.");
}
@ -618,6 +621,7 @@ namespace EnchantTests
TEST_METHOD(AdrenalineStimCheck){
Game::ChangeClass(player,THIEF);
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
Game::Update(0.f);
Assert::AreEqual("Thief.Ability 3.Duration"_F,player->GetBuffs(BuffType::ADRENALINE_RUSH)[0].duration,L"Adrenaline Rush buff duration is normal.");
Assert::AreEqual(100,player->GetHealth(),L"Adrenaline Rush does not reduce health.");
Assert::AreEqual("Thief.Auto Attack.Cooldown"_F*"Thief.Ability 3.Attack Speed Increase"_F/100.f,player->GetAttackRecoveryRateReduction(),L"Adrenaline Rush boosts attack rate normally.");
@ -627,6 +631,7 @@ namespace EnchantTests
player->RemoveAllBuffs();
Game::GiveAndEquipEnchantedRing("Adrenaline Stim");
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
Game::Update(0.f);
Assert::AreEqual("Adrenaline Stim"_ENC["NEW ADRENALINE RUSH DURATION"],player->GetBuffs(BuffType::ADRENALINE_RUSH)[0].duration,L"Adrenaline Stim enchant boosts the duration of Adrenaline Rush.");
Assert::AreEqual(80,player->GetHealth(),L"Adrenaline Stim reduces health.");
Assert::AreEqual("Thief.Auto Attack.Cooldown"_F*"Thief.Ability 3.Attack Speed Increase"_F/100.f,player->GetAttackRecoveryRateReduction(),L"Adrenaline Stim still boosts attack rate normally.");
@ -636,6 +641,7 @@ namespace EnchantTests
Game::ChangeClass(player,THIEF);
player->SetBaseStat("Attack",100);
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
Game::Update(0.f);
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
for(int i:std::ranges::iota_view(0,10)){
@ -649,6 +655,7 @@ namespace EnchantTests
Thief::ability3.charges=1;
Game::GiveAndEquipEnchantedRing("Bloodlust");
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
Game::Update(0.f);
for(int i:std::ranges::iota_view(0,30)){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.Hurt(1000,testMonster.OnUpperLevel(),testMonster.GetZ());
@ -663,6 +670,7 @@ namespace EnchantTests
player->GetRightClickAbility().charges=1;
Game::GiveAndEquipEnchantedRing("Evasive Movement");
player->CheckAndPerformAbility(player->GetRightClickAbility(),testKeyboardInput);
Game::Update(0.f);
Assert::AreEqual(size_t(1),player->GetBuffs(BuffType::DAMAGE_REDUCTION).size(),L"Roll gives a damage reduction buff.");
Assert::AreEqual("Evasive Movement"_ENC["DAMAGE REDUCTION PCT"]/100.f,player->GetDamageReductionFromBuffs(),L"Evasive Movement provides 50% damage reduction.");
}
@ -848,6 +856,7 @@ namespace EnchantTests
TEST_METHOD(SwordEnchantmentEnchantCheck){
Game::GiveAndEquipEnchantedRing("Sword Enchantment");
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
Game::Update(0.f);
Assert::AreEqual(300.f,player->GetAttackRange(),L"Attack range of Warrior doubled.");
Game::Update(0.f); //Wait an extra tick for the buff to begin going down.
Game::Update(8.f);

@ -69,6 +69,21 @@ namespace Game{
nullRing.lock()->_EnchantItem(enchantName);
return nullRing;
}
//Adds the buff directly to the player instead of the buffs added list. (By calling an update tick.)
inline void AddBuffToPlayer(BuffType type,float duration,float intensity){
game->GetPlayer()->AddBuff(type,duration,intensity);
Update(0.f);
}
//NOTE: If adding a % increase stat, please use the percentage version! 100% = 1!!
inline void AddBuffToPlayer(BuffType type,float duration,float intensity,std::set<ItemAttribute>attr){
game->GetPlayer()->AddBuff(type,duration,intensity,attr);
Update(0.f);
}
//NOTE: If adding a % increase stat, please use the percentage version! 100% = 1!!
inline void AddBuffToPlayer(BuffType type,float duration,float intensity,std::set<std::string>attr){
game->GetPlayer()->AddBuff(type,duration,intensity,attr);
Update(0.f);
}
}
namespace Test

@ -43,6 +43,7 @@ All rights reserved.
#include "ItemDrop.h"
#include "DamageNumber.h"
#include <ranges>
#include "GameHelper.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
@ -321,11 +322,11 @@ namespace PlayerTests
Assert::AreEqual(1000+player->GetBaseStat("Health"),float(player->GetMaxHealth()),L"Max Health stat should be increased from 100 to 1100.");
}
TEST_METHOD(HealthStatUpBuffCheck){
player->AddBuff(BuffType::STAT_UP,5.f,1000.f,{"Health"});
Game::AddBuffToPlayer(BuffType::STAT_UP,5.f,1000.f,{"Health"});
Assert::AreEqual(1000+player->GetBaseStat("Health"),float(player->GetMaxHealth()),L"Max Health stat should be increased from 100 to 1100.");
}
TEST_METHOD(HealthPctStatUpBuffCheck){
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"Health %"});
Game::AddBuffToPlayer(BuffType::STAT_UP,5.f,1000.0_Pct,{"Health %"});
Assert::AreEqual(1000+player->GetBaseStat("Health"),float(player->GetMaxHealth()),L"Max Health stat should be increased from 100 to 1100.");
}
TEST_METHOD(IllegalCritRateStatUpBuffCheck){
@ -593,6 +594,7 @@ namespace PlayerTests
Assert::AreEqual(0.f,player->GetAttackRecoveryRateReduction(),L"Attack rate cooldown starts with being reduced by 0 seconds.");
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
Game::Update(0.f);
Assert::IsTrue(player->GetBuffs(BuffType::ADRENALINE_RUSH).size()>0,L"After using Adrenaline Rush, player has the Adrenaline Rush buff.");
Assert::AreEqual(1.1f,player->GetMoveSpdMult(),L"Move Speed Multiplier increased by 10% to x1.1");
Assert::AreEqual(0.105f,player->GetAttackRecoveryRateReduction(),L"Attack Recovery Rate reduced by 30%, or 0.105s");

@ -1114,6 +1114,10 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="PiratesTreasure.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Pirate_Buccaneer.cpp" />
<ClCompile Include="Pirate_Captain.cpp" />
<ClCompile Include="Pirate_Marauder.cpp" />

@ -1304,6 +1304,9 @@
<ClCompile Include="GhostSaber.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="PiratesTreasure.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="cpp.hint" />

@ -1068,12 +1068,15 @@ void AiL::RenderWorld(float fElapsedTime){
const std::vector<Buff>adrenalineRushBuffs{player->GetBuffs(BuffType::ADRENALINE_RUSH)};
const std::vector<Buff>damageReductionBuffs{player->GetBuffs(BuffType::DAMAGE_REDUCTION)};
const std::vector<Buff>inkSlowdownDebuff{player->GetBuffs(BuffType::INK_SLOWDOWN)};
const std::vector<Buff>curseDebuff{player->GetBuffs(BuffType::PIRATE_GHOST_CAPTAIN_CURSE_DOT)};
const bool displayCoinSymbol{player->GetBuffs(BuffType::PIRATE_GHOST_CAPTAIN_CURSE).size()>0};
Pixel playerCol{WHITE};
if(attackBuffs.size()>0)playerCol={255,uint8_t(255*abs(sin(1.4f*attackBuffs[0].duration))),uint8_t(255*abs(sin(1.4f*attackBuffs[0].duration)))};
else if(adrenalineRushBuffs.size()>0)playerCol={uint8_t(255*abs(sin(6.f*adrenalineRushBuffs[0].duration))),255,uint8_t(255*abs(sin(6.f*adrenalineRushBuffs[0].duration)))};
else if(movespeedBuffs.size()>0)playerCol={uint8_t(255*abs(sin(2.f*movespeedBuffs[0].duration))),255,uint8_t(255*abs(sin(2.f*movespeedBuffs[0].duration)))};
else if(inkSlowdownDebuff.size()>0)playerCol={uint8_t(255*abs(sin(2.f*inkSlowdownDebuff[0].duration))),uint8_t(255*abs(sin(2.f*inkSlowdownDebuff[0].duration))),uint8_t(255*abs(sin(2.f*inkSlowdownDebuff[0].duration)))};
else if(curseDebuff.size()>0)playerCol={uint8_t(128*abs(sin(2.f*GetRunTime()))+127),uint8_t(128*abs(sin(2.f*GetRunTime()))),uint8_t(128*abs(sin(2.f*GetRunTime()))+127)};
if(player->HasIframes())playerCol.a*=0.62f;
@ -1093,6 +1096,10 @@ void AiL::RenderWorld(float fElapsedTime){
game->AddEffect(std::make_unique<Effect>(player->GetPos()-vf2d{0,4.f}-player->GetFacingDirVector()*6.f,0.2f,"energy_particle.png",player->OnUpperLevel(),vf2d{particleSize,particleSize},0.05f,vf2d{},DARK_GREEN,util::random(2*PI)));
player->poisonArrowLastParticleTimer=0.15f;
}
if(displayCoinSymbol){
view.DrawRotatedDecal(pos+vf2d{0,(-player->GetZ()-24.f-sinf(PI*GetRunTime())*4.f)*(std::signbit(scale.y)?-1:1)},GFX["coin.png"].Decal(),0.f,GFX["coin.png"].Sprite()->Size()/2,{0.5f,0.5f});
}
};
auto RenderZone=[&](geom2d::rect<int>&zone){

@ -66,6 +66,8 @@ enum BuffType{
CURSE_OF_DEATH,
AFFECTED_BY_LIGHTNING_BOLT, //Intensity indicates number of repeats remaining.
INK_SLOWDOWN, //Intensity indicates % movespd slowdown.
PIRATE_GHOST_CAPTAIN_CURSE, //A coin icon appears above the player's head.
PIRATE_GHOST_CAPTAIN_CURSE_DOT, //The same as above, but now is a damage over time as well.
};
enum class BuffRestorationType{
ONE_OFF, //This is used as a hack fix for the RestoreDuringCast Item script since they require us to restore 1 tick immediately. Over time buffs do not apply a tick immediately.
@ -75,11 +77,11 @@ enum class BuffRestorationType{
namespace BuffOverTimeType{
enum BuffOverTimeType{
HP_RESTORATION,
HP_PCT_RESTORATION,
HP_PCT_RESTORATION, //Percentage should be the raw percentage, NOT % form! (So 1 for 1%, not 0.01)
MP_RESTORATION,
MP_PCT_RESTORATION,
MP_PCT_RESTORATION, //Percentage should be the raw percentage, NOT % form! (So 1 for 1%, not 0.01)
HP_DAMAGE_OVER_TIME,
HP_PCT_DAMAGE_OVER_TIME,
HP_PCT_DAMAGE_OVER_TIME, //Percentage should be the raw percentage, NOT % form! (So 1 for 1%, not 0.01)
};
};

@ -54,6 +54,7 @@ void Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std
NORMAL,
AFTERIMAGE_FADEIN,
GHOSTSABER_SLASH=999,
TOSS_COIN,
};
enum CannonShotType{
@ -180,6 +181,12 @@ void Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std
}
}break;
}
if(Config("Curse Thresholds").GetValueCount()>m.I(A::CURSE_THRESHOLD_ARRAY_IND)&&
m.GetHealthRatio()<=ConfigFloatArr("Curse Thresholds",m.I(A::CURSE_THRESHOLD_ARRAY_IND))){
m.I(A::CURSE_THRESHOLD_ARRAY_IND)++;
SETPHASE(TOSS_COIN);
}
}break;
case AFTERIMAGE_FADEIN:{
m.F(A::CASTING_TIMER)-=fElapsedTime;
@ -195,5 +202,10 @@ void Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std
SETPHASE(m.I(A::PREVIOUS_PHASE));
}
}break;
case TOSS_COIN:{
const float curseDmgPctOverTime{ConfigFloat("Curse Damage")};
game->GetPlayer()->AddBuff(BuffType::PIRATE_GHOST_CAPTAIN_CURSE,ConfigFloat("Curse Damage Wait Time"),ceil(game->GetPlayer()->GetMaxHealth()*ConfigFloat("Curse Damage")/100.f)+1,[curseDmgPctOverTime](Player*attachedTarget,Buff&b){attachedTarget->AddBuff(BuffType::PIRATE_GHOST_CAPTAIN_CURSE_DOT,BuffRestorationType::OVER_TIME,BuffOverTimeType::HP_PCT_DAMAGE_OVER_TIME,INFINITY,curseDmgPctOverTime,1.f);});
SETPHASE(NORMAL);
}break;
}
}

@ -82,8 +82,10 @@ Monster::Monster(vf2d pos,MonsterData data,bool upperLevel,bool bossMob):
monsterWalkSoundTimer=util::random(1.f);
UpdateFacingDirection(game->GetPlayer()->GetPos());
animation.UpdateState(internal_animState,randomFrameOffset);
[[likely]]if(!game->TestingModeEnabled()){
const vf2d&currentRect{GetFrame().GetSourceRect().size};
afterImage.Create(currentRect.x,currentRect.y);
}
}
const vf2d&Monster::GetPos()const{
return pos;
@ -411,6 +413,7 @@ void Monster::Update(const float fElapsedTime){
attackedByPlayer=false;
#pragma region Afterimage Handling
[[likely]]if(!game->TestingModeEnabled()){
const auto RemoveScanLine=[&](uint8_t scanLine){
for(int x:std::ranges::iota_view(0,afterImage.Sprite()->width)){
afterImage.Sprite()->SetPixel({x,scanLine},BLANK);
@ -431,6 +434,7 @@ void Monster::Update(const float fElapsedTime){
}
}
}
}
#pragma endregion
}
Direction Monster::GetFacingDirection()const{

@ -392,6 +392,7 @@ private:
static void GIANT_OCTOPUS(Monster&m,float fElapsedTime,std::string strategy);
static void OCTOPUS_ARM(Monster&m,float fElapsedTime,std::string strategy);
static void GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std::string strategy);
static void PIRATES_TREASURE(Monster&m,float fElapsedTime,std::string strategy);
};
bool bumpedIntoTerrain=false; //Gets set to true before a strategy executes if the monster runs into some terrain on this frame.
bool attackedByPlayer=false; //Gets set to true before a strategy executes if the monster has been attacked by the player.

@ -175,4 +175,5 @@ enum class Attribute{
FIRST_WAVE_COMPLETE,
GHOST_SABER_TIMER,
GHOST_SABER_SLASH_ANIMATION_TIMER,
CURSE_THRESHOLD_ARRAY_IND, //Which array index in the curse threshold strategy property we are currently at?
};

@ -0,0 +1,52 @@
#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 © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "AdventuresInLestoria.h"
#include "MonsterStrategyHelpers.h"
INCLUDE_game
void Monster::STRATEGY::PIRATES_TREASURE(Monster&m,float fElapsedTime,std::string strategy){
const float distToPlayer{util::distance(game->GetPlayer()->GetPos(),m.GetPos())};
if(distToPlayer<=ConfigFloat("Open Distance"))m.PerformAnimation("OPEN");
else m.PerformIdleAnimation();
if(m.B(Attribute::COLLIDED_WITH_PLAYER)){
game->GetPlayer()->RemoveBuff(BuffType::PIRATE_GHOST_CAPTAIN_CURSE);
game->GetPlayer()->RemoveBuff(BuffType::PIRATE_GHOST_CAPTAIN_CURSE_DOT);
}
}

@ -441,6 +441,9 @@ void Player::Update(float fElapsedTime){
}
return false;
});
std::for_each(buffsToBeAdded.begin(),buffsToBeAdded.end(),[&](Buff&b){b.Update(game,fElapsedTime);});
std::move(buffsToBeAdded.begin(),buffsToBeAdded.end(),std::back_inserter(buffList));
buffsToBeAdded.clear();
//Class-specific update events.
OnUpdate(fElapsedTime);
switch(state){
@ -1143,39 +1146,31 @@ void Player::UpdateIdleAnimation(Key direction){
animation.ChangeState(internal_catAnimState,std::format("WITCH_CAT_WALK_{}",anim[anim.length()-1]),0.f);
}
void Player::AddBuff(BuffType type,float duration,float intensity){
Buff&newBuff{buffList.emplace_back(this,type,duration,intensity)};
OnBuffAdd(newBuff);
Buff&Player::AddBuff(BuffType type,float duration,float intensity){
return buffsToBeAdded.emplace_back(this,type,duration,intensity);
}
void Player::AddBuff(BuffType type,float duration,float intensity,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc){
Buff&newBuff{buffList.emplace_back(this,type,duration,intensity,expireCallbackFunc)};
OnBuffAdd(newBuff);
Buff&Player::AddBuff(BuffType type,float duration,float intensity,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc){
return buffsToBeAdded.emplace_back(this,type,duration,intensity,expireCallbackFunc);
}
void Player::AddBuff(BuffType type,float duration,float intensity,std::set<ItemAttribute>attr){
Buff&Player::AddBuff(BuffType type,float duration,float intensity,std::set<ItemAttribute>attr){
if(type==STAT_UP)std::for_each(attr.begin(),attr.end(),[](const ItemAttribute&attr){if(attr.ActualName()!="Health"&&attr.ActualName()!="Health %"&&attr.ActualName()!="Attack"&&attr.ActualName()!="Attack %"&&attr.ActualName()!="Defense"&&attr.ActualName()!="Defense %"&&attr.ActualName()!="CDR"&&attr.ActualName()!="Move Spd %")ERR(std::format("WARNING! Stat Up Attribute type {} is NOT IMPLEMENTED!",attr.ActualName()));});
Buff&newBuff{buffList.emplace_back(this,type,duration,intensity,attr)};
OnBuffAdd(newBuff);
return buffsToBeAdded.emplace_back(this,type,duration,intensity,attr);
}
void Player::AddBuff(BuffType type,float duration,float intensity,std::set<std::string>attr){
Buff&Player::AddBuff(BuffType type,float duration,float intensity,std::set<std::string>attr){
if(type==STAT_UP)std::for_each(attr.begin(),attr.end(),[](const std::string&attr){if(attr!="Health"&&attr!="Health %"&&attr!="Attack"&&attr!="Attack %"&&attr!="Defense"&&attr!="Defense %"&&attr!="CDR"&&attr!="Move Spd %")ERR(std::format("WARNING! Stat Up Attribute type {} is NOT IMPLEMENTED!",attr));});
Buff&newBuff{buffList.emplace_back(this,type,duration,intensity,attr)};
OnBuffAdd(newBuff);
return buffsToBeAdded.emplace_back(this,type,duration,intensity,attr);
}
void Player::AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks){
Buff&newBuff{buffList.emplace_back(this,type,overTimeType,duration,intensity,timeBetweenTicks)};
OnBuffAdd(newBuff);
Buff&Player::AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks){
return buffsToBeAdded.emplace_back(this,type,overTimeType,duration,intensity,timeBetweenTicks);
}
void Player::AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc){
Buff&newBuff{buffList.emplace_back(this,type,overTimeType,duration,intensity,timeBetweenTicks,expireCallbackFunc)};
OnBuffAdd(newBuff);
Buff&Player::AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc){
return buffsToBeAdded.emplace_back(this,type,overTimeType,duration,intensity,timeBetweenTicks,expireCallbackFunc);
}
void Player::AddBuff(BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks){
Buff&newBuff{buffList.emplace_back(this,type,restorationType,overTimeType,duration,intensity,timeBetweenTicks)};
OnBuffAdd(newBuff);
Buff&Player::AddBuff(BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks){
return buffsToBeAdded.emplace_back(this,type,restorationType,overTimeType,duration,intensity,timeBetweenTicks);
}
void Player::AddBuff(BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc){
Buff&newBuff{buffList.emplace_back(this,type,restorationType,overTimeType,duration,intensity,timeBetweenTicks,expireCallbackFunc)};
OnBuffAdd(newBuff);
Buff&Player::AddBuff(BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc){
return buffsToBeAdded.emplace_back(this,type,restorationType,overTimeType,duration,intensity,timeBetweenTicks,expireCallbackFunc);
}
bool Player::OnUpperLevel(){
@ -1200,7 +1195,7 @@ void Player::RemoveBuff(BuffType buff){
Buff&Player::GetOrAddBuff(BuffType buff,std::pair<BuffDuration,BuffIntensity>newBuff){
if(GetBuffs(buff).size()>0)return EditBuff(buff,0);
else return buffList.emplace_back(this,buff,newBuff.first,newBuff.second);
else return AddBuff(buff,newBuff.first,newBuff.second);
}
Buff&Player::EditBuff(BuffType buff,size_t buffInd){

@ -174,16 +174,16 @@ public:
void ForceSetPos(vf2d pos);
void SetState(State::State newState);
void AddBuff(BuffType type,float duration,float intensity);
Buff&AddBuff(BuffType type,float duration,float intensity);
//NOTE: If adding a % increase stat, please use the percentage version! 100% = 1!!
void AddBuff(BuffType type,float duration,float intensity,std::set<ItemAttribute>attr);
Buff&AddBuff(BuffType type,float duration,float intensity,std::set<ItemAttribute>attr);
//NOTE: If adding a % increase stat, please use the percentage version! 100% = 1!!
void AddBuff(BuffType type,float duration,float intensity,std::set<std::string>attr);
void AddBuff(BuffType type,float duration,float intensity,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc);
void AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks);
void AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc);
void AddBuff(BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks);
void AddBuff(BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc);
Buff&AddBuff(BuffType type,float duration,float intensity,std::set<std::string>attr);
Buff&AddBuff(BuffType type,float duration,float intensity,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc);
Buff&AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks);
Buff&AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc);
Buff&AddBuff(BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks);
Buff&AddBuff(BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc);
const bool HasBuff(BuffType buff)const;
const std::vector<Buff>GetBuffs(BuffType buff)const;
const std::vector<Buff>GetStatBuffs(const std::vector<std::string>&attr)const;
@ -435,6 +435,7 @@ private:
std::optional<vf2d>testAimingLoc{};
vf2d addedVel{};
vf2d previousPos{pos};
std::vector<Buff>buffsToBeAdded{};
protected:
const float ATTACK_COOLDOWN="Warrior.Auto Attack.Cooldown"_F;
const float MAGIC_ATTACK_COOLDOWN="Wizard.Auto Attack.Cooldown"_F;

@ -78,6 +78,7 @@ void Monster::InitializeStrategies(){
STRATEGY_DATA.insert("Giant Octopus",Monster::STRATEGY::GIANT_OCTOPUS);
STRATEGY_DATA.insert("Octopus Arm",Monster::STRATEGY::OCTOPUS_ARM);
STRATEGY_DATA.insert("Ghost of Pirate Captain",Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN);
STRATEGY_DATA.insert("Pirate's Treasure",Monster::STRATEGY::PIRATES_TREASURE);
STRATEGY_DATA.SetInitialized();
}

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1
#define VERSION_MINOR 3
#define VERSION_PATCH 0
#define VERSION_BUILD 12036
#define VERSION_BUILD 12049
#define stringify(a) stringify_(a)
#define stringify_(a) #a

@ -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="76" height="85" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="32">
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="76" height="85" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="33">
<properties>
<property name="Background Music" propertytype="BGM" value="beach_boss"/>
<property name="Level Type" type="int" propertytype="LevelType" value="1"/>
@ -575,5 +575,10 @@
<property name="spawner" type="object" value="30"/>
</properties>
</object>
<object id="32" template="../maps/Monsters/Pirate's Treasure.tx" x="834" y="1506">
<properties>
<property name="spawner" type="object" value="30"/>
</properties>
</object>
</objectgroup>
</map>

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

@ -1344,5 +1344,16 @@ MonsterStrategy
Ghost Saber Knockback Amt = 100
# Amount of pixels/sec the ghost saber circle expands outwards.
Ghost Saber Expand Spd = 14px
# What HP % the boss throws a coin at the player, applying a curse, and hiding from the player.
Curse Thresholds = 70%, 40%, 10%
# How much time before the curse starts dealing damage to the player
Curse Damage Wait Time = 10s
# How much % damage the curse does to the player every second.
Curse Damage = 1%/sec
}
Pirate's Treasure
{
Open Distance = 64px
}
}

@ -1861,6 +1861,51 @@ Monsters
# Drop Item Name, Drop Percentage(0-100%), Drop Min Quantity, Drop Max Quantity
# DROP[0] = Octopus Ring,100%,1,1
Hurt Sound = Monster Hurt
Death Sound = Slime Dead
Walk Sound = Slime Walk
}
Pirate's Treasure
{
Health = 1
Attack = 80
CollisionDmg = 0
Immovable = True
Invulnerable = True
Fadeout = False
Collision Radius = 64
MoveSpd = 0%
Size = 300%
XP = 0
Strategy = Pirate's Treasure
#Size of each animation frame
SheetFrameSize = 24,24
# Setting this to true means every four rows indicates one animation, the ordering of the directions is: NORTH, EAST, SOUTH, WEST
4-Way Spritesheet = False
Animations
{
# Frame Count, Frame Speed (s), Frame Cycling (Repeat,OneShot,PingPong,Reverse,ReverseOneShot)
# Animations must be defined in the same order as they are in their sprite sheets
# The First Four animations must represent a standing, walking, attack, and death animation. Their names are up to the creator.
IDLE = 1, 1.0, OneShot
WALK = 1, 1.0, OneShot
SLASHING = 1, 1.0, OneShot
OPEN = 1, 1.0, OneShot
}
# Drop Item Name, Drop Percentage(0-100%), Drop Min Quantity, Drop Max Quantity
# DROP[0] = Octopus Ring,100%,1,1
Hurt Sound = Monster Hurt
Death Sound = Slime Dead
Walk Sound = Slime Walk

@ -148,6 +148,7 @@ Images
GFX_Ink = ink.png
GFX_Cannonball = cannonball.png
GFX_GhostDagger = ghost_dagger.png
GFX_Coin = coin.png
GFX_Thief_Sheet = nico-thief.png
GFX_Trapper_Sheet = nico-trapper.png

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.2" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="24" tileheight="24" infinite="1" nextlayerid="3" nextobjectid="46">
<map version="1.10" tiledversion="1.10.2" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="24" tileheight="24" infinite="1" nextlayerid="3" nextobjectid="48">
<tileset firstgid="1" source="Monsters.tsx"/>
<layer id="1" name="Tile Layer 1" width="30" height="20">
<data encoding="csv"/>
@ -40,5 +40,6 @@
<object id="43" template="Monsters/Octopus Arm.tx" type="Monster" x="-48" y="744"/>
<object id="44" template="Monsters/Giant Octopus.tx" type="Monster" x="120" y="744"/>
<object id="45" template="Monsters/Ghost of Pirate Captain.tx" type="Monster" x="-306" y="732"/>
<object id="46" template="Monsters/Pirate's Treasure.tx" type="Monster" x="324" y="174" width="144" height="144"/>
</objectgroup>
</map>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<template>
<tileset firstgid="1" source="../Monsters.tsx"/>
<object name="Pirate's Treasure" type="Monster" gid="34" width="144" height="144"/>
</template>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Loading…
Cancel
Save