Add cooldown charge system. Abilities can now only be used when they are holding charges. One charge gets restored for each full cooldown duration. The cooldown timer stops going down when maximum charges are reached. Fix item tests to use cooldown charges. Add in item checks for CDR test (Items should not be affected by CDR). Add GetPlayerAbilities function to collect and manipulate all player abilities at the same time (cleaner code structure + easier test functionality). Add helper functions util::vformat and util::wformat and to_wstring to make code using the std:: versions much more concise. Add unit test for cooldown charges system. Add helper functions to reset abilities to their original stats before enchant modifications tweak them. Added a helper function to retrieve which ability an enchant affects. Adapt ability HUD with new charge count features. Add Mountain theme to Chapter 2 maps. Multi-Multishot Enchant implemented. Release Build 10901.

mac-build
sigonasr2 4 months ago
parent 214759c606
commit e43798fa9a
  1. 24
      Adventures in Lestoria Tests/EnchantTests.cpp
  2. 69
      Adventures in Lestoria Tests/PlayerTests.cpp
  3. 3
      Adventures in Lestoria/Ability.h
  4. 3
      Adventures in Lestoria/Adventures in Lestoria.tiled-project
  5. 50
      Adventures in Lestoria/AdventuresInLestoria.cpp
  6. 4
      Adventures in Lestoria/AdventuresInLestoria.h
  7. 29
      Adventures in Lestoria/DEFINES.h
  8. 2
      Adventures in Lestoria/Item.h
  9. 34
      Adventures in Lestoria/ItemEnchant.cpp
  10. 18
      Adventures in Lestoria/ItemEnchant.h
  11. 105
      Adventures in Lestoria/Player.cpp
  12. 29
      Adventures in Lestoria/Player.h
  13. 1
      Adventures in Lestoria/Ranger.cpp
  14. 1
      Adventures in Lestoria/Thief.cpp
  15. 1
      Adventures in Lestoria/Trapper.cpp
  16. 2
      Adventures in Lestoria/Version.h
  17. 1
      Adventures in Lestoria/Witch.cpp
  18. 1
      Adventures in Lestoria/Wizard.cpp
  19. 2
      Adventures in Lestoria/assets/Campaigns/2_1.tmx
  20. 2
      Adventures in Lestoria/assets/Campaigns/2_2.tmx
  21. 2
      Adventures in Lestoria/assets/Campaigns/2_3.tmx
  22. 2
      Adventures in Lestoria/assets/Campaigns/2_4.tmx
  23. 2
      Adventures in Lestoria/assets/Campaigns/2_5.tmx
  24. 2
      Adventures in Lestoria/assets/Campaigns/2_6.tmx
  25. 2
      Adventures in Lestoria/assets/Campaigns/2_7.tmx
  26. 2
      Adventures in Lestoria/assets/Campaigns/2_8.tmx
  27. 2
      Adventures in Lestoria/assets/Campaigns/2_B1.tmx
  28. 16
      Adventures in Lestoria/assets/config/audio/bgm.txt
  29. 2
      Adventures in Lestoria/assets/config/credits.txt
  30. BIN
      Adventures in Lestoria/assets/music/AiL_mountain2.ogg
  31. 7
      Adventures in Lestoria/util.cpp
  32. 10
      Adventures in Lestoria/util.h
  33. BIN
      x64/Release/Adventures in Lestoria.exe

@ -432,6 +432,7 @@ namespace EnchantTests
nullRing.lock()->EnchantItem("Wizard's Soul"); nullRing.lock()->EnchantItem("Wizard's Soul");
player->SetState(State::NORMAL); player->SetState(State::NORMAL);
player->RestoreMana(100); player->RestoreMana(100);
player->GetRightClickAbility().charges=1; //Reset the cooldown so it can be used.
player->GetRightClickAbility().cooldown=0.f; //Reset the cooldown so it can be used. player->GetRightClickAbility().cooldown=0.f; //Reset the cooldown so it can be used.
player->CheckAndPerformAbility(player->GetRightClickAbility(),testKeyboardInput); player->CheckAndPerformAbility(player->GetRightClickAbility(),testKeyboardInput);
Assert::AreEqual(player->GetRightClickAbility().GetCooldownTime(),player->GetRightClickAbility().cooldown,L"Right-click ability goes on cooldown like normal."); Assert::AreEqual(player->GetRightClickAbility().GetCooldownTime(),player->GetRightClickAbility().cooldown,L"Right-click ability goes on cooldown like normal.");
@ -441,6 +442,7 @@ namespace EnchantTests
Assert::AreEqual(player->GetAbility4().GetCooldownTime(),player->GetAbility4().cooldown,L"All other abilities are unaffected by Right-click ability being used."); Assert::AreEqual(player->GetAbility4().GetCooldownTime(),player->GetAbility4().cooldown,L"All other abilities are unaffected by Right-click ability being used.");
player->SetState(State::NORMAL); player->SetState(State::NORMAL);
player->RestoreMana(100); player->RestoreMana(100);
player->GetAbility1().charges=1; //Reset the cooldown so it can be used.
player->GetAbility1().cooldown=0.f; //Reset the cooldown so it can be used. player->GetAbility1().cooldown=0.f; //Reset the cooldown so it can be used.
player->CheckAndPerformAbility(player->GetAbility1(),testKeyboardInput); player->CheckAndPerformAbility(player->GetAbility1(),testKeyboardInput);
Assert::AreEqual(player->GetRightClickAbility().GetCooldownTime(),player->GetRightClickAbility().cooldown,L"Right-click ability remains unaffected by other abilities."); Assert::AreEqual(player->GetRightClickAbility().GetCooldownTime(),player->GetRightClickAbility().cooldown,L"Right-click ability remains unaffected by other abilities.");
@ -450,6 +452,7 @@ namespace EnchantTests
Assert::AreEqual(player->GetAbility4().GetCooldownTime()-1.5f,player->GetAbility4().cooldown,L"All other abilities have cooldowns reduced by 1.5 seconds."); Assert::AreEqual(player->GetAbility4().GetCooldownTime()-1.5f,player->GetAbility4().cooldown,L"All other abilities have cooldowns reduced by 1.5 seconds.");
player->SetState(State::NORMAL); player->SetState(State::NORMAL);
player->RestoreMana(100); player->RestoreMana(100);
player->GetAbility2().charges=1; //Reset the cooldown so it can be used.
player->GetAbility2().cooldown=0.f; //Reset the cooldown so it can be used. player->GetAbility2().cooldown=0.f; //Reset the cooldown so it can be used.
player->CheckAndPerformAbility(player->GetAbility2(),testKeyboardInput); player->CheckAndPerformAbility(player->GetAbility2(),testKeyboardInput);
Assert::AreEqual(player->GetRightClickAbility().GetCooldownTime(),player->GetRightClickAbility().cooldown,L"Right-click ability remains unaffected by other abilities."); Assert::AreEqual(player->GetRightClickAbility().GetCooldownTime(),player->GetRightClickAbility().cooldown,L"Right-click ability remains unaffected by other abilities.");
@ -459,6 +462,7 @@ namespace EnchantTests
Assert::AreEqual(player->GetAbility4().GetCooldownTime()-3.f,player->GetAbility4().cooldown,L"All other abilities have cooldowns reduced by 1.5 seconds."); Assert::AreEqual(player->GetAbility4().GetCooldownTime()-3.f,player->GetAbility4().cooldown,L"All other abilities have cooldowns reduced by 1.5 seconds.");
player->SetState(State::NORMAL); player->SetState(State::NORMAL);
player->RestoreMana(100); player->RestoreMana(100);
player->GetAbility3().charges=1; //Reset the cooldown so it can be used.
player->GetAbility3().cooldown=0.f; //Reset the cooldown so it can be used. player->GetAbility3().cooldown=0.f; //Reset the cooldown so it can be used.
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput); player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
game->SetElapsedTime(1.f); game->SetElapsedTime(1.f);
@ -470,6 +474,7 @@ namespace EnchantTests
Assert::AreEqual(player->GetAbility4().GetCooldownTime(),player->GetAbility4().cooldown,L"All other abilities have cooldowns reduced by 1.5 seconds."); Assert::AreEqual(player->GetAbility4().GetCooldownTime(),player->GetAbility4().cooldown,L"All other abilities have cooldowns reduced by 1.5 seconds.");
player->SetState(State::NORMAL); player->SetState(State::NORMAL);
player->RestoreMana(100); player->RestoreMana(100);
player->GetAbility4().charges=1; //Reset the cooldown so it can be used.
player->GetAbility4().cooldown=0.f; //Reset the cooldown so it can be used. player->GetAbility4().cooldown=0.f; //Reset the cooldown so it can be used.
player->CheckAndPerformAbility(player->GetAbility4(),testKeyboardInput); player->CheckAndPerformAbility(player->GetAbility4(),testKeyboardInput);
Assert::AreEqual(player->GetRightClickAbility().GetCooldownTime()-1.f,player->GetRightClickAbility().cooldown,L"Right-click ability remains unaffected by other abilities."); Assert::AreEqual(player->GetRightClickAbility().GetCooldownTime()-1.f,player->GetRightClickAbility().cooldown,L"Right-click ability remains unaffected by other abilities.");
@ -531,7 +536,7 @@ namespace EnchantTests
player->CheckAndPerformAbility(player->GetRightClickAbility(),testKeyboardInput); player->CheckAndPerformAbility(player->GetRightClickAbility(),testKeyboardInput);
Assert::AreEqual("Ranger.Right Click Ability.RetreatTime"_F,player->GetIframeTime(),L"Ranger's retreat iframe time is normal."); Assert::AreEqual("Ranger.Right Click Ability.RetreatTime"_F,player->GetIframeTime(),L"Ranger's retreat iframe time is normal.");
player->_SetIframes(0.f); player->_SetIframes(0.f);
player->GetRightClickAbility().cooldown=0.f; player->GetRightClickAbility().charges=1;
player->SetState(State::NORMAL); player->SetState(State::NORMAL);
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)}; std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
Inventory::EquipItem(nullRing,EquipSlot::RING1); Inventory::EquipItem(nullRing,EquipSlot::RING1);
@ -565,7 +570,7 @@ namespace EnchantTests
game->OnUserUpdate(1.f); game->OnUserUpdate(1.f);
} }
player->SetState(State::NORMAL); player->SetState(State::NORMAL);
player->GetAbility1().cooldown=0.f; player->GetAbility1().charges=1;
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)}; std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
Inventory::EquipItem(nullRing,EquipSlot::RING1); Inventory::EquipItem(nullRing,EquipSlot::RING1);
nullRing.lock()->EnchantItem("Extreme Rapid Fire"); nullRing.lock()->EnchantItem("Extreme Rapid Fire");
@ -575,7 +580,7 @@ namespace EnchantTests
TEST_METHOD(MegaChargedShotCheck){ TEST_METHOD(MegaChargedShotCheck){
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)}; std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
Inventory::EquipItem(nullRing,EquipSlot::RING1); Inventory::EquipItem(nullRing,EquipSlot::RING1);
nullRing.lock()->EnchantItem("Extreme Rapid Fire"); nullRing.lock()->EnchantItem("Mega Charged Shot");
Assert::AreEqual("Warrior.Ability 2.Precast Time"_F,player->GetAbility2().precastInfo.castTime,L"Non-Ranger class' precast times should be unaffected with the item."); Assert::AreEqual("Warrior.Ability 2.Precast Time"_F,player->GetAbility2().precastInfo.castTime,L"Non-Ranger class' precast times should be unaffected with the item.");
game->ChangePlayerClass(RANGER); game->ChangePlayerClass(RANGER);
player=game->GetPlayer(); player=game->GetPlayer();
@ -583,5 +588,18 @@ namespace EnchantTests
Inventory::UnequipItem(EquipSlot::RING1); Inventory::UnequipItem(EquipSlot::RING1);
Assert::AreEqual("Ranger.Ability 2.Precast Time"_F,player->GetAbility2().precastInfo.castTime,L"Ranger class' precast time should be back to normal."); Assert::AreEqual("Ranger.Ability 2.Precast Time"_F,player->GetAbility2().precastInfo.castTime,L"Ranger class' precast time should be back to normal.");
} }
TEST_METHOD(MultiMultiShotCheck){
game->ChangePlayerClass(RANGER);
player=game->GetPlayer();
Assert::AreEqual(uint8_t(1),player->GetAbility3().MAX_CHARGES,L"Player starts with 1 max charge of Multishot.");
const float multishotCooldownTime{"Ranger.Ability 3.Cooldown"_F};
Assert::AreEqual("Ranger.Ability 3.Cooldown"_F,player->GetAbility3().GetCooldownTime(),util::wformat("Player starts with {} seconds of cooldown on Multishot.",multishotCooldownTime).c_str());
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
Inventory::EquipItem(nullRing,EquipSlot::RING1);
nullRing.lock()->EnchantItem("Multi-Multishot");
Assert::AreEqual(uint8_t(3),player->GetAbility3().MAX_CHARGES,L"Player now has 3 max charges of Multishot.");
const float newMultishotCooldownTime{multishotCooldownTime-multishotCooldownTime*"Multi-Multishot"_ENC["COOLDOWN REDUCTION PCT"]/100.f};
Assert::AreEqual(newMultishotCooldownTime,player->GetAbility3().GetCooldownTime(),util::wformat("Player starts with {} seconds of cooldown on Multishot.",newMultishotCooldownTime).c_str());
}
}; };
} }

