Fix DOT buffs not counting down if their timers are intentionally reset. Tick timers are now independent of their duration timers. Make ONE_OFF effect more clear with extra comments. Fix Damage reduction test, which is supposed to report a 75% max damage reduction, even if a piece of equipment has 100% damage reduction. Separated the main buff type and the restoration type variables for DOT buffs so we can still identify them if we need to. Added three new constructors. Added helper AddBuff functions to Monster and Player accordingly with the new changes. Fix SpawnMonster not returning the correct reference to an object (was returning the reference to the termporary storage container). Refactored Buff Blend colors with some lambda functions. Implemented Burning Arrow enchant. Release Build 11008.

mac-build
sigonasr2 4 months ago
parent 9df7016f2d
commit 7ae7d3dcb3
  1. 96
      Adventures in Lestoria Tests/EnchantTests.cpp
  2. 4
      Adventures in Lestoria Tests/PlayerTests.cpp
  3. 2
      Adventures in Lestoria/Adventures in Lestoria.vcxproj
  4. 11
      Adventures in Lestoria/AdventuresInLestoria.cpp
  5. 2
      Adventures in Lestoria/AdventuresInLestoria.h
  6. 1
      Adventures in Lestoria/Arrow.cpp
  7. 56
      Adventures in Lestoria/Buff.cpp
  8. 18
      Adventures in Lestoria/Buff.h
  9. 20
      Adventures in Lestoria/Monster.cpp
  10. 2
      Adventures in Lestoria/Monster.h
  11. 19
      Adventures in Lestoria/Player.cpp
  12. 5
      Adventures in Lestoria/Player.h
  13. 2
      Adventures in Lestoria/Version.h
  14. BIN
      x64/Release/Adventures in Lestoria.exe

