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");
player->SetState(State::NORMAL);
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->CheckAndPerformAbility(player->GetRightClickAbility(),testKeyboardInput);
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.");
player->SetState(State::NORMAL);
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->CheckAndPerformAbility(player->GetAbility1(),testKeyboardInput);
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.");
player->SetState(State::NORMAL);
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->CheckAndPerformAbility(player->GetAbility2(),testKeyboardInput);
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.");
player->SetState(State::NORMAL);
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->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
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.");
player->SetState(State::NORMAL);
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->CheckAndPerformAbility(player->GetAbility4(),testKeyboardInput);
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);
Assert::AreEqual("Ranger.Right Click Ability.RetreatTime"_F,player->GetIframeTime(),L"Ranger's retreat iframe time is normal.");
player->_SetIframes(0.f);
player->GetRightClickAbility().cooldown=0.f;
player->GetRightClickAbility().charges=1;
player->SetState(State::NORMAL);
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
Inventory::EquipItem(nullRing,EquipSlot::RING1);
@ -565,7 +570,7 @@ namespace EnchantTests
game->OnUserUpdate(1.f);
}
player->SetState(State::NORMAL);
player->GetAbility1().cooldown=0.f;
player->GetAbility1().charges=1;
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
Inventory::EquipItem(nullRing,EquipSlot::RING1);
nullRing.lock()->EnchantItem("Extreme Rapid Fire");
@ -575,7 +580,7 @@ namespace EnchantTests
TEST_METHOD(MegaChargedShotCheck){
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
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.");
game->ChangePlayerClass(RANGER);
player=game->GetPlayer();
@ -583,5 +588,18 @@ namespace EnchantTests
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.");
}
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 "ItemDrop.h"
#include "DamageNumber.h"
#include <ranges>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
@ -434,6 +435,18 @@ namespace PlayerTests
player->CheckAndPerformAbility(player->GetAbility2(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetAbility3(),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->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.");
@ -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->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::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){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
@ -680,5 +696,58 @@ namespace PlayerTests
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.");
}
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="";
float cooldown=0;
float COOLDOWN_TIME=0;
uint8_t charges{1};
uint8_t MAX_CHARGES{1};
int manaCost=0;
Pixel barColor1,barColor2;
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.
bool actionPerformedDuringCast=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.
// Argument 1: Player* - player pointer
// Argument 2: vf2d - The returned precast target position (if the ability needs to be aimed, otherwise {})

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

@ -2084,7 +2084,7 @@ void AiL::RenderHud(){
}
void AiL::RenderCooldowns(){
std::vector<Ability>cooldowns{
std::vector<std::reference_wrapper<Ability>>cooldowns{
player->GetAbility1(),
player->GetAbility2(),
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*/){
bool circle=loadoutSlot==-1;
if(a.name!="???"){
const bool HasEnchantWithAbilityAffected{player->HasEnchantWithAbilityAffected(a.name)};
vf2d keyDisplaySize=vf2d{GetTextSize(a.input->GetDisplayName())}*vf2d{0.5f,0.5f};
InputType controlType=KEY;
@ -2107,15 +2108,16 @@ void AiL::RenderCooldowns(){
if(a.cooldown>0.1){
vf2d iconScale={1,1};
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){
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{
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;
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{
vf2d iconScale={1,1};
if(loadoutSlot!=-1)iconScale={0.7f,0.7f};
@ -2134,6 +2136,9 @@ void AiL::RenderCooldowns(){
}
Pixel shortNameCol=BLUE,manaCostShadowCol=DARK_BLUE,keyDisplayCol=YELLOW;
if(HasEnchantWithAbilityAffected)shortNameCol={255,255,0};
if(player->GetMana()<a.manaCost){
Decal*overlayOverlayIcon=GFX["skill_overlay_icon_overlay.png"].Decal();
if(!circle)overlayOverlayIcon=GFX["square_skill_overlay_icon_empty.png"].Decal();
@ -2142,7 +2147,7 @@ void AiL::RenderCooldowns(){
manaCostShadowCol=DARK_RED;
keyDisplayCol=RED;
}else
if(a.cooldown>0){
if(a.charges==0){
shortNameCol=GREY;
manaCostShadowCol=DARK_GREY;
keyDisplayCol=GREY;
@ -2155,7 +2160,7 @@ void AiL::RenderCooldowns(){
if(itemAmt>0){
std::string amtString="x"+std::to_string(itemAmt);
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{
DrawDecal(pos,GFX["square_skill_overlay_icon_empty.png"].Decal(),{1,1},DARK_RED);
shortNameCol=RED;
@ -2171,7 +2176,16 @@ void AiL::RenderCooldowns(){
}
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)
LoadingScreen::AddPhase([&](){
player->GetAbility1().cooldown=0.f;
player->GetAbility2().cooldown=0.f;
player->GetAbility3().cooldown=0.f;
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;
for(const std::reference_wrapper<Ability>&a:player->GetAbilities()){
a.get().cooldown=0.f;
a.get().charges=a.get().MAX_CHARGES;
}
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.
@ -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()));
Player::moneyListeners=moneyListeners;
GetPlayer()->InitializeMinimapImage();
player->RecalculateEquipStats();
}
void AiL::InitializeClasses(){
@ -3012,6 +3023,13 @@ void AiL::InitializeClasses(){
Trapper::InitializeClassAbilities();
Wizard::InitializeClassAbilities();
Witch::InitializeClassAbilities();
Warrior::CreateOriginalCopies();
Thief::CreateOriginalCopies();
Ranger::CreateOriginalCopies();
Trapper::CreateOriginalCopies();
Wizard::CreateOriginalCopies();
Witch::CreateOriginalCopies();
}
std::string AiL::GetString(std::string key){

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

@ -85,6 +85,30 @@ class::class() \
class::class(Player*player) \
:Player::Player(player){} \
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;} \
Ability&class::GetRightClickAbility(){return rightClickAbility;}; \
Ability&class::GetAbility1(){return ability1;}; \
@ -106,6 +130,11 @@ Ability class::ability1=Ability(); \
Ability class::ability2=Ability(); \
Ability class::ability3=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_e="WARRIOR_IDLE_E"; \
std::string class::idle_s="WARRIOR_IDLE_S"; \

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

@ -124,7 +124,7 @@ void ItemEnchantInfo::Initialize(){
}
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)};
if(configValInd==std::string::npos)continue;
std::string formattedFloat{std::format("{}{}#FFFFFF",ItemEnchantInfo::enchantAttributeCol.toHTMLColorCode(),val)};
@ -246,7 +246,7 @@ const std::string ItemEnchant::RollRandomEnchant(){
void ItemEnchant::UpdateDescription(){
description=ItemEnchantInfo::ENCHANT_LIST.at(this->enchantName).Description();
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)};
if(configValInd==std::string::npos)continue;
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{
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 "Pixel.h"
#include "Class.h"
#include "Ability.h"
class ItemEnchantInfo{
friend class ItemEnchant;
@ -55,6 +56,13 @@ public:
CLASS,
UNIQUE,
};
enum class AbilitySlot{
AUTO_ATTACK,
RIGHT_CLICK,
ABILITY_1,
ABILITY_2,
ABILITY_3,
};
const static Pixel enchantAttributeCol;
@ -66,14 +74,9 @@ public:
const std::string_view Description()const;
const ItemEnchantCategory&Category()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;
enum class AbilitySlot{
AUTO_ATTACK,
RIGHT_CLICK,
ABILITY_1,
ABILITY_2,
ABILITY_3,
};
const float operator[](const std::string& name)const;
private:
class ItemEnchantCategoryData{
@ -109,6 +112,7 @@ public:
std::map<ItemAttribute,float>::const_iterator begin()const;
std::map<ItemAttribute,float>::const_iterator end()const;
const Pixel&DisplayCol()const;
const std::optional<std::reference_wrapper<Ability>>Ability()const;
private:
void UpdateDescription();
const ItemEnchantInfo&GetEnchantInfo()const;

@ -378,7 +378,8 @@ void Player::Update(float fElapsedTime){
SetState(State::NORMAL);
if(!allowed&&castPrepAbility->action(this,castInfo.castPos))allowed=true;
if(allowed){
castPrepAbility->cooldown=castPrepAbility->GetCooldownTime();
if(castPrepAbility->cooldown==0.f)castPrepAbility->cooldown=castPrepAbility->GetCooldownTime();
castPrepAbility->charges--;
ConsumeMana(castPrepAbility->manaCost);
OnAbilityUse(ability);
}
@ -572,38 +573,21 @@ void Player::Update(float fElapsedTime){
}else{
cooldownMultiplier=1/(1-game->GetPlayer()->GetCooldownReductionPct());
}
rightClickAbility.cooldown-=fElapsedTime*cooldownMultiplier;
ability.cooldown-=fElapsedTime*cooldownMultiplier;
ability2.cooldown-=fElapsedTime*cooldownMultiplier;
ability3.cooldown-=fElapsedTime*cooldownMultiplier;
ability4.cooldown-=fElapsedTime*cooldownMultiplier;
item1.cooldown-=fElapsedTime;
item2.cooldown-=fElapsedTime;
item3.cooldown-=fElapsedTime;
if(rightClickAbility.cooldown<0){
rightClickAbility.cooldown=0;
}
if(ability.cooldown<0){
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;
}
const std::vector<std::reference_wrapper<Ability>>playerAbilities{GetAbilities()};
std::for_each(playerAbilities.begin(),playerAbilities.end(),[&fElapsedTime,&cooldownMultiplier](std::reference_wrapper<Ability>a){
//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.
if(a.get().itemAbility)a.get().cooldown=std::min(a.get().GetCooldownTime(),a.get().cooldown-fElapsedTime); //Item abilities are not affected by CDR.
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.
else a.get().cooldown=std::min(a.get().GetCooldownTime(),a.get().cooldown-fElapsedTime*cooldownMultiplier);
if(a.get().cooldown<=0.f){
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();
else a.get().cooldown=0.f;
}
});
for(std::shared_ptr<Monster>&m:MONSTER_LIST){
const float playerRadius{12*GetSizeMult()/2};
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);
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)));
SetAnimationBasedOnTargetingDirection("SHOOT",angleToCursor);
if(GetClass()&(RANGER|TRAPPER))SetAnimationBasedOnTargetingDirection("SHOOT",angleToCursor);
rapidFireTimer=RAPID_FIRE_SHOOT_DELAY;
}else{
SetState(State::NORMAL);
@ -1218,7 +1202,11 @@ void Player::SetAnimationBasedOnTargetingDirection(const std::string_view animat
}
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){
@ -1329,7 +1317,7 @@ void EntityStats::RecalculateEquipStats(){
for(auto&[key,size]:ItemAttribute::attributes){
equipStats.A(key)+=equip.lock()->GetStats().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 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);
@ -1419,15 +1407,37 @@ const float Player::GetDamageReductionFromArmor()const{
void Player::RecalculateEquipStats(){
stats.RecalculateEquipStats();
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;};
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);
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){
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.
#pragma region Reset Abilities before Enchant Modifications
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("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){
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||
&ability==&useItem1&&game->GetLoadoutItem(0).lock()->Amt()>0||
&ability==&useItem2&&game->GetLoadoutItem(1).lock()->Amt()>0||
&ability==&useItem3&&game->GetLoadoutItem(2).lock()->Amt()>0;
if(ability.name!="???"){
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){
#pragma region Tutorial Defensive/Use Ability Tasks
if(&ability==&GetRightClickAbility()){
@ -1821,9 +1832,11 @@ void Player::CheckAndPerformAbility(Ability&ability,InputGroup key){
#pragma endregion
if(AllowedToCast&&ability.action(this,{})){
bool allowed=ability.actionPerformedDuringCast;
ability.cooldown=ability.GetCooldownTime();
if(ability.cooldown==0.f)ability.cooldown=ability.GetCooldownTime();
ability.charges--;
CancelCast();
ConsumeMana(ability.manaCost);
ability.keyReleaseRequiredToReactivate=true;
OnAbilityUse(ability);
}else
if(ability.precastInfo.precastTargetingRequired&&GetState()==State::NORMAL&&HasEnoughOfItem){
@ -1994,4 +2007,12 @@ const vf2d Player::GetFacingDirVector()const{
const int Player::RemainingRapidFireShots()const{
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 bool PoisonArrowAutoAttackReady()const; //NOTE: Also checks to make sure we have the enchant built-in...
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:
int hp="Warrior.BaseHealth"_I;
int mana="Player.BaseMana"_I;
@ -385,6 +387,7 @@ private:
std::unordered_set<std::string>myClass{};
float daggerThrowWaitTimer{INFINITY};
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.
const bool LastReserveEnchantConditionsMet()const;
float poisonArrowLastParticleTimer{};
@ -471,6 +474,10 @@ struct Warrior:Player{
std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()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
@ -502,6 +509,10 @@ struct Thief:Player{
std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()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
@ -533,6 +544,10 @@ struct Ranger:Player{
std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()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
@ -564,6 +579,10 @@ struct Trapper:Player{
std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()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
@ -595,6 +614,10 @@ struct Wizard:Player{
std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()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
@ -626,6 +649,10 @@ struct Witch:Player{
std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()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
@ -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}, \
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_s="RANGER_WALK_S";
Ranger::walk_w="RANGER_WALK_W";
Ranger::ability4={};
}
SETUP_CLASS(Ranger)

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

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

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

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

@ -59,6 +59,7 @@ void Wizard::Initialize(){
Wizard::walk_e="WIZARD_WALK_E";
Wizard::walk_s="WIZARD_WALK_S";
Wizard::walk_w="WIZARD_WALK_W";
Wizard::ability4={};
}
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">
<properties>
<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"/>
</properties>
<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">
<properties>
<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"/>
</properties>
<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">
<properties>
<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"/>
</properties>
<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">
<properties>
<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"/>
</properties>
<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">
<properties>
<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"/>
</properties>
<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">
<properties>
<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"/>
</properties>
<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">
<properties>
<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"/>
</properties>
<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">
<properties>
<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"/>
</properties>
<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">
<properties>
<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"/>
</properties>
<tileset firstgid="1" source="../maps/Tilesheet_No_Shadow24x24.tsx"/>

@ -117,4 +117,20 @@ BGM
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[4]="#C03EE5sigonasr2#FFFFFF"
LINE[5]=" "
LINE[6]="Game Designer"
LINE[6]="Game Design & Level Creation"
LINE[7]="#7DDA58Quapsel#FFFFFF"
LINE[8]=" "
LINE[9]="Music Design"

@ -212,4 +212,9 @@ void util::turn_towards_direction(float&angle,float target,float rate)
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.
}
}
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);
//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);
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>

Loading…
Cancel
Save