@ -42,6 +42,7 @@ All rights reserved.
#include <format> #include <format>
#include "ItemDrop.h" #include "ItemDrop.h"
#include "DamageNumber.h" #include "DamageNumber.h"
#include <ranges>
using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils; using namespace olc::utils;
@ -434,6 +435,18 @@ namespace PlayerTests
player->CheckAndPerformAbility(player->GetAbility2(),testKeyboardInput); player->CheckAndPerformAbility(player->GetAbility2(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput); player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetAbility4(),testKeyboardInput); player->CheckAndPerformAbility(player->GetAbility4(),testKeyboardInput);
Inventory::AddItem("Health Potion"s);
Inventory::AddItem("Berries"s);
Inventory::AddItem("Mana Potion"s);
game->SetLoadoutItem(0,"Health Potion");
game->SetLoadoutItem(1,"Berries");
game->SetLoadoutItem(2,"Mana Potion");
player->CheckAndPerformAbility(player->GetItem1(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetItem2(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetItem3(),testKeyboardInput);
game->SetElapsedTime(0.01f); //Force elapsed time value. game->SetElapsedTime(0.01f); //Force elapsed time value.
game->OnUserUpdate(0.01f); //Let 0.01 seconds go by. All abilities should be off cooldown. game->OnUserUpdate(0.01f); //Let 0.01 seconds go by. All abilities should be off cooldown.
Assert::AreEqual(0.f,player->GetRightClickAbility().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown."); Assert::AreEqual(0.f,player->GetRightClickAbility().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown.");
@ -441,6 +454,9 @@ namespace PlayerTests
Assert::AreEqual(0.f,player->GetAbility2().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown."); Assert::AreEqual(0.f,player->GetAbility2().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown.");
Assert::AreEqual(0.f,player->GetAbility3().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown."); Assert::AreEqual(0.f,player->GetAbility3().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown.");
Assert::AreEqual(0.f,player->GetAbility4().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown."); Assert::AreEqual(0.f,player->GetAbility4().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown.");
Assert::AreNotEqual(0.f,player->GetItem1().cooldown,L"Using an item ability with 100% CDR should not affect the cooldown.");
Assert::AreNotEqual(0.f,player->GetItem2().cooldown,L"Using an item ability with 100% CDR should not affect the cooldown.");
Assert::AreNotEqual(0.f,player->GetItem3().cooldown,L"Using an item ability with 100% CDR should not affect the cooldown.");
} }
TEST_METHOD(CritRateStatEquipCheck){ TEST_METHOD(CritRateStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)}; std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
@ -680,5 +696,58 @@ namespace PlayerTests
player->ReduceAutoAttackTimer(0.2f); player->ReduceAutoAttackTimer(0.2f);
Assert::AreEqual("Warrior.Auto Attack.Cooldown"_F-0.2f,player->GetAutoAttackTimer(),L"Auto attack timer should've been reduced by 0.2 seconds."); Assert::AreEqual("Warrior.Auto Attack.Cooldown"_F-0.2f,player->GetAutoAttackTimer(),L"Auto attack timer should've been reduced by 0.2 seconds.");
} }
TEST_METHOD(CooldownChargesCheck){
player->GetAbility4()=Ranger::ability1;
for(const std::reference_wrapper<Ability>&a:player->GetAbilities()){
if(a.get().name=="???")continue;
Assert::AreEqual(uint8_t(1),a.get().charges,util::wformat("Ability {} start with 1 charge.",a.get().name).c_str());
Assert::AreEqual(0.f,a.get().cooldown,util::wformat("Ability {} start off cooldown.",a.get().name).c_str());
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(a.get(),testKeyboardInput);
Assert::AreEqual(uint8_t(0),a.get().charges,util::wformat("Ability {} should consume a charge when used.",a.get().name).c_str());
Assert::AreEqual(a.get().COOLDOWN_TIME,a.get().cooldown,util::wformat("Ability {} go on cooldown when used.",a.get().name).c_str());
player->RestoreMana(100);
player->SetState(State::NORMAL);
player->CheckAndPerformAbility(a.get(),testKeyboardInput);
a.get().MAX_CHARGES=5;
for(int i:std::ranges::iota_view(0,5)){
testGame->SetElapsedTime(a.get().COOLDOWN_TIME);
testGame->OnUserUpdate(a.get().COOLDOWN_TIME);
Assert::AreEqual(uint8_t(i+1),a.get().charges,util::wformat("Ability {} should increment their charge count when they are completely off cooldown.",a.get().name).c_str());
if(i!=4)Assert::AreEqual(a.get().COOLDOWN_TIME,a.get().cooldown,util::wformat("Ability {} should go on cooldown again when their stack count is not maxed out.",a.get().name).c_str());
else Assert::AreEqual(0.f,a.get().cooldown,util::wformat("Ability {} should not go on cooldown again when their stack count is maxed out.",a.get().name).c_str());
}
player->RestoreMana(100);
player->SetState(State::NORMAL);
}
}
TEST_METHOD(ChargesEquipBehaviorCheck){
testGame->ChangePlayerClass(RANGER);
player=testGame->GetPlayer();
player->GetAbility3().charges=3;
testKey->bHeld=true; //Force the key to be held down for testing purposes.
testGame->SetElapsedTime(0.25f);
testGame->OnUserUpdate(0.25f);
Assert::AreEqual(uint8_t(1),player->GetAbility3().charges,L"Back down to 1 charge without having Multi-Multishot enchant equipped.");
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
Assert::AreEqual(uint8_t(0),player->GetAbility3().charges,L"Back down to 0 charges without having Multi-Multishot enchant equipped.");
}
TEST_METHOD(CooldownEquipBehaviorCheck){
testGame->ChangePlayerClass(RANGER);
player=testGame->GetPlayer();
float oldCooldownTime{player->GetAbility3().GetCooldownTime()};
player->GetAbility3().cooldown=player->GetAbility3().GetCooldownTime();
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
Inventory::EquipItem(nullRing,EquipSlot::RING1);
nullRing.lock()->EnchantItem("Multi-Multishot");
testKey->bHeld=true; //Force the key to be held down for testing purposes.
Assert::AreEqual(player->GetAbility3().GetCooldownTime(),oldCooldownTime-oldCooldownTime*"Multi-Multishot"_ENC["COOLDOWN REDUCTION PCT"]/100.f,L"Old cooldown time with multishot cooldown reduction pct matches.");
testGame->SetElapsedTime(0.f);
testGame->OnUserUpdate(0.f);
Assert::AreEqual(player->GetAbility3().GetCooldownTime(),player->GetAbility3().cooldown,L"Cooldown now matches the new reduced amount.");
}
}; };
} }