@ -51,6 +51,7 @@ INCLUDE_GFX
INCLUDE_ITEM_DATA
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_INITIALIZEGAMECONFIGURATIONS
INCLUDE_MONSTER_LIST
extern std::mt19937 rng;
@ -204,16 +205,16 @@ namespace EnchantTests
TEST_METHOD(StoneskinCheck){
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
std::weak_ptr<Item>nullRing2{Inventory::AddItem("Null Ring"s)};
Assert::AreEqual(0.0_Pct,player->GetDamageReductionPct(),L"Player starts with 0% Damage Reduction.");
Assert::AreEqual(0.0_Pct,player->GetDamageReductionFromBuffs(),L"Player starts with 0% Damage Reduction.");
Inventory::EquipItem(nullRing,EquipSlot::RING1);
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->EnchantItem("Stoneskin");
Test::InRange(player->GetDamageReductionPct(),{3.0_Pct,5.0_Pct},L"Damage Reduction not in expected range.");
Test::InRange(player->GetDamageReductionFromBuffs(),{3.0_Pct,5.0_Pct},L"Damage Reduction not in expected range.");
}
Inventory::EquipItem(nullRing2,EquipSlot::RING2);
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->EnchantItem("Stoneskin");
Test::InRange(player->GetDamageReductionPct(),{6.0_Pct,10.0_Pct},L"Damage Reduction not in expected range with two rings.");
Test::InRange(player->GetDamageReductionFromBuffs(),{6.0_Pct,10.0_Pct},L"Damage Reduction not in expected range with two rings.");
}
}
TEST_METHOD(ManaPoolCheck){
@ -235,14 +236,14 @@ namespace EnchantTests
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
std::weak_ptr<Item>nullRing2{Inventory::AddItem("Null Ring"s)};
Assert::AreEqual(100,player->GetMaxHealth(),L"Player starts with 100 health.");
Assert::AreEqual(0.0_Pct,player->GetDamageReductionPct(),L"Player starts with 0% damage reduction.");
Assert::AreEqual(0.0_Pct,player->GetDamageReductionFromBuffs(),L"Player starts with 0% damage reduction.");
Assert::AreEqual(100.0_Pct,player->GetMoveSpdMult(),L"Player starts with 100% move speed.");
Assert::AreEqual(0.0_Pct,player->GetHP6RecoveryPct(),L"Player starts with 0% HP/6 recovery.");
Inventory::EquipItem(nullRing,EquipSlot::RING1);
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->EnchantItem("Magical Protection");
Test::InRange(player->GetMaxHealth(),{102,103},L"Max Health not in expected range.");
Test::InRange(player->GetDamageReductionPct(),{2.0_Pct,3.0_Pct},L"Damage Reduction not in expected range.");
Test::InRange(player->GetDamageReductionFromBuffs(),{2.0_Pct,3.0_Pct},L"Damage Reduction not in expected range.");
Test::InRange(player->GetMoveSpdMult(),{102.0_Pct,103.0_Pct},L"Move Speed % not in expected range.");
Test::InRange(player->GetHP6RecoveryPct(),{1.0_Pct,1.0_Pct},L"HP/6 Recovery not in expected range.");
}
@ -250,7 +251,7 @@ namespace EnchantTests
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->EnchantItem("Magical Protection");
Test::InRange(player->GetMaxHealth(),{102,103},L"Max Health not in expected range with two rings.");
Test::InRange(player->GetDamageReductionPct(),{2.0_Pct,3.0_Pct},L"Damage Reduction not in expected range with two rings.");
Test::InRange(player->GetDamageReductionFromBuffs(),{2.0_Pct,3.0_Pct},L"Damage Reduction not in expected range with two rings.");
Test::InRange(player->GetMoveSpdMult(),{102.0_Pct,103.0_Pct},L"Move Speed % not in expected range with two rings.");
Test::InRange(player->GetHP6RecoveryPct(),{1.0_Pct,1.0_Pct},L"HP/6 Recovery not in expected range with two rings.");
}
@ -486,17 +487,17 @@ namespace EnchantTests
}
TEST_METHOD(LastReserveCheck){
player->SetBaseStat("Attack",100.f);
Assert::AreEqual(0.0_Pct,player->GetDamageReductionPct(),L"Damage reduction starts at 0%");
Assert::AreEqual(0.0_Pct,player->GetDamageReductionFromBuffs(),L"Damage reduction starts at 0%");
Assert::AreEqual(100,player->GetAttack(),L"Attack damage starts at 100.");
Assert::AreEqual(0.0_Pct,player->GetCooldownReductionPct(),L"Cooldown reduction starts at 0%");
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
Inventory::EquipItem(nullRing,EquipSlot::RING1);
nullRing.lock()->EnchantItem("Last Reserve");
Assert::AreEqual(0.0_Pct,player->GetDamageReductionPct(),L"Damage reduction is still 0%");
Assert::AreEqual(0.0_Pct,player->GetDamageReductionFromBuffs(),L"Damage reduction is still 0%");
Assert::AreEqual(100,player->GetAttack(),L"Attack damage still 100.");
Assert::AreEqual(0.0_Pct,player->GetCooldownReductionPct(),L"Cooldown reduction is still 0%");
player->Hurt(80,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(30.0_Pct,player->GetDamageReductionPct(),L"Damage reduction increased to 30%");
Assert::AreEqual(30.0_Pct,player->GetDamageReductionFromBuffs(),L"Damage reduction increased to 30%");
Assert::AreEqual(110,player->GetAttack(),L"Attack damage is now 110.");
Assert::AreEqual(10.0_Pct,player->GetCooldownReductionPct(),L"Cooldown reduction increased to 10%");
}
@ -764,5 +765,82 @@ namespace EnchantTests
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.");
}
TEST_METHOD(BurningArrowCheck){
testKey->bHeld=true; //Force the key to be held down for testing purposes.
game->ChangePlayerClass(TRAPPER);
player=game->GetPlayer();
player->AutoAttack();
Assert::AreEqual(size_t(1),BULLET_LIST.size(),L"Arrow projectile should now exist.");
Monster testMonster{{},MONSTER_DATA["TestName"]};
BULLET_LIST.back()->MonsterHit(testMonster,testMonster.GetMarkStacks());
game->SetElapsedTime(0.4f);
game->OnUserUpdate(0.4f);
Assert::AreEqual(size_t(0),BULLET_LIST.size(),L"No more projectiles exist.");
Assert::AreEqual(size_t(0),testMonster.GetBuffs(BuffType::BURNING_ARROW_BURN).size(),L"Target is not burned.");
player->ReduceAutoAttackTimer(INFINITE);
player->AutoAttack();
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
Inventory::EquipItem(nullRing,EquipSlot::RING1);
nullRing.lock()->EnchantItem("Burning Arrow");
Assert::AreEqual(size_t(1),BULLET_LIST.size(),L"Arrow projectile should now exist.");
BULLET_LIST.back()->MonsterHit(testMonster,testMonster.GetMarkStacks());
game->SetElapsedTime(0.4f);
game->OnUserUpdate(0.4f);
Assert::AreEqual(size_t(0),BULLET_LIST.size(),L"No more projectiles exist.");
Assert::AreEqual(size_t(0),testMonster.GetBuffs(BuffType::BURNING_ARROW_BURN).size(),L"Target should still not be burned since there are no marks on the target.");
player->ReduceAutoAttackTimer(INFINITE);
Monster&newMonster{game->SpawnMonster({},MONSTER_DATA["TestName"])};
game->SetElapsedTime(0.01f);
game->OnUserUpdate(0.01f);
player->_ForceCastSpell(player->GetAbility1());
game->SetElapsedTime("Trapper.Ability 1.Precast Time"_F);
game->OnUserUpdate("Trapper.Ability 1.Precast Time"_F);
game->SetElapsedTime(0.6f); //Wait for mark lock-on
game->OnUserUpdate(0.6f); //Wait for mark lock-on
player->AutoAttack();
Assert::AreEqual(size_t(1),BULLET_LIST.size(),L"Arrow projectile should now exist.");
BULLET_LIST.back()->MonsterHit(newMonster,newMonster.GetMarkStacks());
game->SetElapsedTime(0.4f);
game->OnUserUpdate(0.4f);
Assert::AreEqual(size_t(0),BULLET_LIST.size(),L"No more projectiles exist.");
Assert::AreEqual(size_t(1),newMonster.GetBuffs(BuffType::BURNING_ARROW_BURN).size(),L"Target should be burned since there are marks on the target and we have the Burning Arrow enchant.");
}
TEST_METHOD(BurningArrowTripleStackCheck){
testKey->bHeld=true; //Force the key to be held down for testing purposes.
game->ChangePlayerClass(TRAPPER);
player=game->GetPlayer();
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
Inventory::EquipItem(nullRing,EquipSlot::RING1);
nullRing.lock()->EnchantItem("Burning Arrow");
Monster&newMonster{game->SpawnMonster({},MONSTER_DATA["TestName"])};
game->SetElapsedTime(0.01f);
game->OnUserUpdate(0.01f);
player->_ForceCastSpell(player->GetAbility1());
game->SetElapsedTime("Trapper.Ability 1.Precast Time"_F);
game->OnUserUpdate("Trapper.Ability 1.Precast Time"_F);
game->SetElapsedTime(0.6f); //Wait for mark lock-on
game->OnUserUpdate(0.6f); //Wait for mark lock-on
player->ReduceAutoAttackTimer(INFINITE);
player->AutoAttack();
BULLET_LIST.back()->MonsterHit(newMonster,newMonster.GetMarkStacks());
player->ReduceAutoAttackTimer(INFINITE);
player->AutoAttack();
BULLET_LIST.back()->MonsterHit(newMonster,newMonster.GetMarkStacks());
player->ReduceAutoAttackTimer(INFINITE);
player->AutoAttack();
BULLET_LIST.back()->MonsterHit(newMonster,newMonster.GetMarkStacks());
player->ReduceAutoAttackTimer(INFINITE);
player->AutoAttack();
BULLET_LIST.back()->MonsterHit(newMonster,newMonster.GetMarkStacks());
player->ReduceAutoAttackTimer(INFINITE);
player->AutoAttack();
BULLET_LIST.back()->MonsterHit(newMonster,newMonster.GetMarkStacks());
game->SetElapsedTime(0.4f);
game->OnUserUpdate(0.4f);
Assert::AreEqual(size_t(0),BULLET_LIST.size(),L"No more projectiles exist.");
Assert::AreEqual(size_t("Burning Arrow"_ENC["MAX BURN STACK"]),newMonster.GetBuffs(BuffType::BURNING_ARROW_BURN).size(),L"Only 3 burn ticks maximum are allowed.");
player->ReduceAutoAttackTimer(INFINITE);
player->AutoAttack();
}
};
}