@ -62,6 +62,8 @@ struct Ability{
std::string description=""; std::string description="";
float cooldown=0; float cooldown=0;
float COOLDOWN_TIME=0; float COOLDOWN_TIME=0;
uint8_t charges{1};
uint8_t MAX_CHARGES{1};
int manaCost=0; int manaCost=0;
Pixel barColor1,barColor2; Pixel barColor1,barColor2;
PrecastData precastInfo; PrecastData precastInfo;
@ -73,6 +75,7 @@ struct Ability{
//If set to true, this ability instead activates immediately when a cast occurs. When the cast finishes, nothing happens instead. //If set to true, this ability instead activates immediately when a cast occurs. When the cast finishes, nothing happens instead.
bool actionPerformedDuringCast=false; bool actionPerformedDuringCast=false;
bool waitForRelease=false; bool waitForRelease=false;
bool keyReleaseRequiredToReactivate{false}; //When the player presses an ability, they cannot use it again until they have released the key to press it down again. So this gets set to true everytime an ability activates, and set to false once the player releases that key.
//Ability action function, returns true if the ability can be casted, otherwise returns false. //Ability action function, returns true if the ability can be casted, otherwise returns false.
// Argument 1: Player* - player pointer // Argument 1: Player* - player pointer
// Argument 2: vf2d - The returned precast target position (if the ability needs to be aimed, otherwise {}) // Argument 2: vf2d - The returned precast target position (if the ability needs to be aimed, otherwise {})

@ -52,7 +52,8 @@
"foresty1_1", "foresty1_1",
"overworld", "overworld",
"foresty_boss", "foresty_boss",
"base_camp" "base_camp",
"mountain"
], ],
"valuesAsFlags": false "valuesAsFlags": false
}, },