@ -502,10 +502,10 @@ namespace PlayerTests
}
TEST_METHOD(DamageReductionStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor3"s)};
Assert::AreEqual(0.0_Pct,player->GetDamageReductionPct(),L"Damage Reduction is 0%");
Assert::AreEqual(0.0_Pct,player->GetDamageReductionFromBuffs(),L"Damage Reduction is 0%");
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(100.0_Pct,player->GetDamageReductionPct(),L"Damage Reduction is 100%");
Assert::AreEqual(75.0_Pct,player->GetDamageReductionFromBuffs(),L"Max Damage Reduction is 75%, even if a piece has 100% damage reduction.");
}
TEST_METHOD(HPRecoveryStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor4"s)};

@ -242,7 +242,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<LanguageStandard>stdcpp20</LanguageStandard>
<AdditionalIncludeDirectories>C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\LabUser\Documents\include;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\niconiconii\OneDrive\Documents\include;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\OneDrive\Documents\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<TreatSpecificWarningsAsErrors>4099;5030;4715;4172;4834</TreatSpecificWarningsAsErrors>
<MultiProcessorCompilation>true</MultiProcessorCompilation>

@ -3399,11 +3399,11 @@ void AiL::InitializeLevels(){
}
Monster&AiL::SpawnMonster(vf2d pos,MonsterData&data,bool upperLevel,bool isBossSpawn){
monstersToBeSpawned.push_back(Monster(pos,data,upperLevel,isBossSpawn));
std::shared_ptr<Monster>newMonster{monstersToBeSpawned.emplace_back(std::make_shared<Monster>(pos,data,upperLevel,isBossSpawn))};
if(isBossSpawn){
totalBossEncounterMobs++;
}
return monstersToBeSpawned.back();
return *newMonster;
}
void AiL::DrawPie(vf2d center,float radius,float degreesCut,Pixel col){
@ -4365,11 +4365,10 @@ void AiL::UpdateMonsters(){
}
[[likely]]if(m->isBoss||m->GetDistanceFrom(GetPlayer()->GetPos())<40.f*24)m->Update(game->GetElapsedTime());
}
for(Monster&m:game->monstersToBeSpawned){
for(std::shared_ptr<Monster>&m:game->monstersToBeSpawned){
size_t prevCapacity=MONSTER_LIST.capacity();
std::shared_ptr<Monster>newMonster{std::make_shared<Monster>(m)};
newMonster->weakPtr=newMonster;
MONSTER_LIST.emplace_back(std::move(newMonster));
m->weakPtr=m;
MONSTER_LIST.emplace_back(std::move(m));
if(MONSTER_LIST.capacity()>prevCapacity)LOG(std::format("WARNING! The monster list has automatically reserved more space and resized to {}! This caused one potential frame where bullet/effect hitlists that stored information on what monsters were hit to potentially be hit a second time or cause monsters that should've been hit to never be hit. Consider starting with a larger default reserved size for MONSTER_LIST if your intention was to have this many monsters!",MONSTER_LIST.capacity()));
}
if(aMonsterIsMarkedForDeletion)std::erase_if(MONSTER_LIST,[&](const std::shared_ptr<Monster>&m){return m->markedForDeletion;});

@ -187,7 +187,7 @@ private:
float fadeOutDuration=0;
States::State transitionState=States::State::GAME_RUN;
bool gameEnd=false;
std::vector<Monster>monstersToBeSpawned;
std::vector<std::shared_ptr<Monster>>monstersToBeSpawned;
bool aMonsterIsMarkedForDeletion=false; //DO NOT MODIFY DIRECTLY! Use AMonsterIsMarkedForDeletion() instead!
time_t gameStarted;
std::function<void(std::string_view)>responseCallback;

@ -104,6 +104,7 @@ BulletDestroyState Arrow::MonsterHit(Monster&monster,const uint8_t markStacksBef
fadeOutTime=0.2f;
game->AddEffect(std::make_unique<Effect>(monster.GetPos(),0,"splash_effect.png",upperLevel,monster.GetSizeMult(),0.25));
if(poisonArrow)monster.AddBuff(BuffRestorationType::OVER_TIME,BuffOverTimeType::HP_DAMAGE_OVER_TIME,"Poisonous Arrow"_ENC["POISON DURATION"],damage*("Poisonous Arrow"_ENC["POISON TICK DAMAGE"]/100.f),1.f);
if(game->GetPlayer()->HasEnchant("Burning Arrow")&&markStacksBeforeHit>0&&monster.GetBuffs(BuffType::BURNING_ARROW_BURN).size()<"Burning Arrow"_ENC["MAX BURN STACK"])monster.AddBuff(BuffType::BURNING_ARROW_BURN,BuffRestorationType::OVER_TIME,BuffOverTimeType::HP_DAMAGE_OVER_TIME,"Burning Arrow"_ENC["BURN DURATION"],game->GetPlayer()->GetAttack()*"Burning Arrow"_ENC["BURN DAMAGE"]/100.f,2.f);
return BulletDestroyState::KEEP_ALIVE;
}

@ -51,19 +51,41 @@ Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType t
}
}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc)
:Buff(attachedTarget,type,overTimeType,duration,intensity,timeBetweenTicks){
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc)
:Buff(attachedTarget,ONE_OFF,restorationType,overTimeType,duration,intensity,timeBetweenTicks){
playerBuffCallbackFunc=expireCallbackFunc;
switch(restorationType){
case BuffRestorationType::ONE_OFF:{
this->type=ONE_OFF;
}break;
case BuffRestorationType::OVER_TIME:{
this->type=OVER_TIME;
}break;
case BuffRestorationType::OVER_TIME_DURING_CAST:{
this->type=OVER_TIME_DURING_CAST;
}break;
}
}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc)
:Buff(attachedTarget,type,overTimeType,duration,intensity,timeBetweenTicks){
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc)
:Buff(attachedTarget,ONE_OFF,restorationType,overTimeType,duration,intensity,timeBetweenTicks){
monsterBuffCallbackFunc=expireCallbackFunc;
switch(restorationType){
case BuffRestorationType::ONE_OFF:{
this->type=ONE_OFF;
}break;
case BuffRestorationType::OVER_TIME:{
this->type=OVER_TIME;
}break;
case BuffRestorationType::OVER_TIME_DURING_CAST:{
this->type=OVER_TIME_DURING_CAST;
}break;
}
}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks)
:attachedTarget(attachedTarget),duration(duration),intensity(intensity),nextTick(duration-timeBetweenTicks),timeBetweenTicks(timeBetweenTicks),overTimeType(overTimeType){
switch(type){
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks)
:Buff(attachedTarget,ONE_OFF,restorationType,overTimeType,duration,intensity,timeBetweenTicks){
switch(restorationType){
case BuffRestorationType::ONE_OFF:{
this->type=ONE_OFF;
}break;
@ -76,12 +98,26 @@ Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestor
}
}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc)
:Buff(attachedTarget,type,restorationType,overTimeType,duration,intensity,timeBetweenTicks){
playerBuffCallbackFunc=expireCallbackFunc;
}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc)
:Buff(attachedTarget,type,restorationType,overTimeType,duration,intensity,timeBetweenTicks){
monsterBuffCallbackFunc=expireCallbackFunc;
}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks)
:attachedTarget(attachedTarget),type(type),restorationType(restorationType),duration(duration),intensity(intensity),nextTick(timeBetweenTicks),timeBetweenTicks(timeBetweenTicks),overTimeType(overTimeType){}
void Buff::Update(AiL*game,float fElapsedTime){
duration-=fElapsedTime;
if(enabled&&overTimeType.has_value()&&nextTick>0&&duration<nextTick){
lifetime+=fElapsedTime;
if(enabled&&overTimeType.has_value()&&lifetime>=nextTick){
BuffTick(game,fElapsedTime);
nextTick-=timeBetweenTicks;
if(type==ONE_OFF)enabled=false;
nextTick+=timeBetweenTicks;
if(restorationType==BuffRestorationType::ONE_OFF)enabled=false;
}
}

@ -53,15 +53,16 @@ enum BuffType{
ADRENALINE_RUSH, //Intensity indicates the stack count (used by the Bloodlust enchant) this buff gives which in turn increases attack.
TRAPPER_MARK,
OVER_TIME,
ONE_OFF,
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.
OVER_TIME_DURING_CAST,
GLOW_PURPLE,
COLOR_MOD,
DAMAGE_AMPLIFICATION, //Multiplies all incoming damage by this amount.
LETHAL_TEMPO,
BURNING_ARROW_BURN,
};
enum class BuffRestorationType{
ONE_OFF,
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.
OVER_TIME,
OVER_TIME_DURING_CAST,
};
@ -83,10 +84,12 @@ struct Buff{
using MonsterBuffExpireCallbackFunction=std::function<void(std::weak_ptr<Monster>attachedTarget,Buff&b)>;
BuffType type;
BuffRestorationType restorationType;
float duration=1;
float timeBetweenTicks=1;
float intensity=1;
float nextTick=0;
float lifetime{};
std::set<ItemAttribute> attr;
std::variant<Player*,std::weak_ptr<Monster>>attachedTarget; //Who has this buff.
PlayerBuffExpireCallbackFunction playerBuffCallbackFunc=[](Player*p,Buff&b){};
@ -95,13 +98,16 @@ struct Buff{
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<ItemAttribute> attr);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<std::string> attr);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc);
void Update(AiL*game,float fElapsedTime);
private:
bool enabled{true};
bool enabled{true}; //This is only turned off because the ONE_OFF effect. See BuffType::ONE_OFF for more details.
std::optional<BuffOverTimeType::BuffOverTimeType>overTimeType;
void BuffTick(AiL*game,float fElapsedTime);
};

@ -443,9 +443,14 @@ void Monster::Draw()const{
if(GetBuffs(BuffType::GLOW_PURPLE).size()>0)glowPurpleBuff=GetBuffs(BuffType::GLOW_PURPLE)[0];
if(GetBuffs(BuffType::DAMAGE_AMPLIFICATION).size()>0)blendCol=Pixel{uint8_t(128+127*abs(sin(1.4*PI*GetBuffs(BuffType::DAMAGE_AMPLIFICATION)[0].duration))),uint8_t(0*abs(sin(1.4*PI*GetBuffs(BuffType::DAMAGE_AMPLIFICATION)[0].duration))),uint8_t(0*abs(sin(1.4*PI*GetBuffs(BuffType::DAMAGE_AMPLIFICATION)[0].duration)))};
else if(GetBuffs(BuffType::COLOR_MOD).size()>0)blendCol=Pixel{uint8_t(PixelRaw(GetBuffs(BuffType::COLOR_MOD)[0].intensity).r*abs(sin(1.4*GetBuffs(BuffType::COLOR_MOD)[0].duration))),uint8_t(PixelRaw(GetBuffs(BuffType::COLOR_MOD)[0].intensity).g*abs(sin(1.4*GetBuffs(BuffType::COLOR_MOD)[0].duration))),uint8_t(PixelRaw(GetBuffs(BuffType::COLOR_MOD)[0].intensity).b*abs(sin(1.4*GetBuffs(BuffType::COLOR_MOD)[0].duration)))};
else if(GetBuffs(BuffType::SLOWDOWN).size()>0)blendCol=Pixel{uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(128+127*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration)))};
const auto HasBuff=[&](const BuffType buff){return GetBuffs(buff).size()>0;};
//The lerpCutAmount is how much to divide the initial color by, which is used as the lerp oscillation amount. 0.5 means half the color is always active, and the other half linearly oscillates. 0.1 would mean 90% of the color is normal and 10% of the color oscillates.
const auto GetBuffBlendCol=[&](const BuffType buff,const float oscillationTime_s,const Pixel blendCol,const float lerpCutAmount=0.5f){return Pixel{uint8_t(blendCol.r*(1-lerpCutAmount)+blendCol.r*lerpCutAmount*abs(sin(oscillationTime_s*PI*GetBuffs(buff)[0].duration))),uint8_t(blendCol.g*(1-lerpCutAmount)+blendCol.g*lerpCutAmount*abs(sin(oscillationTime_s*PI*GetBuffs(buff)[0].duration))),uint8_t(blendCol.b*(1-lerpCutAmount)+blendCol.b*lerpCutAmount*abs(sin(oscillationTime_s*PI*GetBuffs(buff)[0].duration)))};};
if(HasBuff(BuffType::BURNING_ARROW_BURN))blendCol=GetBuffBlendCol(BuffType::BURNING_ARROW_BURN,1.f,{255,160,0});
else if(HasBuff(BuffType::DAMAGE_AMPLIFICATION))blendCol=GetBuffBlendCol(BuffType::DAMAGE_AMPLIFICATION,1.4f,{255,0,0});
else if(HasBuff(BuffType::COLOR_MOD))blendCol=GetBuffBlendCol(BuffType::COLOR_MOD,1.4f,PixelRaw(GetBuffs(BuffType::COLOR_MOD)[0].intensity),1.f);
else if(HasBuff(BuffType::SLOWDOWN))blendCol=GetBuffBlendCol(BuffType::SLOWDOWN,1.4f,{255,255,128},0.5f);
else if(glowPurpleBuff.has_value())blendCol=Pixel{uint8_t(255*abs(sin(1.4*glowPurpleBuff.value().get().duration))),uint8_t(255*abs(sin(1.4*glowPurpleBuff.value().get().duration))),uint8_t(128+127*abs(sin(1.4*glowPurpleBuff.value().get().duration)))};
const vf2d hitTimerOffset=vf2d{sin(20*PI*lastHitTimer+randomFrameOffset),0.f}*2.f*GetSizeMult();
@ -819,6 +824,15 @@ void Monster::AddBuff(BuffType type,float duration,float intensity,std::set<std:
buffList.emplace_back(GetWeakPointer(),type,duration,intensity,attr);
}
void Monster::AddBuff(BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks){
if(type==STAT_UP)ERR("WARNING! Incorrect usage of STAT_UP buff! It should not be an overtime buff! Use a different consutrctor!!!");
buffList.emplace_back(GetWeakPointer(),type,restorationType,overTimeType,duration,intensity,timeBetweenTicks);
}
void Monster::AddBuff(BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,Buff::MonsterBuffExpireCallbackFunction expireCallback){
if(type==STAT_UP)ERR("WARNING! Incorrect usage of STAT_UP buff! It should not be an overtime buff! Use a different consutrctor!!!");
buffList.emplace_back(GetWeakPointer(),type,restorationType,overTimeType,duration,intensity,timeBetweenTicks,expireCallback);
}
void Monster::RemoveBuff(BuffType type){
std::erase_if(buffList,[&](const Buff&buff){return buff.type==type;});
}