@ -2084,7 +2084,7 @@ void AiL::RenderHud(){
} }
void AiL::RenderCooldowns(){ void AiL::RenderCooldowns(){
std::vector<Ability>cooldowns{ std::vector<std::reference_wrapper<Ability>>cooldowns{
player->GetAbility1(), player->GetAbility1(),
player->GetAbility2(), player->GetAbility2(),
player->GetAbility3(), player->GetAbility3(),
@ -2094,6 +2094,7 @@ void AiL::RenderCooldowns(){
const auto DrawCooldown=[&](vf2d pos,Ability&a,int loadoutSlot=-1/*Set to 0-2 to get an item slot rendered instead*/){ const auto DrawCooldown=[&](vf2d pos,Ability&a,int loadoutSlot=-1/*Set to 0-2 to get an item slot rendered instead*/){
bool circle=loadoutSlot==-1; bool circle=loadoutSlot==-1;
if(a.name!="???"){ if(a.name!="???"){
const bool HasEnchantWithAbilityAffected{player->HasEnchantWithAbilityAffected(a.name)};
vf2d keyDisplaySize=vf2d{GetTextSize(a.input->GetDisplayName())}*vf2d{0.5f,0.5f}; vf2d keyDisplaySize=vf2d{GetTextSize(a.input->GetDisplayName())}*vf2d{0.5f,0.5f};
InputType controlType=KEY; InputType controlType=KEY;
@ -2107,15 +2108,16 @@ void AiL::RenderCooldowns(){
if(a.cooldown>0.1){ if(a.cooldown>0.1){
vf2d iconScale={1,1}; vf2d iconScale={1,1};
if(loadoutSlot!=-1)iconScale={0.7f,0.7f}; if(loadoutSlot!=-1)iconScale={0.7f,0.7f};
DrawRotatedDecal(pos+vf2d{12,12},GFX[a.icon].Decal(),0,{12,12},iconScale,{255,255,255,64}); DrawRotatedDecal(pos+vf2d{12,12},GFX[a.icon].Decal(),0,{12,12},iconScale,{255,255,255,uint8_t(a.charges==0?64:255)});
if(circle){ if(circle){
DrawPie(pos+vf2d{12,12},12,360-(a.cooldown/a.GetCooldownTime())*360,PixelLerp(a.barColor1,a.barColor2,(a.cooldown/a.GetCooldownTime()))); if(a.charges==0)DrawPie(pos+vf2d{12,12},12,360-(a.cooldown/a.GetCooldownTime())*360,PixelLerp(a.barColor1,a.barColor2,(a.cooldown/a.GetCooldownTime())));
else DrawPieArc("safeIndicatorGradient.png",pos+vf2d{12,12},11.5f,360-(a.cooldown/a.GetCooldownTime())*360,WHITE);
}else{ }else{
DrawSquarePie(pos+vf2d{12,12},10,360-(a.cooldown/a.GetCooldownTime())*360,PixelLerp(a.barColor1,a.barColor2,(a.cooldown/a.GetCooldownTime()))); DrawSquarePie(pos+vf2d{12,12},10,360-(a.cooldown/a.GetCooldownTime())*360,PixelLerp(a.barColor1,a.barColor2,(a.cooldown/a.GetCooldownTime())));
} }
std::stringstream cooldownTimeDisplay; std::stringstream cooldownTimeDisplay;
cooldownTimeDisplay<<std::fixed<<std::setprecision(1)<<a.cooldown; cooldownTimeDisplay<<std::fixed<<std::setprecision(1)<<a.cooldown;
DrawShadowStringPropDecal(pos+vf2d{12,12}-vf2d{float(GetTextSizeProp(cooldownTimeDisplay.str()).x*0.5),float(GetTextSizeProp(cooldownTimeDisplay.str()).y*1)}/2,cooldownTimeDisplay.str(),WHITE,BLACK,{0.5,1},{0.5,1}); if(a.charges==0)DrawShadowStringPropDecal(pos+vf2d{12,12}-vf2d{float(GetTextSizeProp(cooldownTimeDisplay.str()).x*0.5),float(GetTextSizeProp(cooldownTimeDisplay.str()).y*1)}/2,cooldownTimeDisplay.str(),WHITE,BLACK,{0.5,1},{0.5,1});
}else{ }else{
vf2d iconScale={1,1}; vf2d iconScale={1,1};
if(loadoutSlot!=-1)iconScale={0.7f,0.7f}; if(loadoutSlot!=-1)iconScale={0.7f,0.7f};
@ -2134,6 +2136,9 @@ void AiL::RenderCooldowns(){
} }
Pixel shortNameCol=BLUE,manaCostShadowCol=DARK_BLUE,keyDisplayCol=YELLOW; Pixel shortNameCol=BLUE,manaCostShadowCol=DARK_BLUE,keyDisplayCol=YELLOW;
if(HasEnchantWithAbilityAffected)shortNameCol={255,255,0};
if(player->GetMana()<a.manaCost){ if(player->GetMana()<a.manaCost){
Decal*overlayOverlayIcon=GFX["skill_overlay_icon_overlay.png"].Decal(); Decal*overlayOverlayIcon=GFX["skill_overlay_icon_overlay.png"].Decal();
if(!circle)overlayOverlayIcon=GFX["square_skill_overlay_icon_empty.png"].Decal(); if(!circle)overlayOverlayIcon=GFX["square_skill_overlay_icon_empty.png"].Decal();
@ -2142,7 +2147,7 @@ void AiL::RenderCooldowns(){
manaCostShadowCol=DARK_RED; manaCostShadowCol=DARK_RED;
keyDisplayCol=RED; keyDisplayCol=RED;
}else }else
if(a.cooldown>0){ if(a.charges==0){
shortNameCol=GREY; shortNameCol=GREY;
manaCostShadowCol=DARK_GREY; manaCostShadowCol=DARK_GREY;
keyDisplayCol=GREY; keyDisplayCol=GREY;
@ -2155,7 +2160,7 @@ void AiL::RenderCooldowns(){
if(itemAmt>0){ if(itemAmt>0){
std::string amtString="x"+std::to_string(itemAmt); std::string amtString="x"+std::to_string(itemAmt);
vf2d qtySize=vf2d{GetTextSize(amtString)}*vf2d{0.5f,0.75f}; vf2d qtySize=vf2d{GetTextSize(amtString)}*vf2d{0.5f,0.75f};
DrawShadowStringDecal(pos+vf2d{20,20}-qtySize/2,amtString,WHITE,BLACK,{0.5f,0.75f},{0.5f,0.75f}); DrawShadowStringDecal(pos+vf2d{20,20}-qtySize/2,amtString,0xE0E0E0,BLACK,{0.5f,0.75f},{0.5f,0.75f});
}else{ }else{
DrawDecal(pos,GFX["square_skill_overlay_icon_empty.png"].Decal(),{1,1},DARK_RED); DrawDecal(pos,GFX["square_skill_overlay_icon_empty.png"].Decal(),{1,1},DARK_RED);
shortNameCol=RED; shortNameCol=RED;
@ -2171,7 +2176,16 @@ void AiL::RenderCooldowns(){
} }
vf2d shortNameSize=vf2d{GetTextSize(a.shortName)}*vf2d{0.5f,0.75f}; vf2d shortNameSize=vf2d{GetTextSize(a.shortName)}*vf2d{0.5f,0.75f};
DrawShadowStringDecal(pos+vf2d{13,24}-shortNameSize/2,a.shortName,shortNameCol,{255,255,255,230},{0.5f,0.75f},{0.5f,0.75f}); Pixel shadowCol{255,255,255,230};
if(HasEnchantWithAbilityAffected)shadowCol={255,187,132,230};
if(a.charges>1){
const std::string chargeCountDisplayStr{std::format("x{}",a.charges)};
const vf2d chargeCountStrSize{GetTextSize(chargeCountDisplayStr)};
DrawShadowStringDecal(pos+vf2d{13,24}+vf2d{shortNameSize.x-chargeCountStrSize.x*0.75f,-shortNameSize.y}-4.f,chargeCountDisplayStr,WHITE,BLACK,{0.75f,0.75f});
}
DrawShadowStringDecal(pos+vf2d{13,24}-shortNameSize/2,a.shortName,shortNameCol,shadowCol,{0.5f,0.75f},{0.5f,0.75f});
} }
}; };
@ -2709,14 +2723,10 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
#pragma region Setup Player and Camera (Loading Phase 8) #pragma region Setup Player and Camera (Loading Phase 8)
LoadingScreen::AddPhase([&](){ LoadingScreen::AddPhase([&](){
player->GetAbility1().cooldown=0.f; for(const std::reference_wrapper<Ability>&a:player->GetAbilities()){
player->GetAbility2().cooldown=0.f; a.get().cooldown=0.f;
player->GetAbility3().cooldown=0.f; a.get().charges=a.get().MAX_CHARGES;
player->GetAbility4().cooldown=0.f; }
player->GetRightClickAbility().cooldown=0.f;
player->useItem1.cooldown=0.f;
player->useItem2.cooldown=0.f;
player->useItem3.cooldown=0.f;
player->upperLevel=false; //Assume player starts on lower level. player->upperLevel=false; //Assume player starts on lower level.
player->ForceSetPos(MAP_DATA[GetCurrentLevel()].MapData.playerSpawnLocation); //Normal set pos does one axis and then the other, so this will make sure that we actually end up at the right spot and ignore collision rules. player->ForceSetPos(MAP_DATA[GetCurrentLevel()].MapData.playerSpawnLocation); //Normal set pos does one axis and then the other, so this will make sure that we actually end up at the right spot and ignore collision rules.
@ -2997,6 +3007,7 @@ void AiL::ChangePlayerClass(Class cl){
Component<MenuLabel>(CHARACTER_MENU,"Level Class Display")->SetLabel(std::format("Lv{} {}",game->GetPlayer()->Level(),game->GetPlayer()->GetClassName())); Component<MenuLabel>(CHARACTER_MENU,"Level Class Display")->SetLabel(std::format("Lv{} {}",game->GetPlayer()->Level(),game->GetPlayer()->GetClassName()));
Player::moneyListeners=moneyListeners; Player::moneyListeners=moneyListeners;
GetPlayer()->InitializeMinimapImage(); GetPlayer()->InitializeMinimapImage();
player->RecalculateEquipStats();
} }
void AiL::InitializeClasses(){ void AiL::InitializeClasses(){
@ -3012,6 +3023,13 @@ void AiL::InitializeClasses(){
Trapper::InitializeClassAbilities(); Trapper::InitializeClassAbilities();
Wizard::InitializeClassAbilities(); Wizard::InitializeClassAbilities();
Witch::InitializeClassAbilities(); Witch::InitializeClassAbilities();
Warrior::CreateOriginalCopies();
Thief::CreateOriginalCopies();
Ranger::CreateOriginalCopies();
Trapper::CreateOriginalCopies();
Wizard::CreateOriginalCopies();
Witch::CreateOriginalCopies();
} }
std::string AiL::GetString(std::string key){ std::string AiL::GetString(std::string key){

@ -66,8 +66,8 @@ class SteamStatsReceivedHandler;
INCLUDE_BULLET_LIST INCLUDE_BULLET_LIST
#define CreateBullet(type) \ #define CreateBullet(type) \
*(type*const)(BULLET_LIST.emplace_back(std::make_unique<type>(type (*((type*const)BULLET_LIST.emplace_back(std::make_unique<type>(type
#define EndBullet )).get()) #define EndBullet )).get()))
using HurtReturnValue=bool; using HurtReturnValue=bool;
using HurtList=std::vector<std::pair<std::variant<Monster*,Player*>,HurtReturnValue>>; using HurtList=std::vector<std::pair<std::variant<Monster*,Player*>,HurtReturnValue>>;

@ -85,6 +85,30 @@ class::class() \
class::class(Player*player) \ class::class(Player*player) \
:Player::Player(player){} \ :Player::Player(player){} \
Class class::GetClass(){return cl;} \ Class class::GetClass(){return cl;} \
void class::CreateOriginalCopies(){ \
original_rightClickAbility=rightClickAbility; \
original_ability1=ability1; \
original_ability2=ability2; \
original_ability3=ability3; \
original_ability4=ability4; \
} \
void class::ResetToOriginalAbilities(){ \
rightClickAbility.COOLDOWN_TIME=original_rightClickAbility.COOLDOWN_TIME; \
ability1.COOLDOWN_TIME=original_ability1.COOLDOWN_TIME; \
ability2.COOLDOWN_TIME=original_ability2.COOLDOWN_TIME; \
ability3.COOLDOWN_TIME=original_ability3.COOLDOWN_TIME; \
ability4.COOLDOWN_TIME=original_ability4.COOLDOWN_TIME; \
rightClickAbility.MAX_CHARGES=original_rightClickAbility.MAX_CHARGES; \
ability1.MAX_CHARGES=original_ability1.MAX_CHARGES; \
ability2.MAX_CHARGES=original_ability2.MAX_CHARGES; \
ability3.MAX_CHARGES=original_ability3.MAX_CHARGES; \
ability4.MAX_CHARGES=original_ability4.MAX_CHARGES; \
rightClickAbility.precastInfo=original_rightClickAbility.precastInfo; \
ability1.precastInfo=original_ability1.precastInfo; \
ability2.precastInfo=original_ability2.precastInfo; \
ability3.precastInfo=original_ability3.precastInfo; \
ability4.precastInfo=original_ability4.precastInfo; \
} \
const std::string&class::GetClassName(){return name;} \ const std::string&class::GetClassName(){return name;} \
Ability&class::GetRightClickAbility(){return rightClickAbility;}; \ Ability&class::GetRightClickAbility(){return rightClickAbility;}; \
Ability&class::GetAbility1(){return ability1;}; \ Ability&class::GetAbility1(){return ability1;}; \
@ -106,6 +130,11 @@ Ability class::ability1=Ability(); \
Ability class::ability2=Ability(); \ Ability class::ability2=Ability(); \
Ability class::ability3=Ability(); \ Ability class::ability3=Ability(); \
Ability class::ability4=Ability(); \ Ability class::ability4=Ability(); \
Ability class::original_rightClickAbility=Ability(); \
Ability class::original_ability1=Ability(); \
Ability class::original_ability2=Ability(); \
Ability class::original_ability3=Ability(); \
Ability class::original_ability4=Ability(); \
std::string class::idle_n="WARRIOR_IDLE_N"; \ std::string class::idle_n="WARRIOR_IDLE_N"; \
std::string class::idle_e="WARRIOR_IDLE_E"; \ std::string class::idle_e="WARRIOR_IDLE_E"; \
std::string class::idle_s="WARRIOR_IDLE_S"; \ std::string class::idle_s="WARRIOR_IDLE_S"; \

@ -192,7 +192,7 @@ private:
ItemInfo*it; ItemInfo*it;
Stats randomizedStats; Stats randomizedStats;
bool locked=false; bool locked=false;
std::optional<ItemEnchant>enchant; std::optional<ItemEnchant>enchant{};
void SetAmt(uint32_t newAmt); void SetAmt(uint32_t newAmt);
static ItemEnhancementFunctionPrimingData enhanceFunctionPrimed; static ItemEnhancementFunctionPrimingData enhanceFunctionPrimed;

@ -124,7 +124,7 @@ void ItemEnchantInfo::Initialize(){
} }
for(const auto&[configName,val]:newEnchant.config){ for(const auto&[configName,val]:newEnchant.config){
const std::string wrappedConfigStr{std::vformat("{{{}}}",std::make_format_args(configName))}; const std::string wrappedConfigStr{util::vformat("{{{}}}",configName)};
size_t configValInd{enchantDescription.find(wrappedConfigStr)}; size_t configValInd{enchantDescription.find(wrappedConfigStr)};
if(configValInd==std::string::npos)continue; if(configValInd==std::string::npos)continue;
std::string formattedFloat{std::format("{}{}#FFFFFF",ItemEnchantInfo::enchantAttributeCol.toHTMLColorCode(),val)}; std::string formattedFloat{std::format("{}{}#FFFFFF",ItemEnchantInfo::enchantAttributeCol.toHTMLColorCode(),val)};
@ -246,7 +246,7 @@ const std::string ItemEnchant::RollRandomEnchant(){
void ItemEnchant::UpdateDescription(){ void ItemEnchant::UpdateDescription(){
description=ItemEnchantInfo::ENCHANT_LIST.at(this->enchantName).Description(); description=ItemEnchantInfo::ENCHANT_LIST.at(this->enchantName).Description();
for(const auto&[attr,val]:ItemEnchantInfo::ENCHANT_LIST.at(this->enchantName).minStatModifiers){ for(const auto&[attr,val]:ItemEnchantInfo::ENCHANT_LIST.at(this->enchantName).minStatModifiers){
const std::string wrappedConfigStr{std::vformat("{{{}}}",std::make_format_args(attr.ActualName()))}; const std::string wrappedConfigStr{util::vformat("{{{}}}",attr.ActualName())};
size_t configValInd{description.find(wrappedConfigStr)}; size_t configValInd{description.find(wrappedConfigStr)};
if(configValInd==std::string::npos)continue; if(configValInd==std::string::npos)continue;
std::string formattedFloat{std::format("{}{}#FFFFFF",ItemEnchantInfo::enchantAttributeCol.toHTMLColorCode(),GetAttribute(attr.ActualName()))}; std::string formattedFloat{std::format("{}{}#FFFFFF",ItemEnchantInfo::enchantAttributeCol.toHTMLColorCode(),GetAttribute(attr.ActualName()))};
@ -271,4 +271,34 @@ std::map<ItemAttribute,float>::const_iterator ItemEnchant::end()const{
const Pixel&ItemEnchant::DisplayCol()const{ const Pixel&ItemEnchant::DisplayCol()const{
return ItemEnchantInfo::GetEnchant(Name()).enchantAttributeCol; return ItemEnchantInfo::GetEnchant(Name()).enchantAttributeCol;
}
const std::optional<ItemEnchantInfo::AbilitySlot>&ItemEnchantInfo::GetAbilitySlot()const{
return abilitySlot;
}
const std::optional<Ability*>ItemEnchantInfo::GetAbility()const{
if(!GetAbilitySlot()||!GetClass())return {};
#pragma region Generate Abilities for all classes
#define GENERATE_ABILITY_LIST_FOR_CLASS(class,staticClass) \
{class,{std::optional<Ability*>{},&staticClass::rightClickAbility,&staticClass::ability1,&staticClass::ability2,&staticClass::ability3}},
#define END_ABILITY_LIST };
std::unordered_map<Class,std::array<std::optional<Ability*>,5>>enchantAbilitiesList{
GENERATE_ABILITY_LIST_FOR_CLASS(WARRIOR,Warrior)
GENERATE_ABILITY_LIST_FOR_CLASS(RANGER,Ranger)
GENERATE_ABILITY_LIST_FOR_CLASS(WIZARD,Wizard)
GENERATE_ABILITY_LIST_FOR_CLASS(THIEF,Thief)
GENERATE_ABILITY_LIST_FOR_CLASS(TRAPPER,Trapper)
GENERATE_ABILITY_LIST_FOR_CLASS(WITCH,Witch)
END_ABILITY_LIST;
#pragma endregion
return enchantAbilitiesList[*GetClass()][int(*GetAbilitySlot())];
}
const std::optional<std::reference_wrapper<Ability>>ItemEnchant::Ability()const{
if(GetEnchantInfo().GetAbility())return **GetEnchantInfo().GetAbility();
return {};
} }

@ -40,6 +40,7 @@ All rights reserved.
#include "AttributableStat.h" #include "AttributableStat.h"
#include "Pixel.h" #include "Pixel.h"
#include "Class.h" #include "Class.h"
#include "Ability.h"
class ItemEnchantInfo{ class ItemEnchantInfo{
friend class ItemEnchant; friend class ItemEnchant;
@ -55,6 +56,13 @@ public:
CLASS, CLASS,
UNIQUE, UNIQUE,
}; };
enum class AbilitySlot{
AUTO_ATTACK,
RIGHT_CLICK,
ABILITY_1,
ABILITY_2,
ABILITY_3,
};
const static Pixel enchantAttributeCol; const static Pixel enchantAttributeCol;
@ -66,14 +74,9 @@ public:
const std::string_view Description()const; const std::string_view Description()const;
const ItemEnchantCategory&Category()const; const ItemEnchantCategory&Category()const;
const std::optional<Class>&GetClass()const; const std::optional<Class>&GetClass()const;
const std::optional<AbilitySlot>&GetAbilitySlot()const;
const std::optional<Ability*>GetAbility()const; //Get the ability this enchant is tied to.
const float GetConfigValue(const std::string_view keyName)const; const float GetConfigValue(const std::string_view keyName)const;
enum class AbilitySlot{
AUTO_ATTACK,
RIGHT_CLICK,
ABILITY_1,
ABILITY_2,
ABILITY_3,
};
const float operator[](const std::string& name)const; const float operator[](const std::string& name)const;
private: private:
class ItemEnchantCategoryData{ class ItemEnchantCategoryData{
@ -109,6 +112,7 @@ public:
std::map<ItemAttribute,float>::const_iterator begin()const; std::map<ItemAttribute,float>::const_iterator begin()const;
std::map<ItemAttribute,float>::const_iterator end()const; std::map<ItemAttribute,float>::const_iterator end()const;
const Pixel&DisplayCol()const; const Pixel&DisplayCol()const;
const std::optional<std::reference_wrapper<Ability>>Ability()const;
private: private:
void UpdateDescription(); void UpdateDescription();
const ItemEnchantInfo&GetEnchantInfo()const; const ItemEnchantInfo&GetEnchantInfo()const;

@ -378,7 +378,8 @@ void Player::Update(float fElapsedTime){
SetState(State::NORMAL); SetState(State::NORMAL);
if(!allowed&&castPrepAbility->action(this,castInfo.castPos))allowed=true; if(!allowed&&castPrepAbility->action(this,castInfo.castPos))allowed=true;
if(allowed){ if(allowed){
castPrepAbility->cooldown=castPrepAbility->GetCooldownTime(); if(castPrepAbility->cooldown==0.f)castPrepAbility->cooldown=castPrepAbility->GetCooldownTime();
castPrepAbility->charges--;
ConsumeMana(castPrepAbility->manaCost); ConsumeMana(castPrepAbility->manaCost);
OnAbilityUse(ability); OnAbilityUse(ability);
} }
@ -572,38 +573,21 @@ void Player::Update(float fElapsedTime){
}else{ }else{
cooldownMultiplier=1/(1-game->GetPlayer()->GetCooldownReductionPct()); cooldownMultiplier=1/(1-game->GetPlayer()->GetCooldownReductionPct());
} }
rightClickAbility.cooldown-=fElapsedTime*cooldownMultiplier;
ability.cooldown-=fElapsedTime*cooldownMultiplier; const std::vector<std::reference_wrapper<Ability>>playerAbilities{GetAbilities()};
ability2.cooldown-=fElapsedTime*cooldownMultiplier; std::for_each(playerAbilities.begin(),playerAbilities.end(),[&fElapsedTime,&cooldownMultiplier](std::reference_wrapper<Ability>a){
ability3.cooldown-=fElapsedTime*cooldownMultiplier; //NOTE: We have to compare to the max cooldown time when reducing cooldowns since it's possible when we equip/unequip items that the cooldown remaining is longer than the max cooldown, so we will immediately set the cooldown to that value if necessary.
ability4.cooldown-=fElapsedTime*cooldownMultiplier; if(a.get().itemAbility)a.get().cooldown=std::min(a.get().GetCooldownTime(),a.get().cooldown-fElapsedTime); //Item abilities are not affected by CDR.
item1.cooldown-=fElapsedTime; else if(a.get().charges>=a.get().MAX_CHARGES)a.get().cooldown=0.f; //We got in a state where we probably unequipped something and no longer need to be on cooldown.
item2.cooldown-=fElapsedTime; else a.get().cooldown=std::min(a.get().GetCooldownTime(),a.get().cooldown-fElapsedTime*cooldownMultiplier);
item3.cooldown-=fElapsedTime;
if(rightClickAbility.cooldown<0){ if(a.get().cooldown<=0.f){
rightClickAbility.cooldown=0; a.get().charges=std::min(a.get().MAX_CHARGES,uint8_t(a.get().charges+1));
} if(a.get().charges<a.get().MAX_CHARGES)a.get().cooldown=a.get().GetCooldownTime();
if(ability.cooldown<0){ else a.get().cooldown=0.f;
ability.cooldown=0; }
} });
if(ability2.cooldown<0){
ability2.cooldown=0;
}
if(ability3.cooldown<0){
ability3.cooldown=0;
}
if(ability4.cooldown<0){
ability4.cooldown=0;
}
if(item1.cooldown<0){
item1.cooldown=0;
}
if(item2.cooldown<0){
item2.cooldown=0;
}
if(item3.cooldown<0){
item3.cooldown=0;
}
for(std::shared_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
const float playerRadius{12*GetSizeMult()/2}; const float playerRadius{12*GetSizeMult()/2};
const float monsterRadius{m->GetCollisionRadius()}; const float monsterRadius{m->GetCollisionRadius()};
@ -759,7 +743,7 @@ void Player::Update(float fElapsedTime){
float angleToCursor=atan2(extendedLine.y-GetPos().y,extendedLine.x-GetPos().x); float angleToCursor=atan2(extendedLine.y-GetPos().y,extendedLine.x-GetPos().x);
attack_cooldown_timer=ARROW_ATTACK_COOLDOWN; attack_cooldown_timer=ARROW_ATTACK_COOLDOWN;
BULLET_LIST.push_back(std::make_unique<Arrow>(Arrow(GetPos(),extendedLine,vf2d{cos(angleToCursor)*"Ranger.Ability 1.ArrowSpd"_F,float(sin(angleToCursor)*"Ranger.Ability 1.ArrowSpd"_F-PI/8*"Ranger.Ability 1.ArrowSpd"_F)}+movementVelocity/1.5f,12*"Ranger.Ability 1.ArrowRadius"_F/100,int(GetAttack()*"Ranger.Ability 1.DamageMult"_F),OnUpperLevel(),true))); BULLET_LIST.push_back(std::make_unique<Arrow>(Arrow(GetPos(),extendedLine,vf2d{cos(angleToCursor)*"Ranger.Ability 1.ArrowSpd"_F,float(sin(angleToCursor)*"Ranger.Ability 1.ArrowSpd"_F-PI/8*"Ranger.Ability 1.ArrowSpd"_F)}+movementVelocity/1.5f,12*"Ranger.Ability 1.ArrowRadius"_F/100,int(GetAttack()*"Ranger.Ability 1.DamageMult"_F),OnUpperLevel(),true)));
SetAnimationBasedOnTargetingDirection("SHOOT",angleToCursor); if(GetClass()&(RANGER|TRAPPER))SetAnimationBasedOnTargetingDirection("SHOOT",angleToCursor);
rapidFireTimer=RAPID_FIRE_SHOOT_DELAY; rapidFireTimer=RAPID_FIRE_SHOOT_DELAY;
}else{ }else{
SetState(State::NORMAL); SetState(State::NORMAL);
@ -1218,7 +1202,11 @@ void Player::SetAnimationBasedOnTargetingDirection(const std::string_view animat
} }
SetFacingDirection(facingKey); SetFacingDirection(facingKey);
UpdateAnimation(std::format("{}_{}_{}",Capitalize(GetClassName()),animation_name,facingChar));
std::string newAnimState{std::format("{}_{}_{}",Capitalize(GetClassName()),animation_name,facingChar)};
if(animation.HasState(newAnimState))UpdateAnimation(newAnimState);
else ERR(std::format("WARNING! Animation {} does not exist on the player! THIS SHOULD NOT BE HAPPENING!",newAnimState));
} }
void Player::ApplyIframes(float duration){ void Player::ApplyIframes(float duration){
@ -1329,7 +1317,7 @@ void EntityStats::RecalculateEquipStats(){
for(auto&[key,size]:ItemAttribute::attributes){ for(auto&[key,size]:ItemAttribute::attributes){
equipStats.A(key)+=equip.lock()->GetStats().A_Read(key); equipStats.A(key)+=equip.lock()->GetStats().A_Read(key);
equipStats.A(key)+=equip.lock()->RandomStats().A_Read(key); equipStats.A(key)+=equip.lock()->RandomStats().A_Read(key);
if(slot==EquipSlot::RING2&&equip.lock()->HasEnchant()){ //Special doubling-up ring logic. if(slot&EquipSlot::RING2&&equip.lock()->HasEnchant()){ //Special doubling-up ring logic.
const bool IsNonCommonEnchant{equip.lock()->HasEnchant()&&equip.lock()->GetEnchant().value().Category()!=ItemEnchantInfo::ItemEnchantCategory::GENERAL}; const bool IsNonCommonEnchant{equip.lock()->HasEnchant()&&equip.lock()->GetEnchant().value().Category()!=ItemEnchantInfo::ItemEnchantCategory::GENERAL};
const bool Slot1HasSameEnchant{!ISBLANK(Inventory::GetEquip(EquipSlot::RING1))&&Inventory::GetEquip(EquipSlot::RING1).lock()->HasEnchant()&&Inventory::GetEquip(EquipSlot::RING1).lock()->GetEnchant().value().Name()==equip.lock()->GetEnchant().value().Name()}; const bool Slot1HasSameEnchant{!ISBLANK(Inventory::GetEquip(EquipSlot::RING1))&&Inventory::GetEquip(EquipSlot::RING1).lock()->HasEnchant()&&Inventory::GetEquip(EquipSlot::RING1).lock()->GetEnchant().value().Name()==equip.lock()->GetEnchant().value().Name()};
if(!IsNonCommonEnchant||!Slot1HasSameEnchant)equipStats.A(key)+=equip.lock()->GetEnchant().value().GetAttribute(key); if(!IsNonCommonEnchant||!Slot1HasSameEnchant)equipStats.A(key)+=equip.lock()->GetEnchant().value().GetAttribute(key);
@ -1419,15 +1407,37 @@ const float Player::GetDamageReductionFromArmor()const{
void Player::RecalculateEquipStats(){ void Player::RecalculateEquipStats(){
stats.RecalculateEquipStats(); stats.RecalculateEquipStats();
enchantList.clear(); enchantList.clear();
enchantAbilityList.clear();
const auto EquipSlotHasEnchant=[](const EquipSlot slot,const std::string_view enchantToCheck){return !Inventory::GetEquip(slot).expired()&&Inventory::GetEquip(slot).lock()->GetEnchant().has_value()&&Inventory::GetEquip(slot).lock()->GetEnchant().value().Name()==enchantToCheck;}; const auto EquipSlotHasEnchant=[](const EquipSlot slot,const std::string_view enchantToCheck){return !Inventory::GetEquip(slot).expired()&&Inventory::GetEquip(slot).lock()->GetEnchant().has_value()&&Inventory::GetEquip(slot).lock()->GetEnchant().value().Name()==enchantToCheck;};
for(int i=int(EquipSlot::RING2);i>=int(EquipSlot::HELMET);i>>=1){ //I'm inverting the order of this equip slot check because typically the player will have enchants in the ring slots, so check those earlier to terminate the loop quicker. for(int i=int(EquipSlot::RING2);i>=int(EquipSlot::HELMET);i>>=1){ //I'm inverting the order of this equip slot check because typically the player will have enchants in the ring slots, so check those earlier to terminate the loop quicker.
EquipSlot slot=EquipSlot(i); EquipSlot slot=EquipSlot(i);
if(!ISBLANK(Inventory::GetEquip(slot))&&Inventory::GetEquip(slot).lock()->GetEnchant().has_value())enchantList.insert(Inventory::GetEquip(slot).lock()->GetEnchant().value().Name()); if(!ISBLANK(Inventory::GetEquip(slot))&&Inventory::GetEquip(slot).lock()->GetEnchant().has_value()){
const ItemEnchant enchant{*Inventory::GetEquip(slot).lock()->GetEnchant()};
enchantList.insert(enchant.Name());
if(enchant.Ability())enchantAbilityList.insert((*enchant.Ability()).get().name);
}
} }
if(GetClass()==RANGER){ #pragma region Reset Abilities before Enchant Modifications
GetAbility2().precastInfo.castTime=Ranger::ability2.precastInfo.castTime; //This resets the cast time of this ability since it's possible for an enchant to modify it. Warrior::ResetToOriginalAbilities();
Ranger::ResetToOriginalAbilities();
Wizard::ResetToOriginalAbilities();
Thief::ResetToOriginalAbilities();
Trapper::ResetToOriginalAbilities();
Witch::ResetToOriginalAbilities();
#pragma endregion
if(GetClass()&RANGER){
if(HasEnchant("Mega Charged Shot"))GetAbility2().precastInfo.castTime+="Mega Charged Shot"_ENC["CAST TIME INCREASE"]; if(HasEnchant("Mega Charged Shot"))GetAbility2().precastInfo.castTime+="Mega Charged Shot"_ENC["CAST TIME INCREASE"];
if(HasEnchant("Multi-Multishot")){
GetAbility3().MAX_CHARGES="Multi-Multishot"_ENC["EXTRA CHARGE COUNT"];
GetAbility3().COOLDOWN_TIME-=GetAbility3().COOLDOWN_TIME*"Multi-Multishot"_ENC["COOLDOWN REDUCTION PCT"]/100.f;
}
}
for(const std::reference_wrapper<Ability>&a:GetAbilities()){
if(a.get().itemAbility)continue;
if(a.get().charges<a.get().MAX_CHARGES&&a.get().cooldown==0.f)a.get().cooldown=a.get().GetCooldownTime();
} }
} }
@ -1803,14 +1813,15 @@ const bool Player::IsAlive()const{
} }
void Player::CheckAndPerformAbility(Ability&ability,InputGroup key){ void Player::CheckAndPerformAbility(Ability&ability,InputGroup key){
const bool AllowedToCast=!ability.precastInfo.precastTargetingRequired&&GetState()!=State::ANIMATION_LOCK; if(key.Released())ability.keyReleaseRequiredToReactivate=false;
const bool AllowedToCast=!ability.precastInfo.precastTargetingRequired&&GetState()!=State::ANIMATION_LOCK&&(game->TestingModeEnabled()||!ability.keyReleaseRequiredToReactivate);
const bool HasEnoughOfItem=!ability.itemAbility|| const bool HasEnoughOfItem=!ability.itemAbility||
&ability==&useItem1&&game->GetLoadoutItem(0).lock()->Amt()>0|| &ability==&useItem1&&game->GetLoadoutItem(0).lock()->Amt()>0||
&ability==&useItem2&&game->GetLoadoutItem(1).lock()->Amt()>0|| &ability==&useItem2&&game->GetLoadoutItem(1).lock()->Amt()>0||
&ability==&useItem3&&game->GetLoadoutItem(2).lock()->Amt()>0; &ability==&useItem3&&game->GetLoadoutItem(2).lock()->Amt()>0;
if(ability.name!="???"){ if(ability.name!="???"){
if(CanAct(ability)){ if(CanAct(ability)){
if(ability.cooldown==0&&GetMana()>=ability.manaCost){ if(ability.charges>=1&&GetMana()>=ability.manaCost){
if(key.Held()||key.Released()&&&ability==castPrepAbility&&GetState()==State::PREP_CAST){ if(key.Held()||key.Released()&&&ability==castPrepAbility&&GetState()==State::PREP_CAST){
#pragma region Tutorial Defensive/Use Ability Tasks #pragma region Tutorial Defensive/Use Ability Tasks
if(&ability==&GetRightClickAbility()){ if(&ability==&GetRightClickAbility()){
@ -1821,9 +1832,11 @@ void Player::CheckAndPerformAbility(Ability&ability,InputGroup key){
#pragma endregion #pragma endregion
if(AllowedToCast&&ability.action(this,{})){ if(AllowedToCast&&ability.action(this,{})){
bool allowed=ability.actionPerformedDuringCast; bool allowed=ability.actionPerformedDuringCast;
ability.cooldown=ability.GetCooldownTime(); if(ability.cooldown==0.f)ability.cooldown=ability.GetCooldownTime();
ability.charges--;
CancelCast(); CancelCast();
ConsumeMana(ability.manaCost); ConsumeMana(ability.manaCost);
ability.keyReleaseRequiredToReactivate=true;
OnAbilityUse(ability); OnAbilityUse(ability);
}else }else
if(ability.precastInfo.precastTargetingRequired&&GetState()==State::NORMAL&&HasEnoughOfItem){ if(ability.precastInfo.precastTargetingRequired&&GetState()==State::NORMAL&&HasEnoughOfItem){
@ -1994,4 +2007,12 @@ const vf2d Player::GetFacingDirVector()const{
const int Player::RemainingRapidFireShots()const{ const int Player::RemainingRapidFireShots()const{
return remainingRapidFireShots; return remainingRapidFireShots;
}
const std::vector<std::reference_wrapper<Ability>>Player::GetAbilities(){
return {GetRightClickAbility(),GetAbility1(),GetAbility2(),GetAbility3(),GetAbility4(),GetItem1(),GetItem2(),GetItem3()};
}
const bool Player::HasEnchantWithAbilityAffected(const std::string_view abilityName)const{
return enchantAbilityList.count(std::string(abilityName));
} }

@ -308,6 +308,8 @@ public:
const vf2d GetFacingDirVector()const; //Returns a normalized vector based on the facing direction of the character. Ex. {0,-1} for north and {1,0} for east. const vf2d GetFacingDirVector()const; //Returns a normalized vector based on the facing direction of the character. Ex. {0,-1} for north and {1,0} for east.
const bool PoisonArrowAutoAttackReady()const; //NOTE: Also checks to make sure we have the enchant built-in... const bool PoisonArrowAutoAttackReady()const; //NOTE: Also checks to make sure we have the enchant built-in...
const int RemainingRapidFireShots()const; const int RemainingRapidFireShots()const;
const std::vector<std::reference_wrapper<Ability>>GetAbilities();//Returns player defensive, core abilities (1-4) and item abilities.
const bool HasEnchantWithAbilityAffected(const std::string_view abilityName)const; //Returns whether or not the player has an enchant that affects the provided name.
private: private:
int hp="Warrior.BaseHealth"_I; int hp="Warrior.BaseHealth"_I;
int mana="Player.BaseMana"_I; int mana="Player.BaseMana"_I;
@ -385,6 +387,7 @@ private:
std::unordered_set<std::string>myClass{}; std::unordered_set<std::string>myClass{};
float daggerThrowWaitTimer{INFINITY}; float daggerThrowWaitTimer{INFINITY};
std::unordered_set<std::string>enchantList; std::unordered_set<std::string>enchantList;
std::unordered_set<std::string>enchantAbilityList; //Which abilities are currently affected by our enchants.
void OnAbilityUse(const Ability&ability); //Callback when an ability successfully is used and has gone on cooldown. void OnAbilityUse(const Ability&ability); //Callback when an ability successfully is used and has gone on cooldown.
const bool LastReserveEnchantConditionsMet()const; const bool LastReserveEnchantConditionsMet()const;
float poisonArrowLastParticleTimer{}; float poisonArrowLastParticleTimer{};
@ -471,6 +474,10 @@ struct Warrior:Player{
std::string&GetIdleEAnimation()override; std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()override; std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override; std::string&GetIdleWAnimation()override;
static void CreateOriginalCopies();
static void ResetToOriginalAbilities();
private:
static Ability original_rightClickAbility,original_ability1,original_ability2,original_ability3,original_ability4;
}; };
#pragma endregion #pragma endregion
@ -502,6 +509,10 @@ struct Thief:Player{
std::string&GetIdleEAnimation()override; std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()override; std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override; std::string&GetIdleWAnimation()override;
static void CreateOriginalCopies();
static void ResetToOriginalAbilities();
private:
static Ability original_rightClickAbility,original_ability1,original_ability2,original_ability3,original_ability4;
}; };
#pragma endregion #pragma endregion
@ -533,6 +544,10 @@ struct Ranger:Player{
std::string&GetIdleEAnimation()override; std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()override; std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override; std::string&GetIdleWAnimation()override;
static void CreateOriginalCopies();
static void ResetToOriginalAbilities();
private:
static Ability original_rightClickAbility,original_ability1,original_ability2,original_ability3,original_ability4;
}; };
#pragma endregion #pragma endregion
@ -564,6 +579,10 @@ struct Trapper:Player{
std::string&GetIdleEAnimation()override; std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()override; std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override; std::string&GetIdleWAnimation()override;
static void CreateOriginalCopies();
static void ResetToOriginalAbilities();
private:
static Ability original_rightClickAbility,original_ability1,original_ability2,original_ability3,original_ability4;
}; };
#pragma endregion #pragma endregion
@ -595,6 +614,10 @@ struct Wizard:Player{
std::string&GetIdleEAnimation()override; std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()override; std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override; std::string&GetIdleWAnimation()override;
static void CreateOriginalCopies();
static void ResetToOriginalAbilities();
private:
static Ability original_rightClickAbility,original_ability1,original_ability2,original_ability3,original_ability4;
}; };
#pragma endregion #pragma endregion
@ -626,6 +649,10 @@ struct Witch:Player{
std::string&GetIdleEAnimation()override; std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()override; std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override; std::string&GetIdleWAnimation()override;
static void CreateOriginalCopies();
static void ResetToOriginalAbilities();
private:
static Ability original_rightClickAbility,original_ability1,original_ability2,original_ability3,original_ability4;
}; };
#pragma endregion #pragma endregion
@ -684,4 +711,4 @@ struct Witch:Player{
{#class".Ability 3.Precast Time"_F,#class".Ability 3.Casting Range"_I/100.f*24,#class".Ability 3.Casting Size"_I/100.f*24}, \ {#class".Ability 3.Precast Time"_F,#class".Ability 3.Casting Range"_I/100.f*24,#class".Ability 3.Casting Size"_I/100.f*24}, \
bool(#class".Ability 3.CancelCast"_I) \ bool(#class".Ability 3.CancelCast"_I) \
}; \ }; \
class::ability4; class::ability4;

@ -59,6 +59,7 @@ void Ranger::Initialize(){
Ranger::walk_e="RANGER_WALK_E"; Ranger::walk_e="RANGER_WALK_E";
Ranger::walk_s="RANGER_WALK_S"; Ranger::walk_s="RANGER_WALK_S";
Ranger::walk_w="RANGER_WALK_W"; Ranger::walk_w="RANGER_WALK_W";
Ranger::ability4={};
} }
SETUP_CLASS(Ranger) SETUP_CLASS(Ranger)

@ -60,6 +60,7 @@ void Thief::Initialize(){
Thief::walk_e="THIEF_WALK_E"; Thief::walk_e="THIEF_WALK_E";
Thief::walk_s="THIEF_WALK_S"; Thief::walk_s="THIEF_WALK_S";
Thief::walk_w="THIEF_WALK_W"; Thief::walk_w="THIEF_WALK_W";
Thief::ability4={};
} }
SETUP_CLASS(Thief) SETUP_CLASS(Thief)

@ -60,6 +60,7 @@ void Trapper::Initialize(){
Trapper::walk_e="TRAPPER_WALK_E"; Trapper::walk_e="TRAPPER_WALK_E";
Trapper::walk_s="TRAPPER_WALK_S"; Trapper::walk_s="TRAPPER_WALK_S";
Trapper::walk_w="TRAPPER_WALK_W"; Trapper::walk_w="TRAPPER_WALK_W";
Trapper::ability4={};
} }
SETUP_CLASS(Trapper) SETUP_CLASS(Trapper)

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

@ -60,6 +60,7 @@ void Witch::Initialize(){
Witch::walk_e="WITCH_WALK_E"; Witch::walk_e="WITCH_WALK_E";
Witch::walk_s="WITCH_WALK_S"; Witch::walk_s="WITCH_WALK_S";
Witch::walk_w="WITCH_WALK_W"; Witch::walk_w="WITCH_WALK_W";
Witch::ability4={};
} }
SETUP_CLASS(Witch) SETUP_CLASS(Witch)

@ -59,6 +59,7 @@ void Wizard::Initialize(){
Wizard::walk_e="WIZARD_WALK_E"; Wizard::walk_e="WIZARD_WALK_E";
Wizard::walk_s="WIZARD_WALK_S"; Wizard::walk_s="WIZARD_WALK_S";
Wizard::walk_w="WIZARD_WALK_W"; Wizard::walk_w="WIZARD_WALK_W";
Wizard::ability4={};
} }
SETUP_CLASS(Wizard) SETUP_CLASS(Wizard)

@ -2,7 +2,7 @@
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="238" height="369" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="127"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="238" height="369" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="127">
<properties> <properties>
<property name="Backdrop" propertytype="Backdrop" value="mountain_day"/> <property name="Backdrop" propertytype="Backdrop" value="mountain_day"/>
<property name="Background Music" propertytype="BGM" value="foresty1_1"/> <property name="Background Music" propertytype="BGM" value="mountain"/>
<property name="Level Type" propertytype="LevelType" value="Dungeon"/> <property name="Level Type" propertytype="LevelType" value="Dungeon"/>
</properties> </properties>
<tileset firstgid="1" source="../maps/Tilesheet_No_Shadow24x24.tsx"/> <tileset firstgid="1" source="../maps/Tilesheet_No_Shadow24x24.tsx"/>

@ -2,7 +2,7 @@
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="237" height="275" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="70"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="237" height="275" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="70">
<properties> <properties>
<property name="Backdrop" propertytype="Backdrop" value="mountain_day"/> <property name="Backdrop" propertytype="Backdrop" value="mountain_day"/>
<property name="Background Music" propertytype="BGM" value="foresty1_1"/> <property name="Background Music" propertytype="BGM" value="mountain"/>
<property name="Level Type" propertytype="LevelType" value="Dungeon"/> <property name="Level Type" propertytype="LevelType" value="Dungeon"/>
</properties> </properties>
<tileset firstgid="1" source="../maps/Tilesheet_No_Shadow24x24.tsx"/> <tileset firstgid="1" source="../maps/Tilesheet_No_Shadow24x24.tsx"/>

@ -2,7 +2,7 @@
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="262" height="167" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="72"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="262" height="167" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="72">
<properties> <properties>
<property name="Backdrop" propertytype="Backdrop" value="mountain_day"/> <property name="Backdrop" propertytype="Backdrop" value="mountain_day"/>
<property name="Background Music" propertytype="BGM" value="foresty1_1"/> <property name="Background Music" propertytype="BGM" value="mountain"/>
<property name="Level Type" propertytype="LevelType" value="Dungeon"/> <property name="Level Type" propertytype="LevelType" value="Dungeon"/>
</properties> </properties>
<tileset firstgid="1" source="../maps/Tilesheet_No_Shadow24x24.tsx"/> <tileset firstgid="1" source="../maps/Tilesheet_No_Shadow24x24.tsx"/>

@ -2,7 +2,7 @@
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="317" height="174" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="84"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="317" height="174" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="84">
<properties> <properties>
<property name="Backdrop" propertytype="Backdrop" value="mountain_day"/> <property name="Backdrop" propertytype="Backdrop" value="mountain_day"/>
<property name="Background Music" propertytype="BGM" value="foresty1_1"/> <property name="Background Music" propertytype="BGM" value="mountain"/>
<property name="Level Type" propertytype="LevelType" value="Dungeon"/> <property name="Level Type" propertytype="LevelType" value="Dungeon"/>
</properties> </properties>
<tileset firstgid="1" source="../maps/Tilesheet_No_Shadow24x24.tsx"/> <tileset firstgid="1" source="../maps/Tilesheet_No_Shadow24x24.tsx"/>

@ -2,7 +2,7 @@
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="303" height="211" tilewidth="24" tileheight="24" infinite="0" nextlayerid="10" nextobjectid="99"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="303" height="211" tilewidth="24" tileheight="24" infinite="0" nextlayerid="10" nextobjectid="99">
<properties> <properties>
<property name="Backdrop" propertytype="Backdrop" value="mountain_day"/> <property name="Backdrop" propertytype="Backdrop" value="mountain_day"/>
<property name="Background Music" propertytype="BGM" value="foresty1_1"/> <property name="Background Music" propertytype="BGM" value="mountain"/>
<property name="Level Type" propertytype="LevelType" value="Dungeon"/> <property name="Level Type" propertytype="LevelType" value="Dungeon"/>
</properties> </properties>
<tileset firstgid="1" source="../maps/Tilesheet_No_Shadow24x24.tsx"/> <tileset firstgid="1" source="../maps/Tilesheet_No_Shadow24x24.tsx"/>

@ -2,7 +2,7 @@
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="440" height="114" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="75"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="440" height="114" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="75">
<properties> <properties>
<property name="Backdrop" propertytype="Backdrop" value="mountain_day"/> <property name="Backdrop" propertytype="Backdrop" value="mountain_day"/>
<property name="Background Music" propertytype="BGM" value="foresty1_1"/> <property name="Background Music" propertytype="BGM" value="mountain"/>
<property name="Level Type" propertytype="LevelType" value="Dungeon"/> <property name="Level Type" propertytype="LevelType" value="Dungeon"/>
</properties> </properties>
<tileset firstgid="1" source="../maps/objects.tsx"/> <tileset firstgid="1" source="../maps/objects.tsx"/>

@ -2,7 +2,7 @@
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="179" height="219" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="72"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="179" height="219" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="72">
<properties> <properties>
<property name="Backdrop" propertytype="Backdrop" value="mountain_day"/> <property name="Backdrop" propertytype="Backdrop" value="mountain_day"/>
<property name="Background Music" propertytype="BGM" value="foresty1_1"/> <property name="Background Music" propertytype="BGM" value="mountain"/>
<property name="Level Type" propertytype="LevelType" value="Dungeon"/> <property name="Level Type" propertytype="LevelType" value="Dungeon"/>
</properties> </properties>
<tileset firstgid="1" source="../maps/Decorations_c1_No_Shadow24x24.tsx"/> <tileset firstgid="1" source="../maps/Decorations_c1_No_Shadow24x24.tsx"/>

@ -2,7 +2,7 @@
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="198" height="179" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="144"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="198" height="179" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="144">
<properties> <properties>
<property name="Backdrop" propertytype="Backdrop" value="mountain_day"/> <property name="Backdrop" propertytype="Backdrop" value="mountain_day"/>
<property name="Background Music" propertytype="BGM" value="foresty1_1"/> <property name="Background Music" propertytype="BGM" value="mountain"/>
<property name="Level Type" propertytype="LevelType" value="Dungeon"/> <property name="Level Type" propertytype="LevelType" value="Dungeon"/>
</properties> </properties>
<tileset firstgid="1" source="../maps/Decorations_c1_No_Shadow24x24.tsx"/> <tileset firstgid="1" source="../maps/Decorations_c1_No_Shadow24x24.tsx"/>

@ -2,7 +2,7 @@
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="419" height="158" tilewidth="24" tileheight="24" infinite="0" nextlayerid="6" nextobjectid="54"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="419" height="158" tilewidth="24" tileheight="24" infinite="0" nextlayerid="6" nextobjectid="54">
<properties> <properties>
<property name="Backdrop" propertytype="Backdrop" value="mountain_day"/> <property name="Backdrop" propertytype="Backdrop" value="mountain_day"/>
<property name="Background Music" propertytype="BGM" value="foresty1_1"/> <property name="Background Music" propertytype="BGM" value="mountain"/>
<property name="Level Type" propertytype="LevelType" value="Dungeon"/> <property name="Level Type" propertytype="LevelType" value="Dungeon"/>
</properties> </properties>
<tileset firstgid="1" source="../maps/Tilesheet_No_Shadow24x24.tsx"/> <tileset firstgid="1" source="../maps/Tilesheet_No_Shadow24x24.tsx"/>

@ -117,4 +117,20 @@ BGM
Default Volume = 20%,50%,20%,70% Default Volume = 20%,50%,20%,70%
} }
} }
#Song title followed by filenames for individual parts
mountain
{
Track Name = Foresty
channel[0]=AiL_mountain2.ogg
# Transition time between one phase to the next.
Fade Time = 2.0
Events
{
Default Volume = 70%
}
}
} }

@ -6,7 +6,7 @@ Credits
LINE[3]="Production & Programming" LINE[3]="Production & Programming"
LINE[4]="#C03EE5sigonasr2#FFFFFF" LINE[4]="#C03EE5sigonasr2#FFFFFF"
LINE[5]=" " LINE[5]=" "
LINE[6]="Game Designer" LINE[6]="Game Design & Level Creation"
LINE[7]="#7DDA58Quapsel#FFFFFF" LINE[7]="#7DDA58Quapsel#FFFFFF"
LINE[8]=" " LINE[8]=" "
LINE[9]="Music Design" LINE[9]="Music Design"

@ -212,4 +212,9 @@ void util::turn_towards_direction(float&angle,float target,float rate)
if(diff>0&&newAngleDiff<0|| if(diff>0&&newAngleDiff<0||
diff<0&&newAngleDiff>0)angle=fmod(target,2*PI); //We have crossed the angle difference threshold and can safely say we reached it. diff<0&&newAngleDiff>0)angle=fmod(target,2*PI); //We have crossed the angle difference threshold and can safely say we reached it.
} }
std::wstring util::to_wstring(const std::string&str){
return {str.begin(),str.end()};
}

@ -63,6 +63,16 @@ namespace olc::util{
const float distance(const vf2d&point1,const vf2d&point2); const float distance(const vf2d&point1,const vf2d&point2);
//Modifies angle argument directly to turn towards said direction. rate is in radians, please multiply by fElapsedTime if you intend to use this per frame. //Modifies angle argument directly to turn towards said direction. rate is in radians, please multiply by fElapsedTime if you intend to use this per frame.
void turn_towards_direction(float&angle,float target,float rate); void turn_towards_direction(float&angle,float target,float rate);
std::wstring to_wstring(const std::string&str);
template<class..._Args>
std::string vformat(std::string str,_Args&..._Vals){
return std::vformat(str,std::make_format_args(_Vals...));
}
template<class..._Args>
std::wstring wformat(std::string str,_Args&..._Vals){
return util::to_wstring(std::vformat(str,std::make_format_args(_Vals...)));
}
} }
template<class TL, class TR> template<class TL, class TR>

Loading…
Cancel
Save