@ -142,6 +142,8 @@ public:
void AddBuff(BuffType type,float duration,float intensity,std::set<std::string>attr);
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::MonsterBuffExpireCallbackFunction expireCallback);
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::MonsterBuffExpireCallbackFunction expireCallback);
std::vector<Buff>GetBuffs(BuffType buff)const;
//Removes all buffs of a given type.
void RemoveBuff(BuffType type);

@ -880,7 +880,7 @@ bool Player::Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag dama
SoundEffect::PlaySFX("Warrior Block Hit",SoundEffect::CENTERED);
}else{
float otherDmgTaken=1-GetDamageReductionFromBuffs();
float armorDmgTaken=1-GetDamageReductionFromArmor();
float armorDmgTaken=1-GetDefenseDamageReductionLookup();
lastCombatTime=0;
float finalPctDmgTaken=armorDmgTaken*otherDmgTaken;
@ -1107,6 +1107,14 @@ void Player::AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeType
Buff&newBuff{buffList.emplace_back(Buff{this,type,overTimeType,duration,intensity,timeBetweenTicks,expireCallbackFunc})};
OnBuffAdd(newBuff);
}
void Player::AddBuff(BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks){
Buff&newBuff{buffList.emplace_back(Buff{this,type,restorationType,overTimeType,duration,intensity,timeBetweenTicks})};
OnBuffAdd(newBuff);
}
void Player::AddBuff(BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc){
Buff&newBuff{buffList.emplace_back(Buff{this,type,restorationType,overTimeType,duration,intensity,timeBetweenTicks,expireCallbackFunc})};
OnBuffAdd(newBuff);
}
bool Player::OnUpperLevel(){
return upperLevel;
@ -1421,7 +1429,7 @@ const float Player::GetDamageReductionFromBuffs()const{
return std::min(0.75f,dmgReduction);
};
const float Player::GetDamageReductionFromArmor()const{
const float Player::GetDefenseDamageReductionLookup()const{
float dmgReduction=0;
dmgReduction+=Stats::GetDamageReductionPct(GetDefense());
return std::min(0.75f,dmgReduction);
@ -2099,4 +2107,11 @@ void Player::UpdatePlayerOutline(){
void Player::OnBuffAdd(Buff&newBuff){
if(newBuff.type==DAMAGE_REDUCTION)UpdatePlayerOutline();
}
void Player::_ForceCastSpell(Ability&ability){
if(game->TestingModeEnabled()){
PrepareCast(ability);
CastSpell(ability);
}
}

@ -132,7 +132,7 @@ public:
const int GetAttack()const;
const int GetDefense()const;
const float GetDamageReductionFromBuffs()const;
const float GetDamageReductionFromArmor()const;
const float GetDefenseDamageReductionLookup()const;
float GetMoveSpdMult();
float GetSizeMult()const;
const float GetCooldownReductionPct()const;
@ -182,6 +182,8 @@ public:
void AddBuff(BuffType type,float duration,float intensity,std::set<std::string>attr);
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);
const std::vector<Buff>GetBuffs(BuffType buff)const;
const std::vector<Buff>GetStatBuffs(const std::vector<std::string>&attr)const;
@ -315,6 +317,7 @@ public:
Timer&GetTimer(const PlayerTimerType type);
void CancelTimer(const PlayerTimerType type);
void ResetTimers();
void _ForceCastSpell(Ability&ability);
private:
int hp="Warrior.BaseHealth"_I;
int mana="Player.BaseMana"_I;

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

Loading…
Cancel
Save