Compare commits

..

1 Commits

  1. 11
      Adventures in Lestoria Tests/Adventures in Lestoria Tests.vcxproj
  2. 11
      Adventures in Lestoria Tests/Adventures in Lestoria Tests.vcxproj.filters
  3. 165
      Adventures in Lestoria Tests/BuffTests.cpp
  4. 889
      Adventures in Lestoria Tests/EnchantTests.cpp
  5. 69
      Adventures in Lestoria Tests/EngineTests.cpp
  6. 113
      Adventures in Lestoria Tests/FileTests.cpp
  7. 80
      Adventures in Lestoria Tests/GameHelper.h
  8. 114
      Adventures in Lestoria Tests/ItemTests.cpp
  9. 107
      Adventures in Lestoria Tests/MonsterTests.cpp
  10. 68
      Adventures in Lestoria Tests/PlayerTests.cpp
  11. 4
      Adventures in Lestoria/Ability.cpp
  12. 2
      Adventures in Lestoria/Ability.h
  13. 47
      Adventures in Lestoria/Adventures in Lestoria.tiled-project
  14. 92
      Adventures in Lestoria/Adventures in Lestoria.vcxproj
  15. 137
      Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters
  16. 322
      Adventures in Lestoria/AdventuresInLestoria.cpp
  17. 50
      Adventures in Lestoria/AdventuresInLestoria.h
  18. 40
      Adventures in Lestoria/Animation.cpp
  19. 79
      Adventures in Lestoria/Arc.cpp
  20. 59
      Adventures in Lestoria/Arc.h
  21. 50
      Adventures in Lestoria/ArtificerDisassembleConfirmWindow.cpp
  22. 150
      Adventures in Lestoria/ArtificerDisassembleWindow.cpp
  23. 73
      Adventures in Lestoria/ArtificerEnchantConfirmWindow.cpp
  24. 185
      Adventures in Lestoria/ArtificerEnchantWindow.cpp
  25. 42
      Adventures in Lestoria/ArtificerRefineConfirmWindow.cpp
  26. 75
      Adventures in Lestoria/ArtificerRefineResultWindow.cpp
  27. 164
      Adventures in Lestoria/ArtificerRefineWindow.cpp
  28. 28
      Adventures in Lestoria/ArtificerWindow.cpp
  29. 26
      Adventures in Lestoria/Attributable.h
  30. 3
      Adventures in Lestoria/AttributableStat.h
  31. 22
      Adventures in Lestoria/Audio.cpp
  32. 7
      Adventures in Lestoria/Audio.h
  33. 8
      Adventures in Lestoria/Bear.cpp
  34. 59
      Adventures in Lestoria/BlackHole.h
  35. 10
      Adventures in Lestoria/Boar.cpp
  36. 2
      Adventures in Lestoria/Bomb.cpp
  37. 4
      Adventures in Lestoria/BreakingPillar.cpp
  38. 23
      Adventures in Lestoria/Buff.cpp
  39. 9
      Adventures in Lestoria/Buff.h
  40. 2
      Adventures in Lestoria/Bullet.cpp
  41. 2
      Adventures in Lestoria/Bullet.h
  42. 48
      Adventures in Lestoria/BulletTypes.h
  43. 74
      Adventures in Lestoria/CharacterMenuWindow.cpp
  44. 13
      Adventures in Lestoria/ChargedArrow.cpp
  45. 7
      Adventures in Lestoria/ClassSelectionWindow.cpp
  46. 2
      Adventures in Lestoria/ConnectionPoint.cpp
  47. 118
      Adventures in Lestoria/Crab.cpp
  48. 2
      Adventures in Lestoria/CraftItemWindow.cpp
  49. 2
      Adventures in Lestoria/Crawler_Artificer.txt
  50. 30
      Adventures in Lestoria/DEFINES.h
  51. 4
      Adventures in Lestoria/DaggerSlash.cpp
  52. 4
      Adventures in Lestoria/DaggerStab.cpp
  53. 10
      Adventures in Lestoria/DamageNumber.cpp
  54. 1
      Adventures in Lestoria/DamageNumber.h
  55. 2
      Adventures in Lestoria/DeadlyDash.cpp
  56. 57
      Adventures in Lestoria/DynamicMenuLabel.h
  57. 41
      Adventures in Lestoria/Effect.cpp
  58. 76
      Adventures in Lestoria/Effect.h
  59. 5
      Adventures in Lestoria/EnergyBolt.cpp
  60. 3
      Adventures in Lestoria/EnvironmentalAudio.cpp
  61. 2
      Adventures in Lestoria/EquipSlotButton.h
  62. 8
      Adventures in Lestoria/Error.h
  63. 27
      Adventures in Lestoria/ExplosiveTrap.cpp
  64. 65
      Adventures in Lestoria/FadeInOutEffect.cpp
  65. 2
      Adventures in Lestoria/FallingStone.cpp
  66. 9
      Adventures in Lestoria/FireBolt.cpp
  67. 10
      Adventures in Lestoria/ForegroundEffect.cpp
  68. 12
      Adventures in Lestoria/Frog.cpp
  69. 2
      Adventures in Lestoria/FrogTongue.cpp
  70. 84
      Adventures in Lestoria/GiantCrab.cpp
  71. 85
      Adventures in Lestoria/GiantOctopus.cpp
  72. 9
      Adventures in Lestoria/Goblin_Boar_Rider.cpp
  73. 6
      Adventures in Lestoria/Goblin_Bomb.cpp
  74. 10
      Adventures in Lestoria/Goblin_Bow.cpp
  75. 12
      Adventures in Lestoria/Goblin_Dagger.cpp
  76. 10
      Adventures in Lestoria/Hawk.cpp
  77. 20
      Adventures in Lestoria/IBullet.cpp
  78. 8
      Adventures in Lestoria/IBullet.h
  79. 5
      Adventures in Lestoria/IT.cpp
  80. 2
      Adventures in Lestoria/IT.h
  81. 221
      Adventures in Lestoria/Item.cpp
  82. 50
      Adventures in Lestoria/Item.h
  83. 9
      Adventures in Lestoria/ItemDrop.cpp
  84. 55
      Adventures in Lestoria/ItemEnchant.cpp
  85. 15
      Adventures in Lestoria/ItemEnchant.h
  86. 102
      Adventures in Lestoria/ItemScript.cpp
  87. 52
      Adventures in Lestoria/LightningBolt.cpp
  88. 41
      Adventures in Lestoria/Menu.cpp
  89. 7
      Adventures in Lestoria/Menu.h
  90. 1
      Adventures in Lestoria/MenuComponent.cpp
  91. 4
      Adventures in Lestoria/MenuComponent.h
  92. 2
      Adventures in Lestoria/MenuDefinitions.h
  93. 6
      Adventures in Lestoria/MenuItemButton.h
  94. 17
      Adventures in Lestoria/MenuItemItemButton.h
  95. 40
      Adventures in Lestoria/MenuItemLabel.h
  96. 29
      Adventures in Lestoria/MenuLabel.h
  97. 78
      Adventures in Lestoria/MenuRefineLabel.h
  98. 11
      Adventures in Lestoria/MenuType.h
  99. 4
      Adventures in Lestoria/Merchant.cpp
  100. 79
      Adventures in Lestoria/Meteor.cpp
  101. Some files were not shown because too many files have changed in this diff Show More

@ -103,7 +103,6 @@
<ClCompile Include="..\Adventures in Lestoria\discord-files\store_manager.cpp" />
<ClCompile Include="..\Adventures in Lestoria\discord-files\user_manager.cpp" />
<ClCompile Include="..\Adventures in Lestoria\discord-files\voice_manager.cpp" />
<ClCompile Include="BuffTests.cpp" />
<ClCompile Include="EnchantTests.cpp">
<SubType>
</SubType>
@ -112,10 +111,6 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="FileTests.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="GeometryTests.cpp" />
<ClCompile Include="ItemTests.cpp">
<SubType>
@ -135,12 +130,6 @@
<Project>{8e3067af-cfe7-4b11-bc6b-b867c32753d7}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClInclude Include="GameHelper.h">
<SubType>
</SubType>
</ClInclude>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>

@ -75,16 +75,5 @@
<ClCompile Include="EngineTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="BuffTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FileTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="GameHelper.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

@ -1,165 +0,0 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "CppUnitTest.h"
#include "AdventuresInLestoria.h"
#include "config.h"
#include "ItemDrop.h"
#include "Tutorial.h"
#include "DamageNumber.h"
#include "GameHelper.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
INCLUDE_MONSTER_DATA
INCLUDE_game
INCLUDE_GFX
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_MONSTER_LIST
INCLUDE_INITIALIZEGAMECONFIGURATIONS
namespace BuffTests
{
TEST_CLASS(BuffTest)
{
public:
std::unique_ptr<AiL>testGame;
#pragma region Setup Functions
void SetupTestMonster(){
InitializeGameConfigurations();
testGame.reset(new AiL(true));
ItemAttribute::Initialize();
ItemInfo::InitializeItems();
testGame->InitializeGraphics();
testGame->InitializeClasses();
sig::Animation::InitializeAnimations();
testGame->InitializeDefaultKeybinds();
testGame->InitializePlayer();
sig::Animation::SetupPlayerAnimations();
Menu::InitializeMenus();
Tutorial::Initialize();
Stats::InitializeDamageReductionTable();
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
testGame->ResetLevelStates();
#pragma region Setup a fake test map
game->MAP_DATA["CAMPAIGN_1_1"];
game->_SetCurrentLevel("CAMPAIGN_1_1");
ItemDrop::ClearDrops();
#pragma endregion
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
Menu::themes.SetInitialized();
GFX.SetInitialized();
DAMAGENUMBER_LIST.clear();
}
void SetupMockMap(){
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::ClearDrops();
}
#pragma endregion
TEST_METHOD_INITIALIZE(BuffInitialize){
SetupTestMonster();
SetupMockMap();
}
TEST_METHOD_CLEANUP(CleanupTests){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
TEST_METHOD(AddBuffMonsterCallbackExpireFunctionTest){
Monster&m{game->SpawnMonster({},MONSTER_DATA.at("TestName"))};
Game::Update(0.f);
Assert::AreEqual(size_t(0),m.GetBuffs(BuffType::AFFECTED_BY_LIGHTNING_BOLT).size(),L"Monsters do not have any lightning bolt affected buffs when spawning.");
m.AddBuff(BuffType::AFFECTED_BY_LIGHTNING_BOLT,3.f,1,[](std::weak_ptr<Monster>attachedTarget,Buff&b){attachedTarget.lock()->Hurt(5,attachedTarget.lock()->OnUpperLevel(),attachedTarget.lock()->GetZ());});
Assert::AreEqual(size_t(1),m.GetBuffs(BuffType::AFFECTED_BY_LIGHTNING_BOLT).size(),L"Monster now has Affected By Lightning Bolt buff. Should get hurt for 5 damage in 3 seconds...");
Game::Update(0.f);
Game::Update(3.f);
Assert::AreEqual(25,m.GetHealth(),L"Monster should have taken 5 health from the expired callback provided.");
}
TEST_METHOD(AddBuffPlayerCallbackExpireFunctionTest){
Assert::AreEqual(size_t(0),game->GetPlayer()->GetBuffs(BuffType::AFFECTED_BY_LIGHTNING_BOLT).size(),L"Player does not have any lightning bolt affected buffs when spawning.");
game->GetPlayer()->AddBuff(BuffType::AFFECTED_BY_LIGHTNING_BOLT,3.f,1,[](Player*attachedTarget,Buff&b){attachedTarget->Hurt(5,attachedTarget->OnUpperLevel(),attachedTarget->GetZ());});
Assert::AreEqual(size_t(1),game->GetPlayer()->GetBuffs(BuffType::AFFECTED_BY_LIGHTNING_BOLT).size(),L"Player is now affected By Lightning Bolt buff. Should get hurt for 5 damage in 3 seconds...");
Game::Update(0.f);
Game::Update(3.f);
Assert::AreEqual(95,game->GetPlayer()->GetHealth(),L"Player should have taken 5 health from the expired callback provided.");
}
TEST_METHOD(MonsterHasBuffFunctionTest){
Monster&m{game->SpawnMonster({},MONSTER_DATA.at("TestName"))};
Game::Update(0.f);
Assert::AreEqual(false,m.HasBuff(BuffType::SPEEDBOOST),L"Monster should not have a speedboost buff before being given one.");
m.AddBuff(BuffType::ADRENALINE_RUSH,1.f,1.f);
Assert::AreEqual(false,m.HasBuff(BuffType::SPEEDBOOST),L"Monster should not have a speedboost buff when given an unrelated buff.");
m.AddBuff(BuffType::SPEEDBOOST,1.f,1.f);
Assert::AreEqual(true,m.HasBuff(BuffType::SPEEDBOOST),L"Monster should now have a speedboost buff.");
m.AddBuff(BuffType::SPEEDBOOST,2.f,1.f);
Assert::AreEqual(true,m.HasBuff(BuffType::SPEEDBOOST),L"Monster should still report having a speedboost buff.");
Game::Update(0.f);
Game::Update(1.f);
Assert::AreEqual(true,m.HasBuff(BuffType::SPEEDBOOST),L"Monster should still have one speedboost buff.");
Game::Update(1.f);
Assert::AreEqual(false,m.HasBuff(BuffType::SPEEDBOOST),L"Monster should no longer have a speedboost buff.");
m.AddBuff(BuffType::SPEEDBOOST,1.f,1.f);
m.RemoveBuff(BuffType::SPEEDBOOST);
Assert::AreEqual(false,m.HasBuff(BuffType::SPEEDBOOST),L"Monster should no longer have a speedboost buff.");
}
TEST_METHOD(PlayerHasBuffFunctionTest){
Assert::AreEqual(false,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should not have a speedboost buff before being given one.");
game->GetPlayer()->AddBuff(BuffType::ADRENALINE_RUSH,1.f,1.f);
Assert::AreEqual(false,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should not have a speedboost buff when given an unrelated buff.");
game->GetPlayer()->AddBuff(BuffType::SPEEDBOOST,1.f,1.f);
Assert::AreEqual(true,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should now have a speedboost buff.");
game->GetPlayer()->AddBuff(BuffType::SPEEDBOOST,2.f,1.f);
Assert::AreEqual(true,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should still report having a speedboost buff.");
Game::Update(0.f);
Game::Update(1.f);
Assert::AreEqual(true,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should still have one speedboost buff.");
Game::Update(1.f);
Assert::AreEqual(false,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should no longer have a speedboost buff.");
game->GetPlayer()->AddBuff(BuffType::SPEEDBOOST,1.f,1.f);
game->GetPlayer()->RemoveBuff(BuffType::SPEEDBOOST);
Assert::AreEqual(false,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should no longer have a speedboost buff.");
}
};
}

File diff suppressed because it is too large Load Diff

@ -38,72 +38,20 @@ All rights reserved.
#include "CppUnitTest.h"
#include "AdventuresInLestoria.h"
#include "Tutorial.h"
#include <random>
#include <format>
#include "ItemDrop.h"
#include "DamageNumber.h"
#include <ranges>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
INCLUDE_GFX
INCLUDE_ITEM_DATA
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_INITIALIZEGAMECONFIGURATIONS
extern std::mt19937 rng;
namespace EngineTests
{
TEST_CLASS(EngineTest)
{
public:
std::unique_ptr<AiL>testGame;
InputGroup testKeyboardInput;
Player*player;
HWButton*testKey;
TEST_METHOD_INITIALIZE(PlayerInitialize){
InitializeGameConfigurations();
rng=std::mt19937{57189U};//Establish a fixed random seed on setup so the exact same results are generated every test run.
testGame.reset(new AiL(true));
ItemAttribute::Initialize();
ItemInfo::InitializeItems();
testGame->InitializeGraphics();
testGame->InitializeClasses();
sig::Animation::InitializeAnimations();
testGame->InitializeDefaultKeybinds();
testGame->InitializePlayer();
sig::Animation::SetupPlayerAnimations();
Menu::InitializeMenus();
Tutorial::Initialize();
Stats::InitializeDamageReductionTable();
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
#pragma region Setup a fake test map and test monster
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::ClearDrops();
MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
#pragma endregion
testGame->ResetLevelStates();
player=testGame->GetPlayer();
//Setup key "0" as a test input
testKeyboardInput.AddKeybind(Input{InputType::KEY,0});
testKey=testGame->GetKeyboardState(0);
testGame->olc_UpdateKeyFocus(true); //Force the game to be "focused" for tests. Required if we want keyboard inputs to work.
Menu::themes.SetInitialized();
GFX.SetInitialized();
DAMAGENUMBER_LIST.clear();
}
TEST_METHOD_CLEANUP(CleanupTests){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
TEST_METHOD(StripColorTest){
std::string noColCode{"Hello World!"};
@ -157,22 +105,5 @@ namespace EngineTests
std::u32string u32middleColorCodeStr{middleColorCodeStr.begin(),middleColorCodeStr.end()};
Assert::AreEqual(WHITE.n,testGame->GetFinalRenderColor(WHITE,middleColorCodeStr).n,L"Should use source color since there's no leading HTML color code.");
}
TEST_METHOD(UtilMapRangeTest){
Assert::AreEqual(0.f,util::map_range<float>(0.f,0,100,0,100),L"0 in input range 0-100 output range 0-100 maps to 0");
Assert::AreEqual(100.f,util::map_range<float>(100.f,0,100,0,100),L"100 in input range 0-100 output range 0-100 maps to 100");
Assert::AreEqual(50.f,util::map_range<float>(50.f,0,100,0,100),L"50 in input range 0-100 output range 0-100 maps to 100");
Assert::AreEqual(0.f,util::map_range<float>(0.f,0,50,0,100),L"0 in input range 0-50 output range 0-100 maps to 0");
Assert::AreEqual(200.f,util::map_range<float>(100.f,0,50,0,100),L"100 in input range 0-50 output range 0-100 maps to 200");
Assert::AreEqual(100.f,util::map_range<float>(50.f,0,50,0,100),L"50 in input range 0-50 output range 0-100 maps to 100");
Assert::AreEqual(100.f,util::map_range<float>(0.f,0,100,100,200),L"0 in input range 0-100 output range 100-200 maps to 100");
Assert::AreEqual(200.f,util::map_range<float>(100.f,0,100,100,200),L"100 in input range 0-100 output range 100-200 maps to 200");
Assert::AreEqual(150.f,util::map_range<float>(50.f,0,100,100,200),L"50 in input range 0-100 output range 100-200 maps to 150");
Assert::AreEqual(0.f,util::map_range<float>(0.f,50,100,100,200),L"0 in input range 50-100 output range 100-200 maps to 0");
Assert::AreEqual(200.f,util::map_range<float>(100.f,50,100,100,200),L"100 in input range 50-100 output range 100-200 maps to 200");
Assert::AreEqual(100.f,util::map_range<float>(50.f,50,100,100,200),L"50 in input range 50-100 output range 100-200 maps to 100");
}
};
}

@ -1,113 +0,0 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "CppUnitTest.h"
#include "AdventuresInLestoria.h"
#include "config.h"
#include "ItemDrop.h"
#include "Tutorial.h"
#include "DamageNumber.h"
#include "GameHelper.h"
#include "SaveFile.h"
#include <ranges>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
INCLUDE_MONSTER_DATA
INCLUDE_game
INCLUDE_GFX
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_MONSTER_LIST
INCLUDE_INITIALIZEGAMECONFIGURATIONS
namespace FileTests
{
TEST_CLASS(FileTest)
{
public:
std::unique_ptr<AiL>testGame;
#pragma region Setup Functions
void SetupTest(){
InitializeGameConfigurations();
testGame.reset(new AiL(true));
ItemAttribute::Initialize();
ItemInfo::InitializeItems();
testGame->InitializeGraphics();
testGame->InitializeClasses();
sig::Animation::InitializeAnimations();
testGame->InitializeDefaultKeybinds();
testGame->InitializePlayer();
sig::Animation::SetupPlayerAnimations();
Menu::InitializeMenus();
Tutorial::Initialize();
Stats::InitializeDamageReductionTable();
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
testGame->ResetLevelStates();
#pragma region Setup a fake test map
game->MAP_DATA["CAMPAIGN_1_1"];
game->_SetCurrentLevel("CAMPAIGN_1_1");
ItemDrop::ClearDrops();
#pragma endregion
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
Menu::themes.SetInitialized();
GFX.SetInitialized();
DAMAGENUMBER_LIST.clear();
}
void SetupMockMap(){
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::ClearDrops();
}
#pragma endregion
TEST_METHOD_INITIALIZE(FileTestInitialize){
SetupTest();
SetupMockMap();
}
TEST_METHOD_CLEANUP(CleanupFileTests){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
};
}

@ -1,80 +0,0 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#pragma once
#include "CppUnitTest.h"
#include "AdventuresInLestoria.h"
INCLUDE_game
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace Game{
enum class CastWaitProperty{
WAIT_FOR_CAST_TIME,
NO_WAIT,
};
inline void Update(const float fElapsedTime){
game->SetElapsedTime(fElapsedTime);
game->OnUserUpdate(fElapsedTime);
}
inline void CastAbilityAtLocation(Ability&ability,const vf2d&worldLoc,const CastWaitProperty castWaitTime=CastWaitProperty::WAIT_FOR_CAST_TIME){ //NOTE: screenLoc is the actual screen coordinates, NOT the world coordinates! You are defining the mouse position essentially.
game->GetPlayer()->SetTestScreenAimingLocation(worldLoc);
game->GetPlayer()->PrepareCast(ability);
game->GetPlayer()->CastSpell(ability);
Game::Update(ability.precastInfo.castTime);
}
inline void ChangeClass(Player*&player_in,const Class&cl){
game->ChangePlayerClass(cl);
player_in=game->GetPlayer();
}
inline std::weak_ptr<Item>GiveAndEquipEnchantedRing(const std::string_view enchantName,const EquipSlot slot=EquipSlot::RING1){
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
Inventory::EquipItem(nullRing,slot);
nullRing.lock()->_EnchantItem(enchantName);
return nullRing;
}
}
namespace Test
{
template<class T>
inline static void InRange(T initialVal,std::pair<T,T>range,std::wstring_view assertMessage){
Assert::IsTrue(initialVal>=range.first&&initialVal<=range.second,std::format(L"Expected: {}~{} Actual: {} - {}",range.first,range.second,initialVal,assertMessage).c_str());
}
}

@ -42,7 +42,6 @@ All rights reserved.
#include <format>
#include "ItemDrop.h"
#include <ranges>
#include "GameHelper.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
@ -80,7 +79,6 @@ namespace ItemTests
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
testGame->ResetLevelStates();
#pragma region Setup a fake test map and test monster
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::ClearDrops();
@ -96,10 +94,8 @@ namespace ItemTests
Menu::themes.SetInitialized();
GFX.SetInitialized();
}
TEST_METHOD_CLEANUP(ItemCleanupTests){
TEST_METHOD_CLEANUP(CleanupTests){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
TEST_METHOD(ItemGiveTest){
Inventory::AddItem("Health Potion"s,3);
@ -155,7 +151,6 @@ namespace ItemTests
game->SetLoadoutItem(0,"Flat Recovery Potion");
testKey->bHeld=true; //Simulate key being pressed.
player->CheckAndPerformAbility(player->GetItem1(),testKeyboardInput);
game->OnUserUpdate(0.f); //Wait an extra tick for the buff to begin going down.
game->SetElapsedTime(0.05f);
game->OnUserUpdate(0.05f);//Wait some time as the item applies a buff that heals us. We're also going to gain one mana during this tick.
Assert::AreEqual(75,player->GetHealth(),L"Player Health is 75 after using Flat Recovery Potion.");
@ -168,7 +163,6 @@ namespace ItemTests
game->SetLoadoutItem(1,"Pct Recovery Potion");
testKey->bHeld=true; //Simulate key being pressed.
player->CheckAndPerformAbility(player->GetItem2(),testKeyboardInput);
game->OnUserUpdate(0.f); //Wait an extra tick for the buff to begin going down.
game->SetElapsedTime(0.05f);
game->OnUserUpdate(0.05f);//Wait some time as the item applies a buff that heals us.
Assert::AreEqual(75,player->GetHealth(),L"Player Health is 75 after using Pct Recovery Potion.");
@ -180,7 +174,6 @@ namespace ItemTests
game->SetLoadoutItem(2,"Bandages");
testKey->bHeld=true; //Simulate key being pressed.
player->CheckAndPerformAbility(player->GetItem3(),testKeyboardInput);
game->OnUserUpdate(0.f); //Wait an extra tick for the buff to begin going down.
game->SetElapsedTime(0.05f);
game->OnUserUpdate(0.05f);//Wait some time as the item applies a buff that heals us.
Assert::AreEqual(30,player->GetHealth(),L"Player is immediately healed for 5 health points on Bandages use.");
@ -223,7 +216,6 @@ namespace ItemTests
std::weak_ptr<Item>testArmor{Inventory::AddItem("Test Armor"s)};
Assert::IsFalse(testArmor.lock()->CanBeRefined(),L"Test Armor should not be allowed to be refined since it's not an accessory.");
Inventory::AddItem(slimeKingRing.lock()->FragmentName(),50U);
Inventory::EquipItem(slimeKingRing,EquipSlot::RING1);
Assert::IsTrue(slimeKingRing.lock()->CanBeRefined(),L"Ring of the Slime King should now be allowed to be refined since we meet all requirements.");
player->SetMoney(0);
Assert::IsFalse(slimeKingRing.lock()->CanBeRefined(),L"Ring of the Slime King should not be allowed to be refined since we do not have enough money.");
@ -235,122 +227,22 @@ namespace ItemTests
for(const auto&[attr,val]:slimeKingRing.lock()->RandomStats()){
Assert::AreEqual(ITEM_DATA[slimeKingRing.lock()->ActualName()].GetMaxStats().A_Read(attr),val,L"The current stats should be equal to the maximum stats when refinement is done.");
}
/*Ring of the Slime King has the following refining stats:
* StatValues = Health,Mana,Move Spd %
MinStats = 5,1,1
MaxStats = 20,4,3
*
Therefore, after this process is done the player should have 20 more health, 4 more mana, and 3% more move speed than normal.
*/
//These checks make sure that the refining call actually modifies already equipped items!
Assert::AreEqual(120,player->GetMaxHealth(),L"Player should now have 120 max health.");
Assert::AreEqual(104,player->GetMaxMana(),L"Player should now have 104 max mana.");
Assert::AreEqual(1.03f,player->GetMoveSpdMult(),L"Player should now have 103% move speed.");
}
TEST_METHOD(EnchantTestCheck){
std::weak_ptr<Item>slimeKingRing{Inventory::AddItem("Ring of the Slime King"s)};
Assert::IsFalse(slimeKingRing.lock()->HasEnchant());
bool obtainedDuplicate{false};
for(int i:std::ranges::iota_view(0,1000)){
std::optional<ItemEnchant>previousEnchant{slimeKingRing.lock()->GetEnchant()};
std::string previousEnchantName{};
if(slimeKingRing.lock()->HasEnchant())previousEnchantName=slimeKingRing.lock()->GetEnchant().value().Name();
slimeKingRing.lock()->_EnchantItem(ItemEnchant::RollRandomEnchant(previousEnchant));
slimeKingRing.lock()->EnchantItem(ItemEnchant::RollRandomEnchant());
Assert::IsTrue(slimeKingRing.lock()->HasEnchant());
if(previousEnchantName==slimeKingRing.lock()->GetEnchant().value().Name()){
bool improvementExists{false};
std::string statDowngrades{};
if(previousEnchant.value().HasAttributes()){
for(const auto&[attr,val]:previousEnchant.value()){
if(slimeKingRing.lock()->GetEnchant().value().GetAttribute(attr.ActualName())>val){
improvementExists=true;
break;
}else statDowngrades+=std::format("{} - Previous:{}, New:{}\n",attr.Name(),val,slimeKingRing.lock()->GetEnchant().value().GetAttribute(attr.ActualName()));
}
}else improvementExists=true;
Assert::IsTrue(improvementExists,util::wformat("Could not find a stat improvement for same name stat! {} THIS SHOULD NOT BE ALLOWED!",statDowngrades).c_str());
obtainedDuplicate=true;
}
if(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().has_value())Assert::AreEqual(int(player->GetClass()),int(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().value())); //Validate enchant is only for this class if it's a class-based ability.
}
testGame->ChangePlayerClass(WIZARD);
player=testGame->GetPlayer(); //The player pointer has been reassigned...
for(int i:std::ranges::iota_view(0,1000)){
std::optional<ItemEnchant> previousEnchant{slimeKingRing.lock()->GetEnchant()};
std::string previousEnchantName{};
if(slimeKingRing.lock()->HasEnchant())previousEnchantName=slimeKingRing.lock()->GetEnchant().value().Name();
slimeKingRing.lock()->_EnchantItem(ItemEnchant::RollRandomEnchant(previousEnchant));
slimeKingRing.lock()->EnchantItem(ItemEnchant::RollRandomEnchant());
Assert::IsTrue(slimeKingRing.lock()->HasEnchant());
if(previousEnchantName==slimeKingRing.lock()->GetEnchant().value().Name()){
bool improvementExists{false};
std::string statDowngrades{};
if(previousEnchant.value().HasAttributes()){
for(const auto&[attr,val]:previousEnchant.value()){
if(slimeKingRing.lock()->GetEnchant().value().GetAttribute(attr.ActualName())>val){
improvementExists=true;
break;
}else statDowngrades+=std::format("{} - Previous:{}, New:{}\n",attr.Name(),val,slimeKingRing.lock()->GetEnchant().value().GetAttribute(attr.ActualName()));
}
}else improvementExists=true;
Assert::IsTrue(improvementExists,util::wformat("Could not find a stat improvement for same name stat! {} THIS SHOULD NOT BE ALLOWED!",statDowngrades).c_str());
obtainedDuplicate=true;
}
if(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().has_value())Assert::AreEqual(int(player->GetClass()),int(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().value())); //Validate enchant is only for this class if it's a class-based ability.
}
Assert::IsTrue(obtainedDuplicate,L"During this test a duplicate enchant was never obtained! THIS SHOULD BE ALLOWED!");
}
TEST_METHOD(AccessoryAntiCompatibilityCheck){
std::weak_ptr<Item>extraRing{Inventory::AddItem("Ring of the Slime King"s)};
std::weak_ptr<Item>extraRing2{Inventory::AddItem("Ring of the Slime King"s)};
Inventory::EquipItem(extraRing,EquipSlot::RING2);
Assert::AreEqual(true,Item::SelectedEquipIsDifferent(extraRing2,EquipSlot::RING1),L"The game should allow equipping of any two normal rings that are not the same ring.");
Assert::AreEqual(false,Item::SelectedEquipIsDifferent(extraRing,EquipSlot::RING1),L"The game should not allow equipping the same ring if it's already equipped.");
Inventory::UnequipItem(EquipSlot::RING2);
Assert::AreEqual(true,Item::SelectedEquipIsDifferent(extraRing,EquipSlot::RING1),L"The game should allow equipping a ring to either blank slot if they're open.");
}
TEST_METHOD(AccessoryRandomEnchantTest){
std::weak_ptr<Item>extraRing{Inventory::AddItem("Ring of the Slime King"s)};
std::unordered_map<ItemEnchantInfo::ItemEnchantCategory,uint32_t>enchantCounts;
for(int i:std::ranges::iota_view(0,1000)){
Inventory::AddItem(extraRing.lock()->FragmentName(),"Fragment Enchant Cost"_i[0]);
player->AddMoney("Fragment Enchant Cost"_i[1]);
const ItemEnchant&resultEnchant{extraRing.lock()->ApplyRandomEnchant()};
if(resultEnchant.GetClass().has_value())Assert::AreEqual(int(resultEnchant.GetClass().value()),int(player->GetClass()),L"Player's class matches the class of the enchant.");
enchantCounts[resultEnchant.Category()]++;
Assert::AreEqual(true,extraRing.lock()->GetEnchant().has_value(),L"Ring is expected to be enchanted.");
Assert::AreEqual(resultEnchant.Name(),extraRing.lock()->GetEnchant().value().Name(),L"Ring is expected to be enchanted with the same enchant that was selected.");
Assert::AreEqual(false,player->HasEnchant(resultEnchant.Name()),L"Player is not expected to have the same enchant that was selected while the ring is unequipped.");
Inventory::EquipItem(extraRing,EquipSlot::RING1);
Assert::AreEqual(true,player->HasEnchant(resultEnchant.Name()),L"Player is expected to have the same enchant that was selected.");
Inventory::UnequipItem(EquipSlot::RING1);
}
Test::InRange(enchantCounts[ItemEnchantInfo::ItemEnchantCategory::GENERAL],{450U,550U},util::wformat("General enchants % is approx 50%."));
Test::InRange(enchantCounts[ItemEnchantInfo::ItemEnchantCategory::CLASS],{350U,450U},util::wformat("Class enchants % is approx 40%."));
Test::InRange(enchantCounts[ItemEnchantInfo::ItemEnchantCategory::UNIQUE],{50U,150U},util::wformat("Unique enchants % is approx 40%."));
}
TEST_METHOD(EnchantColorTest){
Assert::AreEqual("Item Enchants.General Enchants.Enchant Display Color"_Pixel.n,ItemEnchantInfo::GetEnchant("Health Boost").DisplayCol().n,L"Expecting a general enchant to have the general enchant pixel display color.");
Assert::AreEqual("Item Enchants.Class Enchants.Enchant Display Color"_Pixel.n,ItemEnchantInfo::GetEnchant("Quickdraw").DisplayCol().n,L"Expecting a class enchant to have the class enchant pixel display color.");
Assert::AreEqual("Item Enchants.Unique Enchants.Enchant Display Color"_Pixel.n,ItemEnchantInfo::GetEnchant("Magical Protection").DisplayCol().n,L"Expecting a unique enchant to have the unique enchant pixel display color.");
}
TEST_METHOD(CanBeEnchantedTest){
std::weak_ptr<Item>extraRing{Inventory::AddItem("Ring of the Slime King"s)};
std::weak_ptr<Item>testArmor{Inventory::AddItem("Test Armor"s)};
Assert::AreEqual(false,extraRing.lock()->CanBeEnchanted(),L"We don't have the money nor required fragments to enchant this item.");
Assert::AreEqual(false,testArmor.lock()->CanBeEnchanted(),L"We can't enchant armor.");
player->SetMoney(2000U);
Assert::AreEqual(false,extraRing.lock()->CanBeEnchanted(),L"We don't have the required fragments to enchant this item.");
player->SetMoney(0U);
Inventory::AddItem(extraRing.lock()->FragmentName(),"Fragment Enchant Cost"_i[0]);
Assert::AreEqual(false,extraRing.lock()->CanBeEnchanted(),L"We don't have the required money to enchant this item.");
player->SetMoney(2000U);
Assert::AreEqual(true,extraRing.lock()->CanBeEnchanted(),L"We don't have the required money to enchant this item.");
extraRing.lock()->ApplyRandomEnchant();
Assert::AreEqual(false,extraRing.lock()->CanBeEnchanted(),L"Ring cannot be enchanted again due to consumption of fragments.");
Inventory::AddItem(extraRing.lock()->FragmentName(),"Fragment Enchant Cost"_i[0]);
Assert::AreEqual(true,extraRing.lock()->CanBeEnchanted(),L"Ring can be enchanted again with the right amount of fragments.");
Assert::AreEqual(uint32_t(2000-"Fragment Enchant Cost"_i[1]),player->GetMoney(),util::wformat("Lost {} money due to enchanting ring.","Fragment Enchant Cost"_i[1]).c_str());
}
};
}

@ -41,7 +41,6 @@ All rights reserved.
#include "ItemDrop.h"
#include "Tutorial.h"
#include "DamageNumber.h"
#include "GameHelper.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
@ -74,8 +73,6 @@ namespace MonsterTests
testGame->InitializeGraphics();
testGame->InitializeClasses();
sig::Animation::InitializeAnimations();
Monster::InitializeStrategies();
MonsterData::InitializeMonsterData();
testGame->InitializeDefaultKeybinds();
testGame->InitializePlayer();
sig::Animation::SetupPlayerAnimations();
@ -85,7 +82,6 @@ namespace MonsterTests
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
testGame->ResetLevelStates();
#pragma region Setup a fake test map
game->MAP_DATA["CAMPAIGN_1_1"];
@ -110,10 +106,8 @@ namespace MonsterTests
SetupTestMonster();
SetupMockMap();
}
TEST_METHOD_CLEANUP(MonsterTestCleanup){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
~MonsterTest(){
testGame.release();
}
TEST_METHOD(DisplayNameCheck){
Assert::AreEqual("Test Monster",MONSTER_DATA["TestName"].GetDisplayName().c_str());
@ -410,7 +404,8 @@ namespace MonsterTests
TEST_METHOD(TrapperMarkTest){
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
game->SpawnMonster({},testMonsterData);
Game::Update(0.1f); //A monster that is spawned needs to be added to the monster list in the next tick.
game->SetElapsedTime(0.1f);
game->OnUserUpdate(0.1f); //A monster that is spawned needs to be added to the monster list in the next tick.
std::weak_ptr<Monster>testMonster{MONSTER_LIST.front()};
Assert::AreEqual(uint8_t(0),testMonster.lock()->GetMarkStacks(),L"Monster has 0 marks initially.");
@ -461,99 +456,5 @@ namespace MonsterTests
Assert::AreEqual(uint8_t(0),testMonster.lock()->GetMarkStacks(),L"The marks should have expired after 10 seconds.");
}
TEST_METHOD(HurtFailsWhenDead){
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
Monster&m{game->SpawnMonster({},testMonsterData)};
m.Hurt(50,m.OnUpperLevel(),m.GetZ());
Assert::IsTrue(m.IsDead(),L"Monster is considered dead.");
Assert::IsTrue(!m.IsAlive(),L"Monster is considered dead.");
Assert::IsFalse(m.Hurt(50,m.OnUpperLevel(),m.GetZ()),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
Assert::IsFalse(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::DOT),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
Assert::IsFalse(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::PLAYER_ABILITY),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
Assert::IsFalse(m._DealTrueDamage(50),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
Assert::IsFalse(m._DealTrueDamage(50,HurtFlag::DOT),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
Assert::IsFalse(m._DealTrueDamage(50,HurtFlag::PLAYER_ABILITY),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
}
TEST_METHOD(HurtSucceedsWhenAlive){
MonsterData testMonsterData{"TestName","Test Monster",3000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
Monster&m{game->SpawnMonster({},testMonsterData)};
m.Hurt(50,m.OnUpperLevel(),m.GetZ());
Assert::IsTrue(!m.IsDead(),L"Monster is considered alive.");
Assert::IsTrue(m.IsAlive(),L"Monster is considered alive.");
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ()),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::DOT),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::PLAYER_ABILITY),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
Assert::IsTrue(m._DealTrueDamage(50),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
Assert::IsTrue(m._DealTrueDamage(50,HurtFlag::DOT),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
Assert::IsTrue(m._DealTrueDamage(50,HurtFlag::PLAYER_ABILITY),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
}
TEST_METHOD(HurtSucceedsWhenDyingWithExactHealth){
MonsterData testMonsterData{"TestName","Test Monster",50,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
Monster&m{game->SpawnMonster({},testMonsterData)};
m.Hurt(50,m.OnUpperLevel(),m.GetZ());
Assert::IsTrue(m.IsDead(),L"Monster is considered dead.");
Assert::IsTrue(!m.IsAlive(),L"Monster is considered dead.");
m.Heal(50);
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ()),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
m.Heal(50);
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::DOT),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
m.Heal(50);
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::PLAYER_ABILITY),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
m.Heal(50);
Assert::IsTrue(m._DealTrueDamage(50),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
m.Heal(50);
Assert::IsTrue(m._DealTrueDamage(50,HurtFlag::DOT),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
m.Heal(50);
Assert::IsTrue(m._DealTrueDamage(50,HurtFlag::PLAYER_ABILITY),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
}
TEST_METHOD(UnconsciousMonsterTest){
Monster&parrot{game->SpawnMonster({},MONSTER_DATA.at("Parrot"))};
Game::Update(1.f);
Assert::IsFalse(parrot.IsUnconscious(),L"Parrot should not be immediately unconscious.");
parrot.Hurt(1,parrot.OnUpperLevel(),parrot.GetZ());
Assert::IsFalse(parrot.IsUnconscious(),L"Parrot should not be unconscious after taking 1 damage.");
parrot.Hurt(parrot.GetMaxHealth(),parrot.OnUpperLevel(),parrot.GetZ());
Assert::IsTrue(parrot.IsUnconscious(),L"Parrot should now be unconscious.");
Assert::IsTrue(parrot.IsDead(),L"Parrot is considered dead.");
Assert::IsTrue(parrot.InUndamageableState(parrot.OnUpperLevel(),parrot.GetZ()),L"Parrot should be in an undamageable state. Hopefully for obvious reasons.");
Game::Update(MONSTER_DATA.at("Parrot").UnconsciousTime()/2);
Assert::IsTrue(parrot.IsUnconscious(),L"Parrot should still be unconscious.");
Assert::IsTrue(parrot.IsDead(),L"Parrot should still be considered dead.");
Assert::IsTrue(parrot.InUndamageableState(parrot.OnUpperLevel(),parrot.GetZ()),L"Parrot should still be in an undamageable state.");
Game::Update(MONSTER_DATA.at("Parrot").UnconsciousTime()/2);
Assert::IsTrue(!parrot.IsUnconscious(),L"Parrot should no longer be unconscious.");
Assert::IsTrue(parrot.IsAlive(),L"Parrot should be alive.");
Assert::IsTrue(!parrot.InUndamageableState(parrot.OnUpperLevel(),parrot.GetZ()),L"Parrot should not be in an undamageable state.");
}
TEST_METHOD(UnconsciousMonsterHurtTest){
Monster&parrot{game->SpawnMonster({},MONSTER_DATA.at("Parrot"))};
Game::Update(1.f);
parrot.Hurt(parrot.GetMaxHealth(),parrot.OnUpperLevel(),parrot.GetZ());
Assert::IsTrue(parrot.IsUnconscious(),L"Parrot should now be unconscious.");
}
TEST_METHOD(MonsterCollisionRadiusTest){
Monster&parrot{game->SpawnMonster({},MONSTER_DATA.at("Parrot"))};
Game::Update(1.f);
Game::Update(1.f);
Assert::AreEqual(parrot.GetOriginalCollisionRadius(),parrot.GetCollisionRadius(),L"Parrot collision radius should be normal.");
Assert::AreEqual(int(game->GetPlayer()->GetMaxHealth()-parrot.GetCollisionDamage()),game->GetPlayer()->GetHealth(),L"Player should take collision damage from the parrot.");
parrot.SetCollisionRadius(0.f);
Assert::AreEqual(0.f,parrot.GetCollisionRadius(),L"Parrot collision radius should now be zero.");
game->GetPlayer()->Heal(game->GetPlayer()->GetMaxHealth());
game->GetPlayer()->_SetIframes(0.f);
parrot.SetPos({});
game->GetPlayer()->ForceSetPos({});
Game::Update(1.f);
Assert::AreEqual(game->GetPlayer()->GetMaxHealth(),game->GetPlayer()->GetHealth(),L"Player should be full health.");
parrot.SetCollisionRadius(parrot.GetOriginalCollisionRadius());
Game::Update(1.f);
Assert::AreEqual(int(game->GetPlayer()->GetMaxHealth()-parrot.GetCollisionDamage()),game->GetPlayer()->GetHealth(),L"Player should take collision damage from the parrot.");
}
TEST_METHOD(MonsterCollisionRadiusSizeTest){
Monster&parrot{game->SpawnMonster({},MONSTER_DATA.at("Parrot"))};
}
};
}

@ -88,7 +88,6 @@ namespace PlayerTests
MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
#pragma endregion
testGame->ResetLevelStates();
player=testGame->GetPlayer();
//Setup key "0" as a test input
@ -103,7 +102,6 @@ namespace PlayerTests
TEST_METHOD_CLEANUP(CleanupTests){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
TEST_METHOD(PlayerAlive){
Assert::IsTrue(player->IsAlive());
@ -629,17 +627,17 @@ namespace PlayerTests
TEST_METHOD(HasEnchantCheck){
std::weak_ptr<Item>slimeKingRing{Inventory::AddItem("Ring of the Slime King"s)};
Inventory::EquipItem(slimeKingRing,EquipSlot::RING1);
slimeKingRing.lock()->_EnchantItem("Emergency Recovery"sv);
slimeKingRing.lock()->EnchantItem("Emergency Recovery");
Assert::IsTrue(player->HasEnchant("Emergency Recovery"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING2);
Assert::IsTrue(player->HasEnchant("Emergency Recovery"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING1);
slimeKingRing.lock()->_EnchantItem("Reaper of Souls"sv);
slimeKingRing.lock()->EnchantItem("Reaper of Souls");
Assert::IsTrue(player->HasEnchant("Reaper of Souls"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING2);
Assert::IsTrue(player->HasEnchant("Reaper of Souls"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING1);
slimeKingRing.lock()->_EnchantItem("Attack Boost"sv);
slimeKingRing.lock()->EnchantItem("Attack Boost");
Assert::IsTrue(player->HasEnchant("Attack Boost"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING2);
Assert::IsTrue(player->HasEnchant("Attack Boost"));
@ -658,18 +656,18 @@ namespace PlayerTests
Inventory::EquipItem(leatherShoes,EquipSlot::SHOES);
Inventory::EquipItem(woodenSword,EquipSlot::WEAPON);
leatherHelmet.lock()->_EnchantItem("Wizard's Soul"sv);
leatherHelmet.lock()->EnchantItem("Wizard's Soul");
Assert::IsTrue(player->HasEnchant("Wizard's Soul"));
leatherArmor.lock()->_EnchantItem("Ability Haste"sv);
leatherArmor.lock()->EnchantItem("Ability Haste");
Assert::IsTrue(player->HasEnchant("Ability Haste"));
leatherPants.lock()->_EnchantItem("Improved Ground Slam"sv);
leatherPants.lock()->EnchantItem("Improved Ground Slam");
Assert::IsTrue(player->HasEnchant("Improved Ground Slam"));
leatherGloves.lock()->_EnchantItem("Battle Shout"sv);
leatherGloves.lock()->EnchantItem("Battle Shout");
Assert::IsTrue(player->HasEnchant("Battle Shout"));
leatherShoes.lock()->_EnchantItem("Attack Boost"sv);
leatherShoes.lock()->EnchantItem("Attack Boost");
Inventory::UnequipItem(EquipSlot::RING2);
Assert::IsTrue(player->HasEnchant("Attack Boost"));
woodenSword.lock()->_EnchantItem("Mana Pool"sv);
woodenSword.lock()->EnchantItem("Mana Pool");
Assert::IsTrue(player->HasEnchant("Mana Pool"));
Inventory::UnequipItem(EquipSlot::HELMET);
@ -744,58 +742,12 @@ namespace PlayerTests
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"sv);
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.");
}
TEST_METHOD(PlayerGetShieldCheck){
player=testGame->GetPlayer();
Assert::AreEqual(0U,player->GetShield(),L"Player should have no shields at first.");
}
TEST_METHOD(PlayerAddShieldCheck){
player=testGame->GetPlayer();
player->AddShield(60U,5.f,PlayerTimerType::ADVANCE_SHIELD_TIMER);
Assert::AreEqual(60U,player->GetShield(),L"Player has 60 shield points.");
testGame->SetElapsedTime(5.f);
testGame->OnUserUpdate(5.f);
Assert::AreEqual(0U,player->GetShield(),L"Player should lose the shield after 5 seconds.");
}
TEST_METHOD(PlayerMultiShieldCheck){
player=testGame->GetPlayer();
player->AddShield(60U,5.f,PlayerTimerType::ADVANCE_SHIELD_TIMER);
player->AddShield(100U,2.f,PlayerTimerType::PLAYER_OUTLINE_TIMER);
Assert::AreEqual(160U,player->GetShield(),L"Player has 160 shield points.");
testGame->SetElapsedTime(2.f);
testGame->OnUserUpdate(2.f);
Assert::AreEqual(60U,player->GetShield(),L"Player has 60 shield points.");
testGame->SetElapsedTime(3.f);
testGame->OnUserUpdate(3.f);
Assert::AreEqual(0U,player->GetShield(),L"Player should lose the shield after 5 seconds.");
}
TEST_METHOD(PlayerSubtractShieldCheck){
player=testGame->GetPlayer();
player->AddShield(60U,5.f,PlayerTimerType::ADVANCE_SHIELD_TIMER);
Assert::AreEqual(60U,player->GetShield(),L"Player has 60 shield points.");
player->SubtractShield(40U);
Assert::AreEqual(20U,player->GetShield(),L"Player has 20 shield points.");
player->SubtractShield(200U);
Assert::AreEqual(0U,player->GetShield(),L"Player should have 0 shield points.");
}
TEST_METHOD(PlayerDamageShieldCheck){
player=testGame->GetPlayer();
player->AddShield(60U,5.f,PlayerTimerType::ADVANCE_SHIELD_TIMER);
Assert::AreEqual(60U,player->GetShield(),L"Player has 60 shield points.");
player->Hurt(20U,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(40U,player->GetShield(),L"Player has 40 shield points.");
Assert::AreEqual(player->GetMaxHealth(),player->GetHealth(),L"Player should still be full health.");
player->Hurt(200U,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(0U,player->GetShield(),L"Player has 0 shield points.");
Assert::AreEqual(player->GetMaxHealth(),player->GetHealth(),L"Player should still be full health.");
player->Hurt(10U,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(player->GetMaxHealth()-10,player->GetHealth(),L"Player now takes damage with 0 shield.");
}
};
}

@ -56,8 +56,4 @@ Ability::Ability(std::string name,std::string shortName,std::string description,
const float Ability::GetCooldownTime()const{
return COOLDOWN_TIME;
}
const bool Ability::operator==(const Ability&a)const{
return name==a.name&&isOriginalAbility==a.isOriginalAbility;
}

@ -76,14 +76,12 @@ struct Ability{
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.
bool isOriginalAbility{false};
//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 {})
std::function<bool(Player*,vf2d)>action=[](Player*,vf2d){return false;};
static InputGroup DEFAULT;
const float GetCooldownTime()const;
const bool operator==(const Ability&a)const;
Ability();
//NOTE: icon expects the actual name relative to the "Ability Icons" directory for this constructor!
Ability(std::string name,std::string shortName,std::string description,float cooldownTime,int manaCost,InputGroup*input,std::string icon,Pixel barColor1=VERY_DARK_RED,Pixel barColor2=DARK_RED,PrecastData precastInfo={},bool canCancelCast=false);

@ -29,24 +29,6 @@
"object"
]
},
{
"id": 43,
"name": "AudioEvent",
"storageType": "string",
"type": "enum",
"values": [
"LowHealth",
"TitleScreenLoaded",
"BlacksmithUnlock",
"Chapter2Unlock",
"Chapter3Unlock",
"BossFanfare",
"BossDefeated",
"PreBossPhase",
"Default Volume"
],
"valuesAsFlags": false
},
{
"id": 30,
"name": "Backdrop",
@ -71,10 +53,7 @@
"overworld",
"foresty_boss",
"base_camp",
"mountain",
"mountain_boss",
"beach",
"beach_boss"
"mountain"
],
"valuesAsFlags": false
},
@ -197,11 +176,7 @@
"Birds2",
"Birds3",
"Birds4",
"Campfire",
"Crashing Waves",
"Gull1",
"Gull2",
"Gull3"
"Campfire"
],
"valuesAsFlags": false
},
@ -305,18 +280,14 @@
"CAMPAIGN_3_6",
"CAMPAIGN_3_7",
"CAMPAIGN_3_8",
"CAMPAIGN_3_B1",
"BOSS_3",
"BOSS_3_B",
"STORY_3_1",
"STORY_3_2"
"CAMPAIGN_3_B1"
],
"valuesAsFlags": false
},
{
"id": 25,
"name": "LevelType",
"storageType": "int",
"storageType": "string",
"type": "enum",
"values": [
"Dungeon",
@ -359,12 +330,6 @@
"drawFill": true,
"id": 19,
"members": [
{
"name": "Audio Event",
"propertyType": "AudioEvent",
"type": "string",
"value": "Default Volume"
},
{
"name": "Backdrop",
"propertyType": "Backdrop",
@ -415,8 +380,8 @@
{
"name": "Level Type",
"propertyType": "LevelType",
"type": "int",
"value": 4
"type": "string",
"value": "World Map"
},
{
"name": "Optimize",

@ -130,7 +130,6 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">
<OutDir>$(SolutionDir)$(PlatformTarget)\Release</OutDir>
<CodeAnalysisRuleSet>..\CodeAnalysisRuleset.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<IncludePath>$(IncludePath)</IncludePath>
@ -141,19 +140,11 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IncludePath>$(VCInstallDir)Auxiliary\VS\UnitTest\include;$(IncludePath)</IncludePath>
<LibraryPath>$(VCInstallDir)Auxiliary\VS\UnitTest\lib;$(LibraryPath)</LibraryPath>
<CodeAnalysisRuleSet>..\CodeAnalysisRuleset.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|x64'">
<IncludePath>$(VCInstallDir)Auxiliary\VS\UnitTest\include;$(IncludePath)</IncludePath>
<LibraryPath>$(VCInstallDir)Auxiliary\VS\UnitTest\lib;$(LibraryPath)</LibraryPath>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
<CodeAnalysisRuleSet>..\CodeAnalysisRuleset.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Emscripten|x64'">
<CodeAnalysisRuleSet>..\CodeAnalysisRuleset.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Emscripten Debug|x64'">
<CodeAnalysisRuleSet>..\CodeAnalysisRuleset.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
@ -282,7 +273,7 @@
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalIncludeDirectories>C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;$(ProjectDir)steam;$(ProjectDir)discord-files;C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\LabUser\Documents\include;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\niconiconii\OneDrive\Documents\include;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\OneDrive\Documents\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\LabUser\Documents\include;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\niconiconii\OneDrive\Documents\include;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\OneDrive\Documents\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<Link>
@ -374,10 +365,6 @@
<ClInclude Include="Ability.h" />
<ClInclude Include="AccessoryRowItemDisplay.h" />
<ClInclude Include="Animation.h" />
<ClInclude Include="Arc.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="Attributable.h" />
<ClInclude Include="AttributableStat.h">
<SubType>
@ -391,10 +378,6 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="BlackHole.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="BombBoom.h">
<SubType>
</SubType>
@ -404,10 +387,6 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="DynamicMenuLabel.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="HurtDamageInfo.h">
<SubType>
</SubType>
@ -529,20 +508,12 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="MenuDecal.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="MenuDefinitions.h" />
<ClInclude Include="MenuItemLabel.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="MenuItemLoadoutButton.h" />
<ClInclude Include="MenuRefineLabel.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="MenuType.h">
<SubType>
</SubType>
@ -553,10 +524,6 @@
</ClInclude>
<ClInclude Include="MonsterData.h" />
<ClInclude Include="olcPGEX_SplashScreen.h" />
<ClInclude Include="Oscillator.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="Overlay.h">
<SubType>
</SubType>
@ -746,10 +713,6 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="TrailEffect.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="Tutorial.h" />
<ClInclude Include="VisualNovel.h" />
<ClInclude Include="Test.h" />
@ -768,11 +731,11 @@
<ItemGroup>
<ClCompile Include="Ability.cpp" />
<ClCompile Include="Animation.cpp" />
<ClCompile Include="Arc.cpp">
<ClCompile Include="Arrow.cpp" />
<ClCompile Include="ArtificerDisassembleConfirmWindow.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Arrow.cpp" />
<ClCompile Include="ArtificerDisassembleWindow.cpp">
<SubType>
</SubType>
@ -785,7 +748,7 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ArtificerRefineResultWindow.cpp">
<ClCompile Include="ArtificerRefineConfirmWindow.cpp">
<SubType>
</SubType>
</ClCompile>
@ -834,20 +797,10 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Crab.cpp" />
<ClCompile Include="ExplosiveTrap.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="FadeInOutEffect.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="GiantCrab.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="GiantOctopus.cpp" />
<ClCompile Include="IBullet.cpp" />
<ClCompile Include="BuyItemWindow.cpp">
<SubType>
@ -1002,10 +955,6 @@
</SubType>
</ClCompile>
<ClCompile Include="ItemLoadoutWindow.cpp" />
<ClCompile Include="ItemScript.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Key.cpp" />
<ClCompile Include="LargeStone.cpp" />
<ClCompile Include="LargeTornado.cpp">
@ -1061,7 +1010,6 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="OctopusArm.cpp" />
<ClCompile Include="Overlay.cpp">
<SubType>
</SubType>
@ -1072,27 +1020,15 @@
</SubType>
</ClCompile>
<ClCompile Include="packkey.cpp" />
<ClCompile Include="Parrot.cpp" />
<ClCompile Include="PauseMenu.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Pirate_Buccaneer.cpp" />
<ClCompile Include="Pirate_Captain.cpp" />
<ClCompile Include="Pirate_Marauder.cpp" />
<ClCompile Include="Pixel.cpp" />
<ClCompile Include="PoisonBottle.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ThrownProjectile.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="PoisonPool.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="PurpleEnergyBall.cpp">
<SubType>
</SubType>
@ -1111,7 +1047,6 @@
<ClCompile Include="PulsatingFire.cpp" />
<ClCompile Include="Ranger.cpp" />
<ClCompile Include="RUN_STRATEGY.cpp" />
<ClCompile Include="Sandworm.cpp" />
<ClCompile Include="SaveFile.cpp">
<SubType>
</SubType>
@ -1120,7 +1055,6 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Seagull.cpp" />
<ClCompile Include="SellItemWindow.cpp">
<SubType>
</SubType>
@ -1230,23 +1164,7 @@
<ClCompile Include="Zephy.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="..\commit.bat" />
<None Include="..\debug.sh" />
<None Include="..\debugGame.sh" />
<None Include="..\distribute.ps1" />
<None Include="..\distribute.sh" />
<None Include="..\emscripten_build.ps1" />
<None Include="..\emscripten_build.sh" />
<None Include="..\emscripten_debug_build.ps1" />
<None Include="..\emscripten_debug_build.sh" />
<None Include="..\emscripten_run.ps1" />
<None Include="..\emscripten_run.sh" />
<None Include="..\read_debug_log.ps1" />
<None Include="..\release.sh" />
<None Include="..\runGame - NO STEAM.bat" />
<None Include="..\runGame.bat" />
<None Include="..\runGame.sh" />
<None Include="..\unit-testing-prebuild.ps1" />
<None Include="ClassDiagram.cd" />
<None Include="ClassDiagram2.cd" />
<None Include="cpp.hint" />
@ -1254,7 +1172,6 @@
<None Include="steam\steam_api.json" />
</ItemGroup>
<ItemGroup>
<Text Include="..\x64\Unit Testing\debug.log" />
<Text Include="assets\config\Achievements.txt" />
<Text Include="assets\config\audio\audio.txt" />
<Text Include="assets\config\audio\bgm.txt" />
@ -1316,7 +1233,6 @@
<Text Include="Merchant%27s Items.txt" />
<Text Include="NewClasses.txt" />
<Text Include="InitialConcept.txt" />
<Text Include="Oktopus boss.txt" />
<Text Include="Slime_King_Encounter.txt" />
<Text Include="StatCalculations.txt" />
<Text Include="TODO.txt" />

@ -100,12 +100,6 @@
<Filter Include="Documentation\Admin">
<UniqueIdentifier>{e565fb16-43e6-4d18-a450-af7474df70b9}</UniqueIdentifier>
</Filter>
<Filter Include="Scripts">
<UniqueIdentifier>{eba9dd86-1d5d-4c68-8dcb-760c759099c0}</UniqueIdentifier>
</Filter>
<Filter Include="Debug">
<UniqueIdentifier>{c4119802-3fc8-4555-9013-a7a3ac9b204d}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="olcPixelGameEngine.h">
@ -690,27 +684,6 @@
<ClInclude Include="Timer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="BlackHole.h">
<Filter>Source Files\Effects</Filter>
</ClInclude>
<ClInclude Include="TrailEffect.h">
<Filter>Source Files\Effects</Filter>
</ClInclude>
<ClInclude Include="Oscillator.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="MenuDecal.h">
<Filter>Header Files\Interface</Filter>
</ClInclude>
<ClInclude Include="MenuRefineLabel.h">
<Filter>Header Files\Interface</Filter>
</ClInclude>
<ClInclude Include="DynamicMenuLabel.h">
<Filter>Header Files\Interface</Filter>
</ClInclude>
<ClInclude Include="Arc.h">
<Filter>Header Files\Utils</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Player.cpp">
@ -1193,7 +1166,7 @@
<ClCompile Include="PurpleEnergyBall.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="ThrownProjectile.cpp">
<ClCompile Include="PoisonBottle.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="State_Dialog.cpp">
@ -1205,9 +1178,15 @@
<ClCompile Include="ArtificerRefineWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="ArtificerRefineConfirmWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="ArtificerDisassembleWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="ArtificerDisassembleConfirmWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="ArtificerEnchantWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
@ -1223,54 +1202,6 @@
<ClCompile Include="Timer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FadeInOutEffect.cpp">
<Filter>Source Files\Effects</Filter>
</ClCompile>
<ClCompile Include="PoisonPool.cpp">
<Filter>Source Files\Effects</Filter>
</ClCompile>
<ClCompile Include="ArtificerRefineResultWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="Pirate_Marauder.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Crab.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Parrot.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Pirate_Buccaneer.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Pirate_Captain.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Sandworm.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Seagull.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="GiantCrab.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="ItemScript.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="PoisonBottle.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="GiantOctopus.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="OctopusArm.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Arc.cpp">
<Filter>Source Files\Utils</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="cpp.hint" />
@ -1283,54 +1214,6 @@
<Filter>Header Files\steam</Filter>
</None>
<None Include="..\read_debug_log.ps1" />
<None Include="..\commit.bat">
<Filter>Scripts</Filter>
</None>
<None Include="..\debug.sh">
<Filter>Scripts</Filter>
</None>
<None Include="..\debugGame.sh">
<Filter>Scripts</Filter>
</None>
<None Include="..\distribute.ps1">
<Filter>Scripts</Filter>
</None>
<None Include="..\distribute.sh">
<Filter>Scripts</Filter>
</None>
<None Include="..\emscripten_build.ps1">
<Filter>Scripts</Filter>
</None>
<None Include="..\emscripten_build.sh">
<Filter>Scripts</Filter>
</None>
<None Include="..\emscripten_debug_build.ps1">
<Filter>Scripts</Filter>
</None>
<None Include="..\emscripten_debug_build.sh">
<Filter>Scripts</Filter>
</None>
<None Include="..\emscripten_run.ps1">
<Filter>Scripts</Filter>
</None>
<None Include="..\emscripten_run.sh">
<Filter>Scripts</Filter>
</None>
<None Include="..\release.sh">
<Filter>Scripts</Filter>
</None>
<None Include="..\runGame - NO STEAM.bat">
<Filter>Scripts</Filter>
</None>
<None Include="..\runGame.bat">
<Filter>Scripts</Filter>
</None>
<None Include="..\runGame.sh">
<Filter>Scripts</Filter>
</None>
<None Include="..\unit-testing-prebuild.ps1">
<Filter>Scripts</Filter>
</None>
</ItemGroup>
<ItemGroup>
<Text Include="InitialConcept.txt">
@ -1523,12 +1406,6 @@
<Text Include="ConsoleCommands.txt">
<Filter>Documentation\Admin</Filter>
</Text>
<Text Include="..\x64\Unit Testing\debug.log">
<Filter>Debug</Filter>
</Text>
<Text Include="Oktopus boss.txt">
<Filter>Documentation\Mechanics</Filter>
</Text>
</ItemGroup>
<ItemGroup>
<Image Include="assets\heart.ico">

@ -90,8 +90,7 @@ INCLUDE_FOREDROP_DATA
INCLUDE_MONSTER_DATA
INCLUDE_PACK_KEY
bool DEMO_BUILD = false;
bool ADMIN_MODE = !DEMO_BUILD&&true; //Enables the Unlock All button and admin console.
bool ADMIN_MODE = true; //Enables the Unlock All button and admin console.
bool _DEBUG_MAP_LOAD_INFO = false;
//360x240
vi2d WINDOW_SIZE={24*15,24*10};
@ -182,8 +181,6 @@ AiL::AiL(bool testingMode){
DEBUG_PATHFINDING="debug_pathfinding"_I;
#pragma endregion
Warrior::ResetToOriginalAbilities();
sAppName="GAME_NAME"_S;
game=this;
gameStarted=time(NULL);
@ -191,7 +188,7 @@ AiL::AiL(bool testingMode){
void InitializeGameConfigurations(){
DATA.Reset();
utils::datafile::Read(DATA,"assets/config/configuration.txt");
utils::datafile::DEBUG_ACCESS_OPTIONS="debug_access_options"_I;
@ -281,7 +278,7 @@ void InitializeGameConfigurations(){
}
bool AiL::OnUserCreate(){
if(PACK_KEY=="INSERT_PACK_KEY_HERE")ERR("ERROR! Starting the game with the default pack ID is not allowed!");
if(PACK_KEY=="INSERT_PACK_KEY_HERE")ERR("ERROR! Starting the game with the default pack ID is not allowed!")
gamepack.LoadPack("assets/"+"gamepack_file"_S,PACK_KEY);
GamePad::init();
@ -303,7 +300,6 @@ bool AiL::OnUserCreate(){
InitializePlayerLevelCap();
healthCounter.Initialize(&player->hp,"Interface.HUD Health Tick Rate"_F,"Interface.HUD Health Display Color"_Pixel,"Interface.HUD Heal Damage Color"_Pixel,"Interface.HUD Take Damage Color"_Pixel,"Interface.HUD Health Change Time"_F);
shieldCounter.Initialize(&playerShieldDisplayAmt,"Interface.HUD Shield Tick Rate"_F,"Interface.HUD Shield Display Color"_Pixel,"Interface.HUD Gain Shield Color"_Pixel,"Interface.HUD Lose Shield Color"_Pixel,"Interface.HUD Shield Change Time"_F);
manaCounter.Initialize(&player->mana,"Interface.HUD Mana Tick Rate"_F,"Interface.HUD Mana Display Color"_Pixel,"Interface.HUD Restore Mana Color"_Pixel,"Interface.HUD Reduce Mana Color"_Pixel,"Interface.HUD Mana Change Time"_F);
Monster::InitializeStrategies();
@ -389,7 +385,6 @@ bool AiL::OnUserCreate(){
if(!gamepack.Loaded()&&"GENERATE_GAMEPACK"_B){
gamepack.SavePack("assets/"+"gamepack_file"_S,PACK_KEY);
LOG("Game Pack has been generated!"<<std::endl<<"========================"<<std::endl);
gamepack.LoadPack("assets/"+"gamepack_file"_S,PACK_KEY);
}
return true;
@ -429,15 +424,15 @@ bool AiL::OnConsoleCommand(const std::string& sCommand){
}else
if(args[0]=="/give"){
if(args.size()<2)ConsoleOut()<<"Usage: /give <Item Name> [Amount]"<<std::endl;
uint32_t itemAmt{1};
if(args.size()>=3)itemAmt=std::stoul(args[2]);
uint8_t itemAmt{1};
if(args.size()>=3)itemAmt=std::stoi(args[2]);
Inventory::AddItem(args[1],itemAmt);
ConsoleOut()<<"Added x"<<int(itemAmt)<<" "<<args[1]<<" to player's inventory."<<std::endl;
}else
if(args[0]=="/accessory"){
if(args.size()<2)ConsoleOut()<<"Usage: /accessory <Accessory Name> [Enchant Name]"<<std::endl;
std::weak_ptr<Item>accessory{Inventory::AddItem(args[1])};
if(args.size()>=3)accessory.lock()->_EnchantItem(ItemEnchant{args[2]});
if(args.size()>=3)accessory.lock()->EnchantItem(args[2]);
ConsoleOut()<<"Added "<<args[1]<<" to player's inventory."<<std::endl;
}else{
ConsoleOut()<<"Invalid command! Use /help to see available commands."<<std::endl;
@ -828,7 +823,7 @@ const HurtList AiL::Hurt(vf2d pos,float radius,int damage,bool upperLevel,float
const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()));
if(InRange){
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags);
hitList.emplace_back(HurtListItem{&*m,returnVal});
hitList.push_back({&*m,returnVal});
}
}
}
@ -836,7 +831,7 @@ const HurtList AiL::Hurt(vf2d pos,float radius,int damage,bool upperLevel,float
const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(GetPlayer()->GetPos(),12*GetPlayer()->GetSizeMult()));
if(InRange){
HurtReturnValue returnVal=GetPlayer()->Hurt(damage,upperLevel,z,hurtFlags);
hitList.emplace_back(HurtListItem{GetPlayer(),returnVal});
hitList.push_back({GetPlayer(),returnVal});
}
}
return hitList;
@ -850,7 +845,7 @@ const HurtList AiL::HurtMonsterType(vf2d pos,float radius,int damage,bool upperL
const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),m->GetCollisionRadius()));
if(m->GetName()==monsterName&&InRange){
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags);
hitList.emplace_back(HurtListItem{&*m,returnVal});
hitList.push_back({&*m,returnVal});
}
}
@ -869,7 +864,7 @@ const HurtList AiL::HurtConeNotHit(vf2d pos,float radius,float angle,float sweep
float angleDiff=util::angle_difference(angleToMonster,angle);
if(abs(angleDiff)<=sweepAngle){
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags);
affectedList.emplace_back(HurtListItem{&*m,returnVal});
affectedList.push_back({&*m,returnVal});
hitList.insert(&*m);
}
}
@ -881,7 +876,7 @@ const HurtList AiL::HurtConeNotHit(vf2d pos,float radius,float angle,float sweep
float angleDiff=util::angle_difference(angleToPlayer,angle);
if(abs(angleDiff)<=sweepAngle){
HurtReturnValue returnVal=GetPlayer()->Hurt(damage,upperLevel,z,hurtFlags);
affectedList.emplace_back(HurtListItem{GetPlayer(),returnVal});
affectedList.push_back({GetPlayer(),returnVal});
hitList.insert(GetPlayer());
}
}
@ -898,7 +893,7 @@ const HurtList AiL::HurtNotHit(vf2d pos,float radius,int damage,HitList&hitList,
for(std::shared_ptr<Monster>&m:MONSTER_LIST){
if(!hitList.count(&*m)&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags);
affectedList.emplace_back(HurtListItem{&*m,returnVal});
affectedList.push_back({&*m,returnVal});
hitList.insert(&*m);
}
}
@ -906,7 +901,7 @@ const HurtList AiL::HurtNotHit(vf2d pos,float radius,int damage,HitList&hitList,
if(CheckForPlayerCollisions){
if(!hitList.count(GetPlayer())&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(GetPlayer()->GetPos(),12*GetPlayer()->GetSizeMult()))){
HurtReturnValue returnVal=GetPlayer()->Hurt(damage,upperLevel,z,hurtFlags);
affectedList.emplace_back(HurtListItem{GetPlayer(),returnVal});
affectedList.push_back({GetPlayer(),returnVal});
hitList.insert(GetPlayer());
}
}
@ -1108,11 +1103,8 @@ void AiL::RenderWorld(float fElapsedTime){
if(player->IsInvisible())return;
vf2d playerScale=vf2d(player->GetSizeMult(),player->GetSizeMult());
int count=0;
std::reference_wrapper<const Animate2D::Frame>animationFrame{player->GetFrame()};
if(player->CatFormActive())animationFrame=player->GetCatFrame();
for(vf2d&pos:player->ghostPositions){
view.DrawPartialRotatedDecal(pos,animationFrame.get().GetSourceImage()->Decal(),player->GetSpinAngle(),{12,12},animationFrame.get().GetSourceRect().pos,animationFrame.get().GetSourceRect().size,playerScale,{0,0,0,uint8_t(float(count)/player->RETREAT_GHOST_FRAMES*255)});
view.DrawPartialRotatedDecal(pos,player->GetFrame().GetSourceImage()->Decal(),player->GetSpinAngle(),{12,12},player->GetFrame().GetSourceRect().pos,player->GetFrame().GetSourceRect().size,playerScale,{0,0,0,uint8_t(float(count)/player->RETREAT_GHOST_FRAMES*255)});
count++;
}
if(player->teleportAnimationTimer>0){
@ -1128,16 +1120,12 @@ void AiL::RenderWorld(float fElapsedTime){
if(attackBuffs.size()>0)playerCol={255,uint8_t(255*abs(sin(1.4f*attackBuffs[0].duration))),uint8_t(255*abs(sin(1.4f*attackBuffs[0].duration)))};
else if(adrenalineRushBuffs.size()>0)playerCol={uint8_t(255*abs(sin(6.f*adrenalineRushBuffs[0].duration))),255,uint8_t(255*abs(sin(6.f*adrenalineRushBuffs[0].duration)))};
else if(movespeedBuffs.size()>0)playerCol={uint8_t(255*abs(sin(2.f*movespeedBuffs[0].duration))),255,uint8_t(255*abs(sin(2.f*movespeedBuffs[0].duration)))};
if(player->HasIframes())playerCol.a*=0.62f;
if(damageReductionBuffs.size()>0)view.DrawPartialSquishedRotatedDecal(pos+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)},player->playerOutline.Decal(),player->GetSpinAngle(),{12,12},animationFrame.get().GetSourceRect().pos,animationFrame.get().GetSourceRect().size,playerScale*scale+0.1f,{1.f,player->ySquishFactor},{210,210,210,uint8_t(util::lerp(0,255,abs(sin((PI*GetRunTime())/1.25f))))});
view.DrawPartialSquishedRotatedDecal(pos+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)},animationFrame.get().GetSourceImage()->Decal(),player->GetSpinAngle(),{12,12},animationFrame.get().GetSourceRect().pos,animationFrame.get().GetSourceRect().size,playerScale*scale,{1.f,player->ySquishFactor},playerCol);
DrawAfterImage:view.DrawRotatedDecal(player->afterImagePos,player->afterImage.Decal(),0.f,player->afterImage.Sprite()->Size()/2,{player->GetSizeMult(),player->GetSizeMult()},Pixel{0xFFDCDA});
if(damageReductionBuffs.size()>0)view.DrawPartialSquishedRotatedDecal(pos+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)},player->playerOutline.Decal(),player->GetSpinAngle(),{12,12},player->GetFrame().GetSourceRect().pos,player->GetFrame().GetSourceRect().size,playerScale*scale+0.1f,{1.f,player->ySquishFactor},{210,210,210,uint8_t(util::lerp(0,255,abs(sin((PI*GetRunTime())/1.25f))))});
view.DrawPartialSquishedRotatedDecal(pos+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)},player->GetFrame().GetSourceImage()->Decal(),player->GetSpinAngle(),{12,12},player->GetFrame().GetSourceRect().pos,player->GetFrame().GetSourceRect().size,playerScale*scale,{1.f,player->ySquishFactor},playerCol);
DrawAfterImage:view.DrawRotatedDecal(player->afterImagePos,player->afterImage.Decal(),0.f,player->afterImage.Sprite()->Size()/2,{player->GetSizeMult(),player->GetSizeMult()},{0xFFDCDA});
SetDecalMode(DecalMode::NORMAL);
if(player->GetState()==State::BLOCK||player->GetShield()>0){
if(player->GetState()==State::BLOCK){
view.DrawDecal(player->GetPos()+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)}-vf2d{12,12},GFX["block.png"].Decal());
}
@ -1429,7 +1417,7 @@ void AiL::RenderWorld(float fElapsedTime){
auto it=backgroundEffectsLower.begin();
while(it!=backgroundEffectsLower.end()){
const Effect&e=**it;
e._Draw();
e.Draw();
++it;
}
}
@ -1570,7 +1558,7 @@ void AiL::RenderWorld(float fElapsedTime){
++monstersBeforeLowerIt;
}
for(const Effect*const e:backgroundEffectsLower){
e->_Draw();
e->Draw();
}
while(dropsBeforeLowerIt!=dropsBeforeLower.end()){
const int dropInd=*dropsBeforeLowerIt;
@ -1599,7 +1587,7 @@ void AiL::RenderWorld(float fElapsedTime){
b->_Draw();
}
for(const Effect*const e:foregroundEffectsLower){
e->_Draw();
e->Draw();
}
#pragma endregion
#pragma region Permanent Foreground Rendering
@ -1722,7 +1710,7 @@ void AiL::RenderWorld(float fElapsedTime){
auto it=backgroundEffectsUpper.begin();
while(it!=backgroundEffectsUpper.end()){
const Effect&e=**it;
e._Draw();
e.Draw();
++it;
}
}
@ -1871,7 +1859,7 @@ void AiL::RenderWorld(float fElapsedTime){
++monstersBeforeUpperIt;
}
for(const Effect*const e:backgroundEffectsUpper){
e->_Draw();
e->Draw();
}
while(dropsBeforeUpperIt!=dropsBeforeUpper.end()){
const int dropInd=*dropsBeforeUpperIt;
@ -1900,7 +1888,7 @@ void AiL::RenderWorld(float fElapsedTime){
b->_Draw();
}
for(const Effect*const e:foregroundEffectsUpper){
e->_Draw();
e->Draw();
}
#pragma endregion
#pragma region Permanent Upper Foreground Rendering
@ -1938,7 +1926,6 @@ Player*const AiL::GetPlayer()const{
void AiL::RenderHud(){
if(!displayHud)return;
healthCounter.Update();
shieldCounter.Update();
manaCounter.Update();
auto RenderAimingCursor=[&](){
@ -1969,6 +1956,7 @@ void AiL::RenderHud(){
const vf2d&bossIndicator=bossIndicatorPos.value();
const bool BossIsOutsideView=!geom2d::overlaps(geom2d::rect<float>{view.GetWorldTL(),view.GetWorldVisibleArea()},bossIndicator);
if(BossIsOutsideView){
const bool flicker=sinf(GetRunTime())>0.5f&&sinf(GetRunTime())<0.55f;
#pragma region Side Indicators
@ -2019,7 +2007,8 @@ void AiL::RenderHud(){
if(GetPlayer()->GetCastInfo().castTimer>0){
RenderCastbar(GetPlayer()->GetCastInfo());
}else if(GetPlayer()->GetEndZoneStandTime()>0){
}else
if(GetPlayer()->GetEndZoneStandTime()>0){
RenderCastbar(CastInfo{"Exiting Level...",GetPlayer()->GetEndZoneStandTime(),"Player.End Zone Wait Time"_F});
}
@ -2034,34 +2023,29 @@ void AiL::RenderHud(){
}
DrawDecal({2,2},GFX["heart_outline.png"].Decal(),{1.f,1.f},healthOutlineCol);
const Decal*heartImg{GFX["heart.png"].Decal()};
if(player->GetShield()>0)heartImg=GFX["shield_heart.png"].Decal();
float healthRatio{float(healthCounter.GetDisplayValue())/player->GetMaxHealth()};
float manaRatio{float(manaCounter.GetDisplayValue())/player->GetMaxMana()};
DrawPartialDecal(vf2d{2.f,floor(2+(15*(1-healthRatio)))},const_cast<Decal*>(heartImg),vf2d{0.f,floor(15*(1-healthRatio))},vf2d{17.f,floor(15*healthRatio)});
DrawDecal({2,20},GFX["mana_outline.png"].Decal());
DrawPartialDecal(vf2d{2,float(20+(15*(1-manaRatio)))},GFX["mana.png"].Decal(),vf2d{0.f,float(15*(1-manaRatio))},vf2d{17,float(15*manaRatio)});
std::string text=player->GetHealth()>0?std::to_string(healthCounter.GetDisplayValue()+shieldCounter.GetDisplayValue()):"X";
DrawDecal({2,2},GFX["heart.png"].Decal());
DrawDecal({2,20},GFX["mana.png"].Decal());
std::string text=player->GetHealth()>0?std::to_string(healthCounter.GetDisplayValue()):"X";
std::string text_mana=std::to_string(manaCounter.GetDisplayValue());
DrawShadowStringPropDecal({20,3},text,player->GetShield()>0?shieldCounter.GetDisplayColor():healthCounter.GetDisplayColor(),healthOutlineCol,{2,2},{1.8f,1.8f},INFINITE);
DrawShadowStringPropDecal({20,3},text,healthCounter.GetDisplayColor(),healthOutlineCol,{2,2},{1.8f,1.8f},INFINITE);
DrawShadowStringPropDecal({24,23},text_mana,manaCounter.GetDisplayColor(),BLACK,{1.5f,1.5f},{1.45f,1.45f},INFINITE);
#pragma region Show Max Health/Max Mana
if(GameSettings::ShowMaxHealth()){
vf2d healthTextSize=GetTextSizeProp(text)*vf2d{2.f,2.f};
if(GameSettings::ShowMaxHealth()){
vf2d healthTextSize=GetTextSizeProp(text)*vf2d{2.f,2.f};
std::string maxHealthText="/"+std::to_string(int(player->GetMaxHealth()));
float maxHealthTextHeight=GetTextSizeProp(maxHealthText).y;
DrawShadowStringPropDecal(vf2d{20,3}+healthTextSize+vf2d{1.f,-maxHealthTextHeight},maxHealthText,{200,200,200,255},healthOutlineCol,{1.f,1.f},{1.f,1.f},INFINITE);
}
if(GameSettings::ShowMaxMana()){
vf2d manaTextSize=GetTextSizeProp(text_mana)*vf2d{1.5f,1.5f};
std::string maxHealthText="/"+std::to_string(int(player->GetMaxHealth()));
float maxHealthTextHeight=GetTextSizeProp(maxHealthText).y;
DrawShadowStringPropDecal(vf2d{20,3}+healthTextSize+vf2d{1.f,-maxHealthTextHeight},maxHealthText,{200,200,200,255},healthOutlineCol,{1.f,1.f},{1.f,1.f},INFINITE);
}
if(GameSettings::ShowMaxMana()){
vf2d manaTextSize=GetTextSizeProp(text_mana)*vf2d{1.5f,1.5f};
std::string maxManaText="/"+std::to_string(player->GetMaxMana());
float maxManaTextHeight=GetTextSizeProp(maxManaText).y;
DrawShadowStringPropDecal(vf2d{24,23}+manaTextSize+vf2d{1.f,-maxManaTextHeight},maxManaText,{200,200,255,255},BLACK,{1.f,1.f},{1.f,1.f},INFINITE);
}
std::string maxManaText="/"+std::to_string(player->GetMaxMana());
float maxManaTextHeight=GetTextSizeProp(maxManaText).y;
DrawShadowStringPropDecal(vf2d{24,23}+manaTextSize+vf2d{1.f,-maxManaTextHeight},maxManaText,{200,200,255,255},BLACK,{1.f,1.f},{1.f,1.f},INFINITE);
}
#pragma endregion
if(player->notEnoughManaDisplay.second>0){
@ -2120,9 +2104,7 @@ void AiL::RenderCooldowns(){
if(loadoutSlot!=-1){
drawOffset.y+=2.f;
}
InputGroup*input{a.input};
if(a==player->GetAbility4())input=&Player::KEY_ABILITY4;
input->DrawPrimaryInput(game,pos+vf2d{14,0.f}+drawOffset,"",255,controlType,{0.75f,0.75f});
a.input->DrawPrimaryInput(game,pos+vf2d{14,0.f}+drawOffset,"",255,controlType,{0.75f,0.75f});
if(a.cooldown>0.1){
vf2d iconScale={1,1};
@ -2179,7 +2161,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,Pixel{0xE0E0E0},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;
@ -2228,22 +2210,17 @@ void AiL::RenderCooldowns(){
}
}
std::pair<ForegroundWrapper,BackgroundWrapper>AiL::AddEffect(std::unique_ptr<Effect>foreground,std::unique_ptr<Effect> background){
ForegroundWrapper foregroundRef{*foreground};
BackgroundWrapper backgroundRef{*background};
void AiL::AddEffect(std::unique_ptr<Effect>foreground,std::unique_ptr<Effect> background){
AddEffect(std::move(background),true);
AddEffect(std::move(foreground));
return {foregroundRef,backgroundRef};
}
Effect&AiL::AddEffect(std::unique_ptr<Effect> foreground,bool back){
ForegroundWrapper effectRef{*foreground};
void AiL::AddEffect(std::unique_ptr<Effect> foreground,bool back){
if(back){
backgroundEffectsToBeInserted.emplace_back(std::move(foreground));
backgroundEffectsToBeInserted.push_back(std::move(foreground));
} else {
foregroundEffectsToBeInserted.emplace_back(std::move(foreground));
foregroundEffectsToBeInserted.push_back(std::move(foreground));
}
return effectRef;
}
vf2d AiL::GetWorldMousePos(){
@ -2402,7 +2379,7 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
#pragma region Reset all data (Loading phase 1)
LoadingScreen::AddPhase([&](){
if(game->MAP_DATA.count(GetCurrentLevel())==0)ERR(std::format("WARNING! Could not load map {}! Does not exist! Refer to levels.txt for valid maps.",map));
if(game->MAP_DATA[GetCurrentLevel()].GetMapType()==Map::MapType::HUB&&GameState::STATE!=GameState::states[States::GAME_HUB])ERR("WARNING! A hub level should only be initiated in the GAME_HUB game state!");
if(game->MAP_DATA[GetCurrentLevel()].GetMapType()=="Hub"&&GameState::STATE!=GameState::states[States::GAME_HUB])ERR("WARNING! A hub level should only be initiated in the GAME_HUB game state!");
#pragma region Reset Environmental Audio
for(EnvironmentalAudio&audio:MAP_DATA[previousLevel].environmentalAudioData){
@ -2410,7 +2387,55 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
}
#pragma endregion
ResetLevelStates();
bossIndicatorPos.reset();
SPAWNER_LIST.clear();
SPAWNER_CONTROLLER={};
foregroundTileGroups.clear();
upperForegroundTileGroups.clear();
MONSTER_LIST.clear();
BULLET_LIST.clear();
DAMAGENUMBER_LIST.clear();
hudOverlay.Reset();
backgroundEffects.clear();
foregroundEffects.clear();
lockOnTargets.clear();
lockOnSpecialTargets.clear();
ItemDrop::drops.clear();
GameEvent::events.clear();
Audio::SetBGMPitch(1.f);
RepeatingSoundEffect::StopAllSounds();
#ifdef __EMSCRIPTEN__
Audio::muted=true;
Audio::UpdateBGMVolume();
#endif
zoomAdjustSpeed="Default Zoom Adjust Speed"_F;
targetZoom=1.f;
game->view.SetZoom(0.95f,game->view.WorldToScreen(game->camera.GetViewPosition()));
worldColor=WHITE;
worldColorFunc=[&](vi2d pos){return game->worldColor;};
levelTime=0;
bossName="";
bossDisplayTimer=0.f;
worldShakeTime=0.f;
encounterDuration=0;
totalDamageDealt=0;
encounterStarted=false;
totalBossEncounterMobs=0;
SetWindSpeed({});
Inventory::ResetLoadoutItemsUsed();
Input::StopVibration();
Input::SetLightbar({255,0,255});
GetPlayer()->hp=GetPlayer()->GetMaxHealth();
GetPlayer()->mana=GetPlayer()->GetMaxMana();
GetPlayer()->SetState(State::NORMAL);
GetPlayer()->GetCastInfo()={};
GetPlayer()->ResetAccumulatedXP();
GetPlayer()->_SetIframes(0.f);
GetPlayer()->SetInvisible(false);
GetPlayer()->ResetVelocity();
GetPlayer()->RemoveAllBuffs();
GetPlayer()->ResetTimers();
STEAMINPUT( //This is kind of a hack to refresh the in-game controls handle and button icons if for some reason it's not setup correctly.
Input::LoadSteamButtonIcons();
@ -2452,9 +2477,7 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
vf2d spawnerRadius=vf2d{spawnData.ObjectData.GetFloat("width"),spawnData.ObjectData.GetFloat("height")}/2;
for(XMLTag&monster:spawnData.monsters){
std::string monsterName=monster.GetString("value");
const vi2d frameSize{ANIMATION_DATA[std::format("{}_{}",monsterName,MONSTER_DATA[monsterName].GetDefaultIdleAnimation())].GetFrame(0.f).GetSourceRect().size};
const float monsterSizeMult{MONSTER_DATA[monsterName].GetSizeMult()};
monster_list.emplace_back(monsterName,vf2d{monster.GetInteger("x")+(frameSize.x*monsterSizeMult/2)-spawnData.ObjectData.GetFloat("x"),monster.GetInteger("y")-(frameSize.y*monsterSizeMult/2)-spawnData.ObjectData.GetFloat("y")});
monster_list.push_back({monsterName,{monster.GetInteger("x")-spawnData.ObjectData.GetFloat("x"),monster.GetInteger("y")-spawnData.ObjectData.GetFloat("y")}});
}
const int spawnerId=spawnData.ObjectData.GetInteger("id");
@ -2692,7 +2715,7 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
LoadingScreen::AddPhase([&](){
for(NPCData data:game->MAP_DATA[game->GetCurrentLevel()].npcs){
if(Unlock::IsUnlocked(data.unlockCondition)){
MONSTER_LIST.emplace_back(std::make_unique<Monster>(data.spawnPos,MONSTER_DATA[data.name]));
MONSTER_LIST.push_back(std::make_unique<Monster>(data.spawnPos,MONSTER_DATA[data.name]));
MONSTER_LIST.back()->ApplyIframes(INFINITE);
MONSTER_LIST.back()->npcData=data;
}
@ -2719,7 +2742,7 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
#pragma region Setup Pathfinding (Loading Phase 9)
LoadingScreen::AddPhase([&](){
Audio::SetAudioEvent(GetCurrentMap().GetDefaultAudioEvent());
Audio::SetAudioEvent("Default Volume");
#ifdef __EMSCRIPTEN__
Audio::muted=false;
Audio::UpdateBGMVolume();
@ -2732,6 +2755,7 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
if(changeMusic==PLAY_LEVEL_MUSIC){
#pragma region Audio Preparation (Loading Phase 10)
LoadingScreen::AddPhase([&](){
Audio::SetAudioEvent("Default Volume");
game->audioEngine.fullyLoaded=true; //We assume there's no audio to load, so we just set the audio as fully loaded by default.
if(MAP_DATA[GetCurrentLevel()].bgmSongName.length()>0){
Audio::PrepareBGM(MAP_DATA[GetCurrentLevel()].bgmSongName);
@ -2930,6 +2954,7 @@ const MapName&AiL::GetCurrentLevel()const{
}
void AiL::ChangePlayerClass(Class cl){
if(game->GetPlayer()->GetClass()==cl)return;
Ability itemAbility1=player->useItem1;
Ability itemAbility2=player->useItem2;
Ability itemAbility3=player->useItem3;
@ -2938,11 +2963,9 @@ void AiL::ChangePlayerClass(Class cl){
uint8_t levelCap=player->levelCap;
uint32_t totalXPEarned=player->totalXPEarned;
uint32_t currentLevelXP=player->currentLevelXP;
std::vector<std::pair<PlayerTimerType,Player::ShieldAmount>>previousShield=player->shield;
std::vector<std::weak_ptr<MenuComponent>>moneyListeners=Player::moneyListeners;
EntityStats previousStats=player->stats;
size_t cooldownSoundInstance=player->cooldownSoundInstance;
std::unordered_map<PlayerTimerType,Timer>oldTimers=player->timers;
switch(cl){
case WARRIOR:{
player.reset(NEW Warrior(player.get()));
@ -2992,12 +3015,9 @@ void AiL::ChangePlayerClass(Class cl){
GetPlayer()->InitializeMinimapImage();
player->RecalculateEquipStats();
player->OnLevelStart();
player->timers=oldTimers;
player->shield=previousShield;
}
void AiL::InitializeClasses(){
Player::ABILITY_LIST.clear();
Warrior::Initialize();
Thief::Initialize();
Ranger::Initialize();
@ -3357,7 +3377,7 @@ void AiL::InitializeLevels(){
MAP_DATA[cp.map].name=cp.name;
//Boss arena map zone check.
if(MAP_DATA[cp.map].GetMapType()==Map::MapType::BOSS&&MAP_DATA[cp.map].GetZones().at("BossArena").size()==0)ERR(std::format("WARNING! Map {} doesn't have a boss arena region defined! Add an object of class BossArena to the map.",MAP_DATA[cp.map].GetMapDisplayName()));
if(MAP_DATA[cp.map].GetMapType()=="Boss"&&MAP_DATA[cp.map].GetZones().at("BossArena").size()==0)ERR(std::format("WARNING! Map {} doesn't have a boss arena region defined! Add an object of class BossArena to the map.",MAP_DATA[cp.map].GetMapDisplayName()));
for(std::string spawn:MAP_DATA[cp.map].spawns){
cp.spawns.push_back(MONSTER_DATA.at(spawn).GetDisplayName());
@ -3678,10 +3698,6 @@ void AiL::RenderMenu(){
}
#endif
#pragma endregion
for(Notification&notification:notifications){
notification.Draw(*this);
}
}
void AiL::InitializeGraphics(){
@ -3695,7 +3711,7 @@ void AiL::InitializeGraphics(){
circleCooldownPoints.emplace_back(vf2d{0,0});
squareCircleCooldownPoints.emplace_back(vf2d{0,0});
for(int i=0;i<=360;i+=4){
float angle=util::degToRad(float(i))-PI/2;
float angle=util::degToRad(float(i))-PI/2;
#pragma region Cooldown Circles
if(i==0){circleCooldownPoints.emplace_back(vf2d{cos(angle),sin(angle)});}
circleCooldownPoints.emplace_back(vf2d{cos(angle),sin(angle)});
@ -3931,28 +3947,28 @@ void AiL::SetLoadoutItem(int slot,std::string itemName){
inputGroup=&Player::KEY_ITEM3;
}break;
}
Ability itemAbility{itemName,"","","Item.Item Cooldown Time"_F,0,inputGroup,"items/"+itemName+".png",VERY_DARK_RED,DARK_RED,PrecastData{GetLoadoutItem(slot).lock()->CastTime(),GetLoadoutItem(slot).lock()->CastRange(),GetLoadoutItem(slot).lock()->CastSize()},true};
Ability itemAbility{itemName,"","","Item.Item Cooldown Time"_F,0,inputGroup,"items/"+itemName+".png",VERY_DARK_RED,DARK_RED,PrecastData{GetLoadoutItem(slot).lock()->CastTime(),0,0},true};
itemAbility.actionPerformedDuringCast=GetLoadoutItem(slot).lock()->UseDuringCast();
itemAbility.itemAbility=true;
switch(slot){
case 0:{
itemAbility.action=[&](Player*p,vf2d pos={}){
return game->UseLoadoutItem(0,pos);
return game->UseLoadoutItem(0);
};
game->GetPlayer()->SetItem1UseFunc(itemAbility);
Component<MenuItemItemButton>(MenuType::ITEM_LOADOUT,"Loadout Item 1")->SetItem(loadout[slot]);
}break;
case 1:{
itemAbility.action=[&](Player*p,vf2d pos={}){
return game->UseLoadoutItem(1,pos);
return game->UseLoadoutItem(1);
};
game->GetPlayer()->SetItem2UseFunc(itemAbility);
Component<MenuItemItemButton>(MenuType::ITEM_LOADOUT,"Loadout Item 2")->SetItem(loadout[slot]);
}break;
case 2:{
itemAbility.action=[&](Player*p,vf2d pos={}){
return game->UseLoadoutItem(2,pos);
return game->UseLoadoutItem(2);
};
game->GetPlayer()->SetItem3UseFunc(itemAbility);
Component<MenuItemItemButton>(MenuType::ITEM_LOADOUT,"Loadout Item 3")->SetItem(loadout[slot]);
@ -3976,11 +3992,11 @@ void AiL::SetLoadoutItem(int slot,std::string itemName){
}
}
bool AiL::UseLoadoutItem(int slot,const std::optional<vf2d>targetingPos){
bool AiL::UseLoadoutItem(int slot){
if(slot<0||slot>GetLoadoutSize()-1)ERR("Invalid inventory slot "+std::to_string(slot)+", please choose a slot in range (0-"+std::to_string(GetLoadoutSize()-1)+").");
if(!ISBLANK(GetLoadoutItem(slot).lock())&&GetLoadoutItem(slot).lock()->Amt()>0){
Tutorial::GetTask(TutorialTaskName::USE_RECOVERY_ITEMS).I(A::ITEM_USE_COUNT)++;
Inventory::UseItem(GetLoadoutItem(slot).lock()->ActualName(),1U,targetingPos);
Inventory::UseItem(GetLoadoutItem(slot).lock()->ActualName());
Inventory::AddLoadoutItemUsed(GetLoadoutItem(slot).lock()->ActualName(),slot);
GetLoadoutItem(slot).lock()->amt--;
if(GetLoadoutItem(slot).lock()->UseSound().length()>0){
@ -4010,7 +4026,7 @@ void AiL::ClearLoadoutItem(int slot){
switch(slot){
case 0:{
itemAbility.action=[&](Player*p,vf2d pos={}){
return game->UseLoadoutItem(0,pos);
return game->UseLoadoutItem(0);
};
game->GetPlayer()->SetItem1UseFunc(itemAbility);
Component<MenuItemItemButton>(MenuType::ITEM_LOADOUT,"Loadout Item 1")->SetItem(Item::BLANK);
@ -4018,7 +4034,7 @@ void AiL::ClearLoadoutItem(int slot){
}break;
case 1:{
itemAbility.action=[&](Player*p,vf2d pos={}){
return game->UseLoadoutItem(1,pos);
return game->UseLoadoutItem(1);
};
game->GetPlayer()->SetItem2UseFunc(itemAbility);
Component<MenuItemItemButton>(MenuType::ITEM_LOADOUT,"Loadout Item 2")->SetItem(Item::BLANK);
@ -4026,7 +4042,7 @@ void AiL::ClearLoadoutItem(int slot){
}break;
case 2:{
itemAbility.action=[&](Player*p,vf2d pos={}){
return game->UseLoadoutItem(2,pos);
return game->UseLoadoutItem(2);
};
game->GetPlayer()->SetItem3UseFunc(itemAbility);
Component<MenuItemItemButton>(MenuType::ITEM_LOADOUT,"Loadout Item 3")->SetItem(Item::BLANK);
@ -4046,7 +4062,7 @@ void AiL::RenderFadeout(){
SetMosaicEffect(1U);
GameState::_ChangeState(transitionState);
}else{
SetMosaicEffect(util::lerp(uint8_t(1),mosaicEffectTransition,1-(fadeOutDuration/fadeOutTotalTime)));
SetMosaicEffect(util::lerp(1U,mosaicEffectTransition,1-(fadeOutDuration/fadeOutTotalTime)));
alpha=uint8_t(util::lerp(0,255,1-(fadeOutDuration/fadeOutTotalTime)));
}
}else
@ -4191,7 +4207,6 @@ void AiL::ResetGame(bool changeToMainMenu){
Tutorial::Initialize();
minimap.SetMinimapMode(MinimapMode::SMALL);
minimap.EraseChunkData();
ClearDecalInstances();
}
void AiL::OnRequestCompleted(const std::string_view receivedData)const{
@ -4437,26 +4452,22 @@ void AiL::GlobalGameUpdates(){
}
#pragma endregion
for(Notification&notification:notifications){
notification.Update(GetElapsedTime());
}
std::erase_if(notifications,[](const Notification&notification){return notification.Expired();});
if(GetMousePos()!=lastMousePos){
lastMouseMovement=0.f;
lastMousePos=GetMousePos();
}else lastMouseMovement+=GetElapsedTime();
playerShieldDisplayAmt=player->GetShield();
vignetteDisplayTime=std::max(0.f,vignetteDisplayTime-GetElapsedTime());
if(Audio::Engine().IsPlaying(GetPlayer()->cooldownSoundInstance)){
Audio::Engine().SetVolume(GetPlayer()->cooldownSoundInstance,Audio::GetCalculatedSFXVolume("Audio.Casting Sound Volume"_F/100.f));
}
if(!GamePaused())GameState::STATE->OnUserUpdate(this);
else ClearTimedOutGarbage();
if(!GamePaused()){
GameState::STATE->OnUserUpdate(this);
}else{
ClearTimedOutGarbage();
}
}
@ -4541,12 +4552,6 @@ void AiL::UsingSteamAPI(const bool usingSteam){
void AiL::InitializePlayer(){
player=std::make_unique<Warrior>();
Player::moneyListeners.clear();
Warrior::ability4=Ability{};
Ranger::ability4=Ability{};
Wizard::ability4=Ability{};
Thief::ability4=Ability{};
Trapper::ability4=Ability{};
Witch::ability4=Ability{};
}
void AiL::SetWorldZoom(float newZoomScale){
@ -4597,12 +4602,11 @@ std::vector<Effect*>AiL::GetBackgroundEffects()const{
std::for_each(backgroundEffects.begin(),backgroundEffects.end(),[&outputVec](const std::unique_ptr<Effect>&eff){outputVec.emplace_back(eff.get());});
return outputVec;
}
[[nodiscard]]std::vector<Effect*>AiL::GetAllEffects()const{
std::vector<Effect*>AiL::GetAllEffects()const{
std::vector<Effect*>outputVec;
std::vector<Effect*>foregroundEffects{GetForegroundEffects()};
std::vector<Effect*>backgroundEffects{GetBackgroundEffects()};
std::copy(foregroundEffects.begin(),foregroundEffects.end(),std::back_inserter(outputVec));
std::copy(backgroundEffects.begin(),backgroundEffects.end(),std::back_inserter(outputVec));
std::merge(foregroundEffects.begin(),foregroundEffects.end(),backgroundEffects.begin(),backgroundEffects.end(),std::back_inserter(outputVec));
return outputVec;
}
@ -4614,84 +4618,4 @@ void AiL::InitializeCamera(){
camera.SetMode(olc::utils::Camera2D::Mode::LazyFollow);
camera.SetWorldBoundary({0,0},GetCurrentMap().MapData.MapSize*GetCurrentMap().MapData.TileSize);
camera.EnableWorldBoundary(false);
}
void AiL::ResetLevelStates(){
bossIndicatorPos.reset();
SPAWNER_LIST.clear();
SPAWNER_CONTROLLER={};
foregroundTileGroups.clear();
upperForegroundTileGroups.clear();
MONSTER_LIST.clear();
BULLET_LIST.clear();
DAMAGENUMBER_LIST.clear();
hudOverlay.Reset();
backgroundEffects.clear();
foregroundEffects.clear();
lockOnTargets.clear();
lockOnSpecialTargets.clear();
ItemDrop::drops.clear();
GameEvent::events.clear();
Audio::SetBGMPitch(1.f);
RepeatingSoundEffect::StopAllSounds();
#ifdef __EMSCRIPTEN__
Audio::muted=true;
Audio::UpdateBGMVolume();
#endif
zoomAdjustSpeed="Default Zoom Adjust Speed"_F;
targetZoom=1.f;
game->view.SetZoom(0.95f,game->view.WorldToScreen(game->camera.GetViewPosition()));
worldColor=WHITE;
worldColorFunc=[&](vi2d pos){return game->worldColor;};
levelTime=0;
bossName="";
bossDisplayTimer=0.f;
worldShakeTime=0.f;
encounterDuration=0;
totalDamageDealt=0;
encounterStarted=false;
totalBossEncounterMobs=0;
SetWindSpeed({});
Inventory::ResetLoadoutItemsUsed();
Input::StopVibration();
Input::SetLightbar({255,0,255});
GetPlayer()->hp=GetPlayer()->GetMaxHealth();
GetPlayer()->mana=GetPlayer()->GetMaxMana();
GetPlayer()->SetState(State::NORMAL);
GetPlayer()->GetCastInfo()={};
GetPlayer()->ResetAccumulatedXP();
GetPlayer()->_SetIframes(0.f);
GetPlayer()->SetInvisible(false);
GetPlayer()->ResetVelocity();
GetPlayer()->RemoveAllBuffs();
GetPlayer()->ResetTimers();
}
void AiL::Notification::Update(const float fElapsedTime){
time-=fElapsedTime;
}
const bool AiL::Notification::Expired()const{
return time<=0.f;
}
const bool AiL::Notification::operator==(const Notification&n)const{
return message==n.message;
}
void AiL::Notification::Draw(PixelGameEngine&pge){
pge.DrawShadowStringPropDecal(vf2d{float(pge.ScreenWidth()/2),float(pge.ScreenHeight()/4)}-pge.GetTextSizeProp(message)/2,message,col,col/2);
}
void AiL::AddNotification(const Notification&n){
auto it=std::find(notifications.begin(),notifications.end(),n);
if(it!=notifications.end())*it=n;
else notifications.emplace_back(n);
}
AiL::Notification::Notification(const std::string_view message,const float time,const Pixel col)
:message(message),time(time),col(col){}
const std::vector<Effect*>AiL::GetEffect(EffectType type){
std::vector<Effect*>effects{GetAllEffects()};
std::vector<Effect*>matchingEffects{};
std::copy_if(effects.begin(),effects.end(),std::back_inserter(matchingEffects),[&type](const Effect*effect){return effect->GetType()==type;});
return matchingEffects;
}

@ -60,12 +60,6 @@ All rights reserved.
#include "Overlay.h"
#include <variant>
#undef KEY_MENU
#undef KEY_SELECT
#undef KEY_SCROLLDOWN
#undef KEY_SCROLLUP
#undef KEY_ENTER
class SteamKeyboardCallbackHandler;
class SteamStatsReceivedHandler;
@ -76,12 +70,14 @@ INCLUDE_BULLET_LIST
#define EndBullet )).get()))
using HurtReturnValue=bool;
using HurtListItem=std::pair<std::variant<Monster*,Player*>,HurtReturnValue>;
using HurtList=std::vector<HurtListItem>;
using HurtList=std::vector<std::pair<std::variant<Monster*,Player*>,HurtReturnValue>>;
using StackCount=uint8_t;
using MarkTime=float;
using ForegroundWrapper=std::reference_wrapper<Effect>;
using BackgroundWrapper=std::reference_wrapper<Effect>;
enum class HurtType{
PLAYER=0b01,
MONSTER=0b10,
};
enum class KnockbackCondition{
KNOCKBACK_HURT_TARGETS, //Knockback only targets that took damage.
@ -101,23 +97,9 @@ class AiL : public olc::PixelGameEngine
friend class sig::Animation;
friend class Audio;
friend class Minimap;
friend class MiniAudio;
friend class Arc;
std::unique_ptr<Player>player;
SplashScreen splash;
public:
class Notification{
std::string message;
float time;
Pixel col;
public:
#undef GetMessage
Notification(const std::string_view message,const float time,const Pixel col);
void Update(const float fElapsedTime);
const bool Expired()const;
const bool operator==(const Notification&n)const;
void Draw(PixelGameEngine&pge);
};
enum MusicChange{
NO_MUSIC_CHANGE,
PLAY_LEVEL_MUSIC,
@ -168,10 +150,8 @@ public:
double levelTime=0.;
Camera2D camera;
std::map<MapName,Map>MAP_DATA;
ResourcePack gamepack;
private:
std::vector<std::unique_ptr<Effect>>foregroundEffects,backgroundEffects,foregroundEffectsToBeInserted,backgroundEffectsToBeInserted;
std::vector<std::unique_ptr<Effect>>foregroundEffects,backgroundEffects,foregroundEffectsToBeInserted,backgroundEffectsToBeInserted;
std::vector<TileRenderData*>tilesWithCollision,tilesWithoutCollision;
std::vector<int>dropsBeforeLower,dropsAfterLower,dropsBeforeUpper,dropsAfterUpper;
std::vector<ZoneData>endZones,upperEndZones;
@ -216,14 +196,13 @@ private:
bool disableFadeIn=false;
DynamicCounter healthCounter;
DynamicCounter manaCounter;
DynamicCounter shieldCounter;
int playerShieldDisplayAmt{};
Pixel worldColor=WHITE;
std::function<Pixel(vi2d)>worldColorFunc=[](vi2d pos){return WHITE;};
std::map<std::string,std::vector<::ZoneData>>ZONE_LIST;
float lastMouseMovement=0.f; //Amount of time since the last time the cursor was moved or interacted with.
vi2d lastMousePos={};
bool gameInitialized=false;
ResourcePack gamepack;
uint8_t mosaicEffectTransition=1U;
float saveGameDisplayTime=0.f;
float loadingWaitTime=0.f;
@ -255,7 +234,6 @@ private:
void AdminConsole();
virtual bool OnConsoleCommand(const std::string& sCommand)override final;
Pixel vignetteOverlayCol{"Interface.Vignette Color"_Pixel};
std::vector<Notification>notifications;
public:
AiL(bool testingMode=false);
bool OnUserCreate() override;
@ -280,10 +258,9 @@ public:
void RenderHud();
void RenderMenu();
bool MenuClicksDeactivated()const;
std::pair<ForegroundWrapper,BackgroundWrapper>AddEffect(std::unique_ptr<Effect>foreground,std::unique_ptr<Effect>background);
void AddEffect(std::unique_ptr<Effect>foreground,std::unique_ptr<Effect>background);
//If back is true, places the effect in the background
Effect&AddEffect(std::unique_ptr<Effect>foreground,bool back=false);
const std::vector<Effect*>GetEffect(EffectType type);
void AddEffect(std::unique_ptr<Effect>foreground,bool back=false);
const HurtList Hurt(vf2d pos,float radius,int damage,bool upperLevel,float z,const HurtType hurtTargets,HurtFlag::HurtFlag hurtFlags=HurtFlag::NONE)const;
const HurtList HurtMonsterType(vf2d pos,float radius,int damage,bool upperLevel,float z,const std::string_view monsterName,HurtFlag::HurtFlag hurtFlags=HurtFlag::NONE)const;
//NOTE: This function will also add any enemies that were hit into the hit list!
@ -364,7 +341,7 @@ public:
int GetLoadoutSize()const;
void RestockLoadoutItems();
//Returns true if the item can be used (we have >0 of it)
bool UseLoadoutItem(int slot,const std::optional<vf2d>targetingPos);
bool UseLoadoutItem(int slot);
//Blanks out this loadout item.
void ClearLoadoutItem(int slot);
void RenderFadeout();
@ -416,9 +393,6 @@ public:
void InitializeClasses();
void _SetCurrentLevel(const MapName map); //NOTE: This will modify the currentLevel variable without triggering anything else in-game, this will normally mess up the state in the game. Ideally this is only used when initializing a test level.
void InitializeCamera();
void AddNotification(const Notification&n);
void ResetLevelStates();
std::vector<Effect*>GetForegroundEffects()const;
std::vector<Effect*>GetBackgroundEffects()const;
@ -431,4 +405,4 @@ public:
return layer<rhs.layer||(layer==rhs.layer&&tilePos<rhs.tilePos);
}
};
};
};

@ -48,6 +48,7 @@ INCLUDE_GFX
void sig::Animation::InitializeAnimations(){
ANIMATION_DATA.Reset();
auto CreateStillAnimation=[&](std::string imgName,vf2d size,AnimationData data={}){
Animate2D::FrameSequence anim(data.frameDuration,data.style);
anim.AddFrame({&GFX[imgName],{{0,0},size}});
@ -346,28 +347,6 @@ void sig::Animation::InitializeAnimations(){
ANIMATION_DATA["WITCH_TRANSFORM_W"]=pl_witch_transform_w;
ANIMATION_DATA["WITCH_TRANSFORM_E"]=pl_witch_transform_e;
Animate2D::FrameSequence pl_witch_cat_walk_s(0.2f);
for(int i=0;i<2;i++){
pl_witch_cat_walk_s.AddFrame({&GFX["nico-witch.png"],{vi2d{0+i,4}*24,{24,24}}});
}
ANIMATION_DATA["WITCH_CAT_WALK_S"]=pl_witch_cat_walk_s;
Animate2D::FrameSequence pl_witch_cat_walk_n(0.2f);
for(int i=0;i<2;i++){
pl_witch_cat_walk_n.AddFrame({&GFX["nico-witch.png"],{vi2d{0+i,5}*24,{24,24}}});
}
ANIMATION_DATA["WITCH_CAT_WALK_N"]=pl_witch_cat_walk_n;
Animate2D::FrameSequence pl_witch_cat_walk_w(0.2f);
for(int i=0;i<2;i++){
pl_witch_cat_walk_w.AddFrame({&GFX["nico-witch.png"],{vi2d{0+i,6}*24,{24,24}}});
}
ANIMATION_DATA["WITCH_CAT_WALK_W"]=pl_witch_cat_walk_w;
Animate2D::FrameSequence pl_witch_cat_walk_e(0.2f);
for(int i=0;i<2;i++){
pl_witch_cat_walk_e.AddFrame({&GFX["nico-witch.png"],{vi2d{0+i,7}*24,{24,24}}});
}
ANIMATION_DATA["WITCH_CAT_WALK_E"]=pl_witch_cat_walk_e;
CreateHorizontalAnimationSequence("ground-slam-attack-back.png",5,{64,64},{0.02f,Animate2D::Style::OneShot});
CreateHorizontalAnimationSequence("ground-slam-attack-front.png",5,{64,64},{0.02f,Animate2D::Style::OneShot});
CreateHorizontalAnimationSequence("battlecry_effect.png",5,{84,84},{0.02f,Animate2D::Style::OneShot});
@ -401,8 +380,6 @@ void sig::Animation::InitializeAnimations(){
CreateHorizontalAnimationSequence("bomb_boom.png",5,{36,36},AnimationData{.frameDuration{0.2f},.style{Animate2D::Style::OneShot}});
CreateHorizontalAnimationSequence("tornado2.png",4,{24,48},AnimationData{.frameDuration{0.1f},.style{Animate2D::Style::Repeat}});
CreateHorizontalAnimationSequence("large_tornado.png",4,{72,144},AnimationData{.frameDuration{0.1f},.style{Animate2D::Style::Repeat}});
CreateHorizontalAnimationSequence("sand_suction.png",4,{72,72},AnimationData{.frameDuration{0.2f},.style{Animate2D::Style::Repeat}});
CreateHorizontalAnimationSequence("bomb.png",4,{24,24},AnimationData{.frameDuration{0.2f},.style{Animate2D::Style::OneShot}});
CreateStillAnimation("meteor.png",{192,192});
@ -435,14 +412,6 @@ void sig::Animation::InitializeAnimations(){
ANIMATION_DATA[std::format("GOBLIN_BOW_ATTACK_{}",animationIndex)]=mountShootAnimation;
}
Animate2D::FrameSequence parrot_sit_n,parrot_sit_e,parrot_sit_s,parrot_sit_w;
//Idle sequences for the sitting parrot.
for(int animationIndex=0;animationIndex<4;animationIndex++){
Animate2D::FrameSequence mountAnimation{0.6f};
mountAnimation.AddFrame(Animate2D::Frame{&GFX["monsters/commercial_assets/Parrot_foreground.png"],{{0,animationIndex*48},{48,48}}});
ANIMATION_DATA[std::format("PARROT_MOUNTED_{}",animationIndex)]=mountAnimation;
}
#pragma region Trapper Target Mark Debuff
AnimationData targetAnimData{.frameDuration{0.1f},.style{Animate2D::Style::Repeat}};
Animate2D::FrameSequence targetAnim(targetAnimData.frameDuration,targetAnimData.style);
@ -461,14 +430,7 @@ void sig::Animation::InitializeAnimations(){
CreateHorizontalAnimationSequence("bear_trap.png",3,{24,24},AnimationData{.frameDuration{0.1f},.style{Animate2D::Style::PingPong}});
CreateHorizontalAnimationSequence("explosive_trap.png",4,{48,48},AnimationData{.frameDuration{0.06f},.style{Animate2D::Style::PingPong}});
CreateHorizontalAnimationSequence("explosionframes.png",21,{24,24},AnimationData{.frameDuration{0.02f},.style{Animate2D::Style::OneShot}});
CreateHorizontalAnimationSequence("fire_ring.png",5,{60,60},AnimationData{.frameDuration{0.1f},.style{Animate2D::Style::Repeat}});
CreateHorizontalAnimationSequence("portal.png",8,{24,24},AnimationData{.frameDuration{0.1f},.style{Animate2D::Style::Repeat}});
//!!!!!WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//DO NOT CREATE MORE ANIMATION SEQUENCES UNDERNEATH HERE AS THE NEXT BLOCK WILL CREATE DEFAULT ANIMATIONS
//FOR ALL NON-SPECIFIED ANIMATION SEQUENCES!
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
for(auto&dat:GFX){
std::string imgFile=dat.first;
if(!ANIMATION_DATA.count(imgFile)){

@ -1,79 +0,0 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "Arc.h"
#include "AdventuresInLestoria.h"
INCLUDE_game
Arc::Arc(const vf2d pos,const float radius,const float pointingAngle,const float sweepAngle)
:pos(pos),radius(radius),pointingAngle(pointingAngle),sweepAngle(sweepAngle){
if(sweepAngle<0.f)ERR(std::format("WARNING! Sweep angle must be greater than or equal to 0! Provided Sweep Angle: {}",sweepAngle));
GenerateArc();
}
void Arc::Draw(AiL*game,const Pixel col){
game->SetDecalStructure(DecalStructure::FAN);
game->view.DrawPolygonDecal(nullptr,poly.pos,poly.pos,col);
}
const bool Arc::overlaps(const vf2d checkPos)const{
return geom2d::overlaps(checkPos,poly);
}
void Arc::GrowRadius(const float growAmt){
radius+=growAmt;
GenerateArc();
}
void Arc::GenerateArc(){
poly.pos.clear();
//Use cut-off point between two angles
poly.pos.emplace_back(pos); //Always add 0,0
float smallestAng{util::radToDeg(pointingAngle-sweepAngle)+90};
float largestAng{util::radToDeg(pointingAngle+sweepAngle)+90};
while(smallestAng<0.f)smallestAng+=360;
while(largestAng<0.f)largestAng+=360;
int startInd{int(std::fmod(smallestAng/4+1,90))};
int endInd{int(std::fmod(largestAng/4+1,90))};
if(startInd>endInd){
startInd=(startInd+3)%90;
}else{
endInd=(endInd+3)%90;
}
for(int i=startInd;i!=endInd;i=(i+1)%game->circleCooldownPoints.size()){
poly.pos.emplace_back(pos+game->circleCooldownPoints[i]*radius);
}
poly.pos.emplace_back(pos); //Connect back to itself.
}

@ -1,59 +0,0 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#pragma once
#include "olcUTIL_Geometry2D.h"
#include "Pixel.h"
class AiL;
class Arc{
public:
//Define a sweep angle such that each direction will arc that way. Example: PI/2 means a sweep angle from -PI/2 to PI/2. MUST BE POSITIVE
Arc(const vf2d pos,const float radius,const float pointingAngle,const float sweepAngle);
void Draw(AiL*game,const Pixel col);
const bool overlaps(const vf2d checkPos)const;
void GrowRadius(const float growAmt);
const vf2d pos;
const float pointingAngle;
const float sweepAngle;
float radius;
geom2d::polygon<float>poly;
private:
void GenerateArc();
};

@ -35,31 +35,31 @@ Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#pragma once
#include "util.h"
#include "Menu.h"
#include "AdventuresInLestoria.h"
template<class T>
class Oscillator{
friend struct ThrownProjectile;
public:
inline Oscillator()
:Oscillator({},{},0.f){}
inline Oscillator(T val1,T val2,float cycleSpd)
:val1(val1),val2(val2),first(this->val1),second(this->val2),cycleSpd(cycleSpd),currentVal(val1){}
inline const T&Update(const float fElapsedTime){
currentVal=util::lerp(val1,val2,sin(PI*timer*cycleSpd)/2+0.5f);
timer+=fElapsedTime;
return get();
INCLUDE_game
void Menu::InitializeArtificerDisassembleConfirmWindow(){
Menu*artificerDisassembleConfirmWindow=CreateMenu(ARTIFICER_DISASSEMBLE_CONFIRM,CENTERED,vi2d{144,144});
artificerDisassembleConfirmWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
{game->KEY_BACK,{"Stay",[](MenuType type){
Menu::CloseMenu();
}}},
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
}
inline const T&get()const{
return currentVal;
};
const T&first;
const T&second;
private:
T val1,val2;
T currentVal;
float cycleSpd;
double timer{};
};
,{ //Button Navigation Rules
{"Sample Button",{
.up="",
.down="",
.left="",
.right="",}},
});
}

@ -38,112 +38,15 @@ All rights reserved.
#include "Menu.h"
#include "AdventuresInLestoria.h"
#include "RowInventoryScrollableWindowComponent.h"
#include "InventoryCreator.h"
#include "MenuItemItemButton.h"
#include "RowItemDisplay.h"
#include "MenuDecal.h"
#include "SoundEffect.h"
INCLUDE_game
void Menu::InitializeArtificerDisassembleWindow(){
Menu*artificerDisassembleWindow=CreateMenu(ARTIFICER_DISASSEMBLE,CENTERED,game->GetScreenSize()-vi2d{52,52});
Menu*artificerDisassembleWindow=CreateMenu(ARTIFICER_DISASSEMBLE,CENTERED,vi2d{144,144});
const auto ResetDisassemblyDisplay=[artificerDisassembleWindow](){
MenuType menuType{artificerDisassembleWindow->GetType()};
Component<MenuDecal>(menuType,"Down Arrow")->Disable();
Component<MenuIconButton>(menuType,"Disassembly Result")->Disable();
Component<MenuLabel>(menuType,"Disassembly Result Title")->Disable();
Component<MenuLabel>(menuType,"Fragment Total Count")->Disable();
Component<MenuComponent>(menuType,"Disassemble Button")->Disable();
};
const auto EnableDisassemblyDisplay=[artificerDisassembleWindow](){
MenuType menuType{artificerDisassembleWindow->GetType()};
Component<MenuDecal>(menuType,"Down Arrow")->Enable();
Component<MenuIconButton>(menuType,"Disassembly Result")->Enable();
Component<MenuLabel>(menuType,"Disassembly Result Title")->Enable();
Component<MenuLabel>(menuType,"Fragment Total Count")->Enable();
Component<MenuComponent>(menuType,"Disassemble Button")->Enable();
};
auto disassemblyTitleLabel{artificerDisassembleWindow->ADD("Disassembly Title Label",MenuLabel)(geom2d::rect<float>{{},{artificerDisassembleWindow->size.x,24.f}},"Accessory Disassembly",2.f,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END};
auto inventoryLabel=artificerDisassembleWindow->ADD("Accessory List Label",MenuLabel)(geom2d::rect<float>{{0.f,28.f},{180.f,12.f}},"Choose Accessory:",1.f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END;
auto itemIcon{artificerDisassembleWindow->ADD("Item Icon",MenuItemItemButton)(geom2d::rect<float>({artificerDisassembleWindow->size.x/2+4.f,44.f},{48,48}),Item::BLANK,DO_NOTHING,"","Item Description",IconButtonAttr::NOT_SELECTABLE)END};
itemIcon->SetIconScale({2.f,2.f});
itemIcon->SetCompactDescriptions(true);
auto accessoryDescription{artificerDisassembleWindow->ADD("Item Description",MenuLabel)(geom2d::rect<float>{{artificerDisassembleWindow->size.x/2+56.f,44.f},{artificerDisassembleWindow->size.x/2-56.f,72.f}},"",0.5f,ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE|ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END};
auto downArrow{artificerDisassembleWindow->ADD("Down Arrow",MenuDecal)(geom2d::rect<float>{{artificerDisassembleWindow->size.x/2+4.f,92.f},{48,48}},GFX.at("downarrow.png"),DO_NOTHING,DO_NOTHING)END};
auto disassemblyResult{artificerDisassembleWindow->ADD("Disassembly Result",MenuIconButton)(geom2d::rect<float>{{artificerDisassembleWindow->size.x/2+4.f,141.f},{48,48}},nullptr,DO_NOTHING,IconButtonAttr::NOT_SELECTABLE)END};
auto disassemblyResultTitle{artificerDisassembleWindow->ADD("Disassembly Result Title",MenuLabel)(geom2d::rect<float>{{artificerDisassembleWindow->size.x/2+56.f,141.f},{artificerDisassembleWindow->size.x/2-56.f,24.f}},"",2.f,ComponentAttr::BACKGROUND|ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::FIT_TO_LABEL)END};
auto disassemblyTotalCount{artificerDisassembleWindow->ADD("Fragment Total Count",MenuLabel)(geom2d::rect<float>{{artificerDisassembleWindow->size.x/2+56.f,127.f},{artificerDisassembleWindow->size.x/2-56.f,12.f}},"",0.85f,ComponentAttr::RIGHT_ALIGN|ComponentAttr::SHADOW|ComponentAttr::FIT_TO_LABEL)END};
auto disassembleButton{artificerDisassembleWindow->ADD("Disassemble Button",MenuComponent)(geom2d::rect<float>{{artificerDisassembleWindow->size.x/2+72.f,171.f},{artificerDisassembleWindow->size.x/2-72.f,16.f}},"Disassemble",[ResetDisassemblyDisplay](MenuFuncData data){
auto it{Component<MenuItemItemButton>(data.menu.type,"Item Icon")->GetItem()};
if(Inventory::Disassemble(it)==Inventory::DisassembleResult::SUCCESS){
SoundEffect::PlaySFX("Disassemble Item",SoundEffect::CENTERED);
Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(Item::BLANK);
Component<MenuIconButton>(data.menu.type,"Disassembly Result")->SetIcon(nullptr);
Component<MenuLabel>(data.menu.type,"Disassembly Result Title")->SetLabel("");
Component<MenuLabel>(data.menu.type,"Fragment Total Count")->SetLabel("");
ResetDisassemblyDisplay();
}else{
SoundEffect::PlaySFX("Locked Item",SoundEffect::CENTERED);
game->AddNotification(AiL::Notification{"Cannot disassemble locked accessory!",5.f,RED});
}
return true;
})END};
auto inventoryDisplay=artificerDisassembleWindow->ADD("Accessory List",RowInventoryScrollableWindowComponent)(geom2d::rect<float>{{0.f,44.f},{artificerDisassembleWindow->size.x/2-4.f,artificerDisassembleWindow->size.y-60}},"","",[](MenuFuncData data){
RowItemDisplay&item{*DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component)};
DYNAMIC_POINTER_CAST<RowInventoryScrollableWindowComponent>(data.parentComponent.lock())->SelectChild(DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component));
return true;
},[EnableDisassemblyDisplay](MenuFuncData data){
EnableDisassemblyDisplay();
RowItemDisplay&item{*DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component)};
Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(item.GetItem().lock());
Component<MenuIconButton>(data.menu.type,"Disassembly Result")->SetIcon(item.GetItem().lock()->Icon().Decal());
Component<MenuLabel>(data.menu.type,"Disassembly Result Title")->SetLabel(item.GetItem().lock()->FragmentName());
Component<MenuLabel>(data.menu.type,"Fragment Total Count")->SetLabel(std::format("Currently Owned: {}",Inventory::GetItemCount(item.GetItem().lock()->FragmentName())));
return true;
},[ResetDisassemblyDisplay,EnableDisassemblyDisplay](MenuFuncData data){
ResetDisassemblyDisplay();
auto childComponent{DYNAMIC_POINTER_CAST<RowInventoryScrollableWindowComponent>(data.parentComponent.lock())->GetSelectedChild()};
if(childComponent){
RowItemDisplay&item{childComponent.value().get()};
Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(item.GetItem().lock());
Component<MenuIconButton>(data.menu.type,"Disassembly Result")->SetIcon(item.GetItem().lock()->Icon().Decal());
Component<MenuLabel>(data.menu.type,"Disassembly Result Title")->SetLabel(item.GetItem().lock()->FragmentName());
Component<MenuLabel>(data.menu.type,"Fragment Total Count")->SetLabel(std::format("Currently Owned: {}",Inventory::GetItemCount(item.GetItem().lock()->FragmentName())));
EnableDisassemblyDisplay();
}
return true;
},
InventoryCreator::RowPlayer_InventoryUpdate,
InventoryWindowOptions{.padding=1,.size={artificerDisassembleWindow->size.x/2-5.f-12.f,28}})END;
auto backButton=artificerDisassembleWindow->ADD("Back",MenuComponent)(geom2d::rect<float>{{0.f,artificerDisassembleWindow->size.y-12.f},{96.f,16.f}},"Back",[](MenuFuncData data){
Menu::CloseMenu();
return true;
})END;
Menu::AddInventoryListener(inventoryDisplay,"Accessories");
ResetDisassemblyDisplay();
artificerDisassembleWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
auto&items{Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents()};
if(Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild())returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild().value().get().GetName();
else if(items.size()>0)returnData=items[0];
else returnData="Back";
returnData="";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
@ -151,49 +54,12 @@ void Menu::InitializeArtificerDisassembleWindow(){
Menu::CloseMenu();
}}},
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
{{game->KEY_SHOULDER2,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
{{game->KEY_SHOULDER,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
{{game->KEY_FASTSCROLLUP,PressedDAS},{"Scroll",[](MenuType type){
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->DecreaseSelectionIndex(3.f);
}}},
{{game->KEY_FASTSCROLLDOWN,PressedDAS},{"Scroll",[](MenuType type){
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->IncreaseSelectionIndex(3.f);
}}},
}
,{ //Button Navigation Rules
{"Accessory List",{
.up=[&](MenuType type,Data&returnData){
Menu::ScrollUp(type,"Accessory List",returnData,"Back");
},
.down=[&](MenuType type,Data&returnData){
Menu::ScrollDown(type,"Accessory List",returnData,"Back");
},
.left="Back",
.right=[&](MenuType type,Data&returnData){
Menu::menus[type]->GetSelection().lock()->Click();
returnData="Disassemble Button";
},}},
{"Back",{
.up=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().back().lock()->GetName();
},
.down=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().front().lock()->GetName();
},
.left="Disassemble Button",
.right="Disassemble Button",}},
{"Disassemble Button",{
.up=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().back().lock()->GetName();
},
.down=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().front().lock()->GetName();
},
.left=[&](MenuType type,Data&returnData){
if(Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild())returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild().value().get().GetName();
else returnData="Back";
},
.right="Back",}},
}
);
{"Sample Button",{
.up="",
.down="",
.left="",
.right="",}},
});
}

@ -38,81 +38,28 @@ All rights reserved.
#include "Menu.h"
#include "AdventuresInLestoria.h"
#include "MenuIconButton.h"
#include "MenuItemLabel.h"
#include "SoundEffect.h"
INCLUDE_game
void Menu::InitializeArtificerEnchantConfirmWindow(){
Menu*artificerEnchantConfirmWindow=CreateMenu(ARTIFICER_ENCHANT_CONFIRM,CENTERED,vi2d{240,144});
auto enchantSuccessLabel{artificerEnchantConfirmWindow->ADD("Enchant Success Label",MenuLabel)(geom2d::rect<float>{{0.f,-14.f},vf2d{artificerEnchantConfirmWindow->size.x,12.f}},"Enchantment Success!",1.f,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END};
auto chooseResultLabel{artificerEnchantConfirmWindow->ADD("Choose Result Label",MenuLabel)(geom2d::rect<float>{vf2d{0.f,0.f},vf2d{artificerEnchantConfirmWindow->size.x,12.f}},"Choose a Result",1.f,ComponentAttr::SHADOW)END};
auto backgroundOld{artificerEnchantConfirmWindow->ADD("Old Background",MenuLabel)(geom2d::rect<float>{{-2.f,12.f},{artificerEnchantConfirmWindow->size.x/2.f-4.f,124.f}},"",1.f,ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)END};
auto backgroundNew{artificerEnchantConfirmWindow->ADD("New Background",MenuLabel)(geom2d::rect<float>{{artificerEnchantConfirmWindow->size.x/2.f-2.f+8.f,12.f},{artificerEnchantConfirmWindow->size.x/2.f-4.f,124.f}},"",1.f,ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)END};
const float oldLabelTextWidth{game->GetTextSize("OLD").x*2.f};
auto oldLabel{artificerEnchantConfirmWindow->ADD("Old Item Label",MenuLabel)(geom2d::rect<float>{{artificerEnchantConfirmWindow->size.x/4.f-oldLabelTextWidth/2-4.f,14.f},vf2d{oldLabelTextWidth+8.f,24.f}},"OLD",2.f,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND)END};
const float newLabelTextWidth{game->GetTextSize("NEW").x*2.f};
auto newLabel{artificerEnchantConfirmWindow->ADD("New Item Label",MenuLabel)(geom2d::rect<float>{{artificerEnchantConfirmWindow->size.x/2.f+artificerEnchantConfirmWindow->size.x/4.f-oldLabelTextWidth/2+2.f,14.f},vf2d{oldLabelTextWidth+8.f,24.f}},"NEW",2.f,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND)END};
auto oldIcon{artificerEnchantConfirmWindow->ADD("Old Item Icon",MenuIconButton)(geom2d::rect<float>{{artificerEnchantConfirmWindow->size.x/4.f-16.f,42.f},{24,24}},nullptr,DO_NOTHING,IconButtonAttr::NOT_SELECTABLE|IconButtonAttr::NO_OUTLINE)END};
auto newIcon{artificerEnchantConfirmWindow->ADD("New Item Icon",MenuIconButton)(geom2d::rect<float>{{artificerEnchantConfirmWindow->size.x/2.f+artificerEnchantConfirmWindow->size.x/4.f-16.f+4.f,42.f},{24,24}},nullptr,DO_NOTHING,IconButtonAttr::NOT_SELECTABLE|IconButtonAttr::NO_OUTLINE)END};
auto oldItemDescription{artificerEnchantConfirmWindow->ADD("Old Item Description",MenuItemLabel)(geom2d::rect<float>{{0.f,74.f},{artificerEnchantConfirmWindow->size.x/2.f-8.f,60.f}},Item::BLANK,ItemLabelDescriptionType::ITEM_DESCRIPTION,0.5f,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE|ComponentAttr::LEFT_ALIGN)END};
auto newItemDescription{artificerEnchantConfirmWindow->ADD("New Item Description",MenuItemLabel)(geom2d::rect<float>{{artificerEnchantConfirmWindow->size.x/2.f+8.f,74.f},{artificerEnchantConfirmWindow->size.x/2.f-8.f,60.f}},Item::BLANK,ItemLabelDescriptionType::ITEM_DESCRIPTION,0.5f,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE|ComponentAttr::LEFT_ALIGN)END};
const float takeOldTextWidth{float(game->GetTextSize("Take Old").x)*2.f};
auto takeOldButton{artificerEnchantConfirmWindow->ADD("Take Old Button",MenuComponent)(geom2d::rect<float>{{artificerEnchantConfirmWindow->size.x/4.f-takeOldTextWidth/2.f,138.f},{takeOldTextWidth-8.f,20.f}},"Take Old",[](MenuFuncData data){
onClick:
std::any&oldEnchant{Menu::menus[ARTIFICER_ENCHANT]->ANY(A::ENCHANT)};
std::weak_ptr<Item>newItem{std::get<std::weak_ptr<Item>>(Component<MenuItemLabel>(data.menu.GetType(),"New Item Description")->GetItem())}; //NOTE: We're making an assumption here that the new item description holds a weak pointer. This should be true because the only way to get here was to set it up through clicking the Enchant button in Artificer Enchant Window.
if(oldEnchant.has_value())newItem.lock()->_EnchantItem(std::any_cast<ItemEnchant&>(oldEnchant));
else newItem.lock()->RemoveEnchant();
Component<MenuComponent>(ARTIFICER_ENCHANT,"Fragment Enchant Button")->SetGrayedOut(!newItem.lock()->CanBeEnchanted());
const std::string_view fragmentName{newItem.lock()->FragmentName()};
const Pixel fragmentItemDisplayCol{Inventory::GetItemCount(fragmentName)>="Fragment Enchant Cost"_i[0]?WHITE:RED};
const Pixel moneyCostDisplayCol{game->GetPlayer()->GetMoney()>="Fragment Enchant Cost"_i[1]?WHITE:RED};
Component<MenuLabel>(ARTIFICER_ENCHANT,"Fragment Label")->SetLabel(std::format("{}{} x{} ({})",fragmentItemDisplayCol.toHTMLColorCode(),fragmentName,"Fragment Enchant Cost"_i[0],Inventory::GetItemCount(fragmentName)));
Component<MenuLabel>(ARTIFICER_ENCHANT,"Fragment Money Cost Label")->SetLabel(std::format("{}{} gold",moneyCostDisplayCol.toHTMLColorCode(),"Fragment Enchant Cost"_i[1]));
SoundEffect::PlaySFX("Take Enchant",SoundEffect::CENTERED);
Menu::CloseMenu();
return true;
},vf2d{2.f,2.f})END};
const float takeNewTextWidth{float(game->GetTextSize("Take New").x)*2.f};
auto takeNewButton{artificerEnchantConfirmWindow->ADD("Take New Button",MenuComponent)(geom2d::rect<float>{vf2d{artificerEnchantConfirmWindow->size.x/2.f+8.f+artificerEnchantConfirmWindow->size.x/4.f-takeNewTextWidth/2.f,138.f},{takeNewTextWidth-8.f,20.f}},"Take New",[](MenuFuncData data){
onClick:
std::weak_ptr<Item>newItem{std::get<std::weak_ptr<Item>>(Component<MenuItemLabel>(data.menu.GetType(),"New Item Description")->GetItem())};
Component<MenuComponent>(ARTIFICER_ENCHANT,"Fragment Enchant Button")->SetGrayedOut(!newItem.lock()->CanBeEnchanted());
const std::string_view fragmentName{newItem.lock()->FragmentName()};
const Pixel fragmentItemDisplayCol{Inventory::GetItemCount(fragmentName)>="Fragment Enchant Cost"_i[0]?WHITE:RED};
const Pixel moneyCostDisplayCol{game->GetPlayer()->GetMoney()>="Fragment Enchant Cost"_i[1]?WHITE:RED};
Component<MenuLabel>(ARTIFICER_ENCHANT,"Fragment Label")->SetLabel(std::format("{}{} x{} ({})",fragmentItemDisplayCol.toHTMLColorCode(),fragmentName,"Fragment Enchant Cost"_i[0],Inventory::GetItemCount(fragmentName)));
Component<MenuLabel>(ARTIFICER_ENCHANT,"Fragment Money Cost Label")->SetLabel(std::format("{}{} gold",moneyCostDisplayCol.toHTMLColorCode(),"Fragment Enchant Cost"_i[1]));
SoundEffect::PlaySFX("Take Enchant",SoundEffect::CENTERED);
Menu::CloseMenu();
return true;
},vf2d{2.f,2.f})END};
Menu*artificerEnchantConfirmWindow=CreateMenu(ARTIFICER_ENCHANT_CONFIRM,CENTERED,vi2d{144,144});
artificerEnchantConfirmWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="Take New Button";
returnData="";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
{game->KEY_BACK,{"Stay",[](MenuType type){
Menu::CloseMenu();
}}},
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
}
,{ //Button Navigation Rules
{"Take Old Button",{
.left="Take New Button",
.right="Take New Button",}},
{"Take New Button",{
.left="Take Old Button",
.right="Take Old Button",}},
{"Sample Button",{
.up="",
.down="",
.left="",
.right="",}},
});
}

@ -38,144 +38,15 @@ All rights reserved.
#include "Menu.h"
#include "AdventuresInLestoria.h"
#include "RowInventoryScrollableWindowComponent.h"
#include "MenuItemItemButton.h"
#include "MenuItemLabel.h"
#include "MenuDecal.h"
#include "PlayerMoneyLabel.h"
#include "SoundEffect.h"
INCLUDE_game
void Menu::InitializeArtificerEnchantWindow(){
Menu*artificerEnchantWindow=CreateMenu(ARTIFICER_ENCHANT,CENTERED,game->GetScreenSize()-vi2d{52,52});
auto enchantingTitleLabel{artificerEnchantWindow->ADD("Enchanting Title Label",MenuLabel)(geom2d::rect<float>{{0.f,-16.f},{artificerEnchantWindow->size.x,24.f}},"Accessory Enchanting",2.f,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END};
auto inventoryLabel{artificerEnchantWindow->ADD("Accessory List Label",MenuLabel)(geom2d::rect<float>{{0.f,12.f},{180.f,12.f}},"Choose Accessory:",1.f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END};
auto itemIcon{artificerEnchantWindow->ADD("Item Icon",MenuItemItemButton)(geom2d::rect<float>({artificerEnchantWindow->size.x/2+4.f,16.f},{48,48}),Item::BLANK,DO_NOTHING,"","Item Description",IconButtonAttr::NOT_SELECTABLE)END};
itemIcon->SetIconScale({2.f,2.f});
itemIcon->SetCompactDescriptions(true);
auto accessoryDescription{artificerEnchantWindow->ADD("Item Description",MenuLabel)(geom2d::rect<float>{{artificerEnchantWindow->size.x/2+56.f,16.f},{artificerEnchantWindow->size.x/2-40.f,72.f}},"",0.5f,ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE|ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END};
Menu*artificerEnchantWindow=CreateMenu(ARTIFICER_ENCHANT,CENTERED,vi2d{144,144});
const auto ResetEnchantDisplay{[artificerEnchantWindow](){
MenuType menuType{artificerEnchantWindow->GetType()};
Component<MenuItemItemButton>(menuType,"Item Icon")->SetItem(Item::BLANK);
Component<ScrollableWindowComponent>(menuType,"Enchant Container")->Disable();
Component<MenuLabel>(menuType,"Enchant List Header")->Disable();
Component<MenuLabel>(menuType,"Enchant Cost Label")->Disable();
Component<MenuDecal>(menuType,"Fragment Cost Icon")->Disable();
Component<MenuLabel>(menuType,"Fragment Label")->Disable();
Component<MenuLabel>(menuType,"Fragment Money Cost Label")->Disable();
Component<MenuComponent>(menuType,"Fragment Enchant Button")->Disable();
}};
const auto EnableEnchantDisplay{[artificerEnchantWindow](){
MenuType menuType{artificerEnchantWindow->GetType()};
const std::weak_ptr<Item>&selectedItem{Component<MenuItemItemButton>(menuType,"Item Icon")->GetItem()};
std::vector<ItemEnchantInfo>availableEnchants{ItemEnchant::GetAvailableEnchants()};
std::sort(availableEnchants.begin(),availableEnchants.end(),[](const ItemEnchantInfo&enchant1,const ItemEnchantInfo&enchant2){
return enchant1.Category()!=enchant2.Category()?int(enchant1.Category())<int(enchant2.Category()):enchant1.Name()<enchant2.Name();
});
std::string enchantList{std::accumulate(availableEnchants.begin(),availableEnchants.end(),""s,[](const std::string&acc,const ItemEnchantInfo&enchant){
return std::format("{}{}{}#FFFFFF\n",acc,enchant.DisplayCol().toHTMLColorCode(),enchant.Name());
})};
Component<ScrollableWindowComponent>(menuType,"Enchant Container")->Enable();
Component<MenuLabel>(menuType,"Enchant List")->SetLabel(enchantList);
Component<MenuLabel>(menuType,"Enchant List Header")->Enable();
Component<MenuLabel>(menuType,"Enchant Cost Label")->Enable();
Component<MenuDecal>(menuType,"Fragment Cost Icon")->Enable();
Component<MenuDecal>(menuType,"Fragment Cost Icon")->SetImage(GFX.at(selectedItem.lock()->FragmentIcon().value()));
Component<MenuLabel>(menuType,"Fragment Label")->Enable();
Component<MenuLabel>(menuType,"Fragment Money Cost Label")->Enable();
Component<MenuComponent>(menuType,"Fragment Enchant Button")->Enable();
Component<MenuComponent>(menuType,"Fragment Enchant Button")->SetGrayedOut(!selectedItem.lock()->CanBeEnchanted());
const std::string_view fragmentName{selectedItem.lock()->FragmentName()};
const Pixel fragmentItemDisplayCol{Inventory::GetItemCount(fragmentName)>="Fragment Enchant Cost"_i[0]?WHITE:RED};
const Pixel moneyCostDisplayCol{game->GetPlayer()->GetMoney()>="Fragment Enchant Cost"_i[1]?WHITE:RED};
Component<MenuLabel>(menuType,"Fragment Label")->SetLabel(std::format("{}{} x{} ({})",fragmentItemDisplayCol.toHTMLColorCode(),fragmentName,"Fragment Enchant Cost"_i[0],Inventory::GetItemCount(fragmentName)));
Component<MenuLabel>(menuType,"Fragment Money Cost Label")->SetLabel(std::format("{}{} gold",moneyCostDisplayCol.toHTMLColorCode(),"Fragment Enchant Cost"_i[1]));
}};
auto inventoryDisplay{artificerEnchantWindow->ADD("Accessory List",RowInventoryScrollableWindowComponent)(geom2d::rect<float>{{0.f,28.f},{artificerEnchantWindow->size.x/2-4.f,artificerEnchantWindow->size.y-44}},"","",[](MenuFuncData data){
OnClick:
RowItemDisplay&item{*DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component)};
DYNAMIC_POINTER_CAST<RowInventoryScrollableWindowComponent>(data.parentComponent.lock())->SelectChild(DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component));
return true;
},[EnableEnchantDisplay](MenuFuncData data){OnHover:
RowItemDisplay&item{*DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component)};
Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(item.GetItem());
EnableEnchantDisplay();
return true;
},[ResetEnchantDisplay,EnableEnchantDisplay](MenuFuncData data){OnMouseOut:
ResetEnchantDisplay();
auto childComponent{DYNAMIC_POINTER_CAST<RowInventoryScrollableWindowComponent>(data.parentComponent.lock())->GetSelectedChild()};
if(childComponent){
RowItemDisplay&item{childComponent.value().get()};
Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(item.GetItem());
EnableEnchantDisplay();
}
return true;
},
InventoryCreator::RowPlayer_InventoryUpdate,
InventoryWindowOptions{.padding=1,.size={artificerEnchantWindow->size.x/2-5.f-12.f,28}})END};
auto enchantListHeaderLabel{artificerEnchantWindow->ADD("Enchant List Header",MenuLabel)(geom2d::rect<float>{{artificerEnchantWindow->size.x/2+4.f,92.f},{artificerEnchantWindow->size.x/2+12.f,12.f}},"Possible Enchantments:",1.f,ComponentAttr::FIT_TO_LABEL|ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE|ComponentAttr::SHADOW)END};
auto enchantContainer{artificerEnchantWindow->ADD("Enchant Container",ScrollableWindowComponent)(geom2d::rect<float>{{artificerEnchantWindow->size.x/2+4.f,104.f},{artificerEnchantWindow->size.x/2+12.f,44.f}})END};
auto enchantList{enchantContainer->ADD("Enchant List",MenuLabel)(geom2d::rect<float>{{0.f,2.f},{enchantContainer->GetSize().x-12.f,0.f}},"",1.f,ComponentAttr::CENTER|ComponentAttr::SHADOW)END};
auto enchantCostLabel{artificerEnchantWindow->ADD("Enchant Cost Label",MenuLabel)(geom2d::rect<float>{{artificerEnchantWindow->size.x/2+4.f,152.f},{64.f,20.f}},"Enchant Cost:",1.f,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)END};
auto fragmentCostIcon{artificerEnchantWindow->ADD("Fragment Cost Icon",MenuDecal)(geom2d::rect<float>{{artificerEnchantWindow->size.x/2+68.f,152.f},{12.f,12.f}})END};
auto fragmentDisplayLabel{artificerEnchantWindow->ADD("Fragment Label",MenuLabel)(geom2d::rect<float>{{artificerEnchantWindow->size.x/2+80.f,152.f},{artificerEnchantWindow->size.x/2-60.f,12.f}},"",1.f,ComponentAttr::SHADOW|ComponentAttr::FIT_TO_LABEL|ComponentAttr::LEFT_ALIGN)END};
auto fragmentMoneyCostLabel{artificerEnchantWindow->ADD("Fragment Money Cost Label",MenuLabel)(geom2d::rect<float>{{artificerEnchantWindow->size.x/2+80.f,164.f},{artificerEnchantWindow->size.x/2-60.f,12.f}},"",1.f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END};
auto fragmentEnchantButton{artificerEnchantWindow->ADD("Fragment Enchant Button",MenuComponent)(geom2d::rect<float>{{artificerEnchantWindow->size.x/2+96.f,180.f},{artificerEnchantWindow->size.x/2-80.f,12.f}},"Enchant",[EnableEnchantDisplay](MenuFuncData data){
onClick:
EnableEnchantDisplay(); //Refresh the current display contents.
SoundEffect::PlaySFX("Enchant Item",SoundEffect::CENTERED);
std::weak_ptr<Item>item{Component<MenuItemItemButton>(data.menu.type,"Item Icon")->GetItem()};
std::optional<ItemEnchant>previousEnchant{item.lock()->GetEnchant()};
data.menu.ANY(A::ENCHANT)={};
if(previousEnchant.has_value())data.menu.ANY(A::ENCHANT)=previousEnchant.value();
Component<MenuIconButton>(ARTIFICER_ENCHANT_CONFIRM,"Old Item Icon")->SetIcon(item.lock()->Icon().Decal());
Component<MenuItemLabel>(ARTIFICER_ENCHANT_CONFIRM,"Old Item Description")->SetItem(*item.lock());
item.lock()->ApplyRandomEnchant();
const ItemEnchant&newEnchant{item.lock()->GetEnchant().value()};
Component<MenuIconButton>(ARTIFICER_ENCHANT_CONFIRM,"New Item Icon")->SetIcon(item.lock()->Icon().Decal());
Component<MenuItemLabel>(ARTIFICER_ENCHANT_CONFIRM,"New Item Description")->SetItem(item.lock());
Menu::OpenMenu(ARTIFICER_ENCHANT_CONFIRM,true);
return true;
})END};
#pragma region Money Display
vf2d moneyIconPos{artificerEnchantWindow->size.x/2-28.f,artificerEnchantWindow->size.y-12.f};
auto moneyIcon=artificerEnchantWindow->ADD("Money Icon",MenuIconButton)(geom2d::rect<float>{moneyIconPos,{24,24}},GFX["money.png"].Decal(),DO_NOTHING,IconButtonAttr::NOT_SELECTABLE|IconButtonAttr::NO_OUTLINE|IconButtonAttr::NO_BACKGROUND)END;
std::string moneyText=std::to_string(game->GetPlayer()->GetMoney());
vf2d moneyTextSize=game->GetTextSizeProp(moneyText)*2;
auto moneyDisplay=artificerEnchantWindow->ADD("Money Label",PlayerMoneyLabel)(geom2d::rect<float>{moneyIconPos+vf2d{26.f,4.f},moneyTextSize},2,1.85f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN|ComponentAttr::FIT_TO_LABEL)END;
Player::AddMoneyListener(moneyDisplay);
#pragma endregion
auto backButton{artificerEnchantWindow->ADD("Back",MenuComponent)(geom2d::rect<float>{{0.f,artificerEnchantWindow->size.y-12.f},{96.f,16.f}},"Back",[](MenuFuncData data){
Menu::CloseMenu();
return true;
})END};
ResetEnchantDisplay();
Menu::AddInventoryListener(inventoryDisplay,"Accessories");
artificerEnchantWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
auto&items{Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents()};
if(Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild())returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild().value().get().GetName();
else if(items.size()>0)returnData=items[0];
else returnData="Back";
returnData="";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
@ -183,52 +54,12 @@ void Menu::InitializeArtificerEnchantWindow(){
Menu::CloseMenu();
}}},
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
{{game->KEY_SHOULDER2,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
{{game->KEY_SHOULDER,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
{{game->KEY_FASTSCROLLUP,PressedDAS},{"Scroll",[](MenuType type){
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->DecreaseSelectionIndex(3.f);
}}},
{{game->KEY_FASTSCROLLDOWN,PressedDAS},{"Scroll",[](MenuType type){
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->IncreaseSelectionIndex(3.f);
}}},
{{game->KEY_SCROLLVERT_R,Analog,InputEngageGroup::NOT_VISIBLE},{"Scroll Enchants",[](MenuType type){
Component<ScrollableWindowComponent>(type,"Enchant Container")->Scroll(game->KEY_SCROLLVERT.Analog());
}}},
}
,{ //Button Navigation Rules
{"Accessory List",{
.up=[&](MenuType type,Data&returnData){
Menu::ScrollUp(type,"Accessory List",returnData,"Back");
},
.down=[&](MenuType type,Data&returnData){
Menu::ScrollDown(type,"Accessory List",returnData,"Back");
},
.left="Back",
.right=[&](MenuType type,Data&returnData){
Menu::menus[type]->GetSelection().lock()->Click();
returnData="Fragment Enchant Button";
},}},
{"Back",{
.up=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().back().lock()->GetName();
},
.down=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().front().lock()->GetName();
},
.left="Fragment Enchant Button",
.right="Fragment Enchant Button",}},
{"Fragment Enchant Button",{
.up=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().back().lock()->GetName();
},
.down=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().front().lock()->GetName();
},
.left=[&](MenuType type,Data&returnData){
if(Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild())returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild().value().get().GetName();
else returnData="Back";
},
.right="Back",}},
}
);
{"Sample Button",{
.up="",
.down="",
.left="",
.right="",}},
});
}

@ -35,23 +35,31 @@ Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#pragma once
#include "MenuComponent.h"
#include "Menu.h"
#include "AdventuresInLestoria.h"
class MenuDecal:public MenuComponent{
public:
inline MenuDecal(geom2d::rect<float>rect,std::optional<std::reference_wrapper<Renderable>>image={},MenuFunc onClick=DO_NOTHING,MenuFunc onHover=DO_NOTHING)
:image(image),MenuComponent(rect,"",onClick,ButtonAttr::NONE){
SetHoverFunc(onHover);
}
inline void DrawDecal(ViewPort&window,bool focused)override{
if(image)window.DrawDecal(rect.pos,image.value().get().Decal(),rect.size/image.value().get().Sprite()->Size());
}
inline void SetImage(Renderable&renderable){
image=renderable;
}
INCLUDE_game
private:
std::optional<std::reference_wrapper<Renderable>>image;
};
void Menu::InitializeArtificerRefineConfirmWindow(){
Menu*artificerRefineConfirmWindow=CreateMenu(ARTIFICER_REFINE_CONFIRM,CENTERED,vi2d{144,144});
artificerRefineConfirmWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
{game->KEY_BACK,{"Stay",[](MenuType type){
Menu::CloseMenu();
}}},
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
}
,{ //Button Navigation Rules
{"Sample Button",{
.up="",
.down="",
.left="",
.right="",}},
});
}

@ -1,75 +0,0 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "Menu.h"
#include "AdventuresInLestoria.h"
#include "MenuItemItemButton.h"
#include "DynamicMenuLabel.h"
INCLUDE_game
void Menu::InitializeArtificerRefineResultWindow(){
Menu*artificerRefineResultWindow=CreateMenu(ARTIFICER_REFINE_RESULT,CENTERED,vi2d{144,144});
auto itemIcon{artificerRefineResultWindow->ADD("Item Icon",MenuItemItemButton)(geom2d::rect<float>{{artificerRefineResultWindow->size.x/2.f-24.f,0.f},{48.f,48.f}},Item::BLANK,DO_NOTHING,"","",IconButtonAttr::NOT_SELECTABLE)END};
itemIcon->SetIconScale({2.f,2.f});
auto refineItemTextdisplay{artificerRefineResultWindow->ADD("Refine Item Text Display",MenuLabel)(geom2d::rect<float>{vf2d{0.f,artificerRefineResultWindow->size.y/2.f},vf2d{artificerRefineResultWindow->size.x,12.f}},"",1.f,ComponentAttr::SHADOW)END};
auto refineResultDisplay{artificerRefineResultWindow->ADD("Refine Result",DynamicMenuLabel)(geom2d::rect<float>{vf2d{0.f,artificerRefineResultWindow->size.y/2.f+artificerRefineResultWindow->size.y*0.33f},vf2d{artificerRefineResultWindow->size.x,12.f}},[](){return "";},1.f,ComponentAttr::SHADOW)END};
auto continueButton{artificerRefineResultWindow->ADD("Continue Button",MenuComponent)(geom2d::rect<float>{vf2d{artificerRefineResultWindow->size.x/4.f,artificerRefineResultWindow->size.y-6.f},{artificerRefineResultWindow->size.x/2.f,12.f}},"Continue",[](MenuFuncData data){
onClick:
Menu::CloseMenu();
return true;
})END};
artificerRefineResultWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="Continue Button";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
{game->KEY_BACK,{"Stay",[](MenuType type){
Menu::CloseMenu();
}}},
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
}
,{ //Button Navigation Rules
});
}

@ -39,128 +39,31 @@ All rights reserved.
#include "Menu.h"
#include "AdventuresInLestoria.h"
#include "RowInventoryScrollableWindowComponent.h"
#include "MenuItemItemButton.h"
#include "MenuRefineLabel.h"
#include "PlayerMoneyLabel.h"
#include "MenuDecal.h"
#include "SoundEffect.h"
#include "DynamicMenuLabel.h"
#include "MenuLabel.h"
INCLUDE_game
void Menu::InitializeArtificerRefineWindow(){
Menu*const artificerRefineWindow{CreateMenu(ARTIFICER_REFINE,CENTERED,game->GetScreenSize()-vi2d{52,52})};
auto refiningTitleLabel{artificerRefineWindow->ADD("Refining Title Label",MenuLabel)(geom2d::rect<float>{{0.f,-16.f},{artificerRefineWindow->size.x,24.f}},"Accessory Refinement",2.f,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END};
auto inventoryLabel{artificerRefineWindow->ADD("Accessory List Label",MenuLabel)(geom2d::rect<float>{{0.f,12.f},{180.f,12.f}},"Choose Accessory:",1.f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END};
auto itemIcon{artificerRefineWindow->ADD("Item Icon",MenuItemItemButton)(geom2d::rect<float>({artificerRefineWindow->size.x/2+4.f,28.f},{48,48}),Item::BLANK,DO_NOTHING,"","Item Description",IconButtonAttr::NOT_SELECTABLE)END};
itemIcon->SetIconScale({2.f,2.f});
itemIcon->SetCompactDescriptions(true);
auto accessoryDescription{artificerRefineWindow->ADD("Item Description",MenuLabel)(geom2d::rect<float>{{artificerRefineWindow->size.x/2+56.f,28.f},{artificerRefineWindow->size.x/2-40.f,72.f}},"",0.5f,ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE|ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END};
Menu*artificerRefineWindow=CreateMenu(ARTIFICER_REFINE,CENTERED,game->GetScreenSize()-vi2d{52,52});
const auto ResetRefineDisplay{[artificerRefineWindow](){
MenuType menuType{artificerRefineWindow->GetType()};
Component<MenuItemItemButton>(menuType,"Item Icon")->SetItem(Item::BLANK);
Component<MenuRefineLabel>(menuType,"Stats Block")->Disable();
Component<MenuLabel>(menuType,"Refine Cost Label")->Disable();
Component<MenuDecal>(menuType,"Fragment Cost Icon")->Disable();
Component<MenuLabel>(menuType,"Fragment Label")->Disable();
Component<MenuLabel>(menuType,"Fragment Money Cost Label")->Disable();
Component<MenuComponent>(menuType,"Fragment Refine Button")->Disable();
}};
const auto EnableRefineDisplay{[artificerRefineWindow](){
MenuType menuType{artificerRefineWindow->GetType()};
const std::weak_ptr<Item>&selectedItem{Component<MenuItemItemButton>(menuType,"Item Icon")->GetItem()};
Component<MenuRefineLabel>(menuType,"Stats Block")->SetItem(selectedItem);
Component<MenuRefineLabel>(menuType,"Stats Block")->Enable();
Component<MenuLabel>(menuType,"Refine Cost Label")->Enable();
Component<MenuDecal>(menuType,"Fragment Cost Icon")->Enable();
Component<MenuDecal>(menuType,"Fragment Cost Icon")->SetImage(GFX.at(selectedItem.lock()->FragmentIcon().value()));
Component<MenuLabel>(menuType,"Fragment Label")->Enable();
Component<MenuLabel>(menuType,"Fragment Money Cost Label")->Enable();
Component<MenuComponent>(menuType,"Fragment Refine Button")->Enable();
Component<MenuComponent>(menuType,"Fragment Refine Button")->SetGrayedOut(!selectedItem.lock()->CanBeRefined());
const std::string_view fragmentName{selectedItem.lock()->FragmentName()};
const Pixel fragmentItemDisplayCol{Inventory::GetItemCount(fragmentName)>="Fragment Refine Cost"_i[0]?WHITE:RED};
const Pixel moneyCostDisplayCol{game->GetPlayer()->GetMoney()>="Fragment Refine Cost"_i[1]?WHITE:RED};
Component<MenuLabel>(menuType,"Fragment Label")->SetLabel(std::format("{}{} x{} ({})",fragmentItemDisplayCol.toHTMLColorCode(),fragmentName,"Fragment Refine Cost"_i[0],Inventory::GetItemCount(fragmentName)));
Component<MenuLabel>(menuType,"Fragment Money Cost Label")->SetLabel(std::format("{}{} gold",moneyCostDisplayCol.toHTMLColorCode(),"Fragment Refine Cost"_i[1]));
}};
auto inventoryLabel=artificerRefineWindow->ADD("Accessory List Label",MenuLabel)(geom2d::rect<float>{{},{180.f,12.f}},"Choose Accessory:",1.f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END;
auto inventoryDisplay{artificerRefineWindow->ADD("Accessory List",RowInventoryScrollableWindowComponent)(geom2d::rect<float>{{0.f,28.f},{artificerRefineWindow->size.x/2-4.f,artificerRefineWindow->size.y-44}},"","",[](MenuFuncData data){
OnClick:
RowItemDisplay&item{*DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component)};
DYNAMIC_POINTER_CAST<RowInventoryScrollableWindowComponent>(data.parentComponent.lock())->SelectChild(DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component));
auto inventoryDisplay=artificerRefineWindow->ADD("Accessory List",RowInventoryScrollableWindowComponent)(geom2d::rect<float>{{0.f,16.f},{artificerRefineWindow->size.x/2-4.f,artificerRefineWindow->size.y-32}},"","",[](MenuFuncData data){
return true;
},[EnableRefineDisplay](MenuFuncData data){OnHover:
RowItemDisplay&item{*DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component)};
Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(item.GetItem());
EnableRefineDisplay();
return true;
},[ResetRefineDisplay,EnableRefineDisplay](MenuFuncData data){OnMouseOut:
ResetRefineDisplay();
auto childComponent{DYNAMIC_POINTER_CAST<RowInventoryScrollableWindowComponent>(data.parentComponent.lock())->GetSelectedChild()};
if(childComponent){
RowItemDisplay&item{childComponent.value().get()};
Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(item.GetItem());
EnableRefineDisplay();
}
return true;
},
},DO_NOTHING,DO_NOTHING,
InventoryCreator::RowPlayer_InventoryUpdate,
InventoryWindowOptions{.padding=1,.size={artificerRefineWindow->size.x/2-5.f-12.f,28}})END};
auto statsBlock{artificerRefineWindow->ADD("Stats Block",MenuRefineLabel)(geom2d::rect<float>{{artificerRefineWindow->size.x/2+4.f,104.f},{artificerRefineWindow->size.x/2+12.f,44.f}},Item::BLANK,1.f,ComponentAttr::BACKGROUND|ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::FIXED_WIDTH_FONT|ComponentAttr::FIT_TO_LABEL|ComponentAttr::LEFT_ALIGN)END};
auto refineCostLabel{artificerRefineWindow->ADD("Refine Cost Label",MenuLabel)(geom2d::rect<float>{{artificerRefineWindow->size.x/2+4.f,152.f},{64.f,20.f}},"Refine Cost:",1.f,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)END};
InventoryWindowOptions{.padding=1,.size={artificerRefineWindow->size.x/2-5.f,28}})END;
auto fragmentCostIcon{artificerRefineWindow->ADD("Fragment Cost Icon",MenuDecal)(geom2d::rect<float>{{artificerRefineWindow->size.x/2+68.f,152.f},{12.f,12.f}})END};
auto fragmentDisplayLabel{artificerRefineWindow->ADD("Fragment Label",MenuLabel)(geom2d::rect<float>{{artificerRefineWindow->size.x/2+80.f,152.f},{artificerRefineWindow->size.x/2-60.f,12.f}},"",1.f,ComponentAttr::SHADOW|ComponentAttr::FIT_TO_LABEL|ComponentAttr::LEFT_ALIGN)END};
auto fragmentMoneyCostLabel{artificerRefineWindow->ADD("Fragment Money Cost Label",MenuLabel)(geom2d::rect<float>{{artificerRefineWindow->size.x/2+80.f,164.f},{artificerRefineWindow->size.x/2-60.f,12.f}},"",1.f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END};
auto fragmentRefineButton{artificerRefineWindow->ADD("Fragment Refine Button",MenuComponent)(geom2d::rect<float>{{artificerRefineWindow->size.x/2+96.f,180.f},{artificerRefineWindow->size.x/2-80.f,12.f}},"Refine",[EnableRefineDisplay](MenuFuncData data){
onClick:
SoundEffect::PlaySFX("Refine Item",SoundEffect::CENTERED);
std::weak_ptr<Item>item{Component<MenuItemItemButton>(data.menu.type,"Item Icon")->GetItem()};
RefineResult result{item.lock()->Refine()};
Component<MenuItemItemButton>(ARTIFICER_REFINE_RESULT,"Item Icon")->SetItem(item);
Component<DynamicMenuLabel>(ARTIFICER_REFINE_RESULT,"Refine Result")->SetLabelUpdateFunction([result](){
const Pixel shimmeringColor{PixelLerp(WHITE,{220,220,220},sin((70*game->GetRunTime())/2.f+0.5f))};
return std::format("{} -> {}+{}{} #FFFF00UP!",result.first.Name(),shimmeringColor.toHTMLColorCode(),result.second,result.first.DisplayAsPercent()?"%":"");
});
Component<MenuLabel>(ARTIFICER_REFINE_RESULT,"Refine Item Text Display")->SetLabel(std::format("#FFFF00{} has been refined!",item.lock()->DisplayName()));
EnableRefineDisplay(); //Refresh the current display contents.
Menu::OpenMenu(ARTIFICER_REFINE_RESULT,true);
return true;
})END};
#pragma region Money Display
vf2d moneyIconPos{artificerRefineWindow->size.x/2-28.f,artificerRefineWindow->size.y-12.f};
auto moneyIcon=artificerRefineWindow->ADD("Money Icon",MenuIconButton)(geom2d::rect<float>{moneyIconPos,{24,24}},GFX["money.png"].Decal(),DO_NOTHING,IconButtonAttr::NOT_SELECTABLE|IconButtonAttr::NO_OUTLINE|IconButtonAttr::NO_BACKGROUND)END;
std::string moneyText=std::to_string(game->GetPlayer()->GetMoney());
vf2d moneyTextSize=game->GetTextSizeProp(moneyText)*2;
auto moneyDisplay=artificerRefineWindow->ADD("Money Label",PlayerMoneyLabel)(geom2d::rect<float>{moneyIconPos+vf2d{26.f,4.f},moneyTextSize},2,1.85f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN|ComponentAttr::FIT_TO_LABEL)END;
Player::AddMoneyListener(moneyDisplay);
#pragma endregion
auto backButton{artificerRefineWindow->ADD("Back",MenuComponent)(geom2d::rect<float>{{0.f,artificerRefineWindow->size.y-12.f},{96.f,16.f}},"Back",[](MenuFuncData data){
auto backButton=artificerRefineWindow->ADD("Back",MenuComponent)(geom2d::rect<float>{{0.f,artificerRefineWindow->size.y-12.f},{120.f,12.f}},"Back",[](MenuFuncData data){
Menu::CloseMenu();
return true;
})END};
})END;
Menu::AddInventoryListener(inventoryDisplay,"Accessories");
artificerRefineWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
auto&items{Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents()};
if(Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild())returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild().value().get().GetName();
else if(items.size()>0)returnData=items[0];
else returnData="Back";
returnData="";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
@ -168,49 +71,12 @@ void Menu::InitializeArtificerRefineWindow(){
Menu::CloseMenu();
}}},
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
{{game->KEY_SHOULDER2,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
{{game->KEY_SHOULDER,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
{{game->KEY_FASTSCROLLUP,PressedDAS},{"Scroll",[](MenuType type){
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->DecreaseSelectionIndex(3.f);
}}},
{{game->KEY_FASTSCROLLDOWN,PressedDAS},{"Scroll",[](MenuType type){
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->IncreaseSelectionIndex(3.f);
}}},
}
,{ //Button Navigation Rules
{"Accessory List",{
.up=[&](MenuType type,Data&returnData){
Menu::ScrollUp(type,"Accessory List",returnData,"Back");
},
.down=[&](MenuType type,Data&returnData){
Menu::ScrollDown(type,"Accessory List",returnData,"Back");
},
.left="Back",
.right=[&](MenuType type,Data&returnData){
Menu::menus[type]->GetSelection().lock()->Click();
returnData="Fragment Refine Button";
},}},
{"Back",{
.up=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().back().lock()->GetName();
},
.down=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().front().lock()->GetName();
},
.left="Fragment Refine Button",
.right="Fragment Refine Button",}},
{"Fragment Refine Button",{
.up=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().back().lock()->GetName();
},
.down=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().front().lock()->GetName();
},
.left=[&](MenuType type,Data&returnData){
if(Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild())returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild().value().get().GetName();
else returnData="Back";
},
.right="Back",}},
}
);
{"Sample Button",{
.up="",
.down="",
.left="",
.right="",}},
});
}

@ -43,26 +43,22 @@ All rights reserved.
#include "Unlock.h"
#include "MenuLabel.h"
#include "VisualNovel.h"
#include "RowInventoryScrollableWindowComponent.h"
INCLUDE_game
void Menu::InitializeArtificerWindow(){
Menu*artificerWindow=CreateMenu(ARTIFICER,CENTERED,vi2d{144,144});
artificerWindow->ADD("Disassemble Button",MenuComponent)(geom2d::rect<float>{{0.f,4.f},{144.f,24.f}},"Disassemble",[](MenuFuncData data){
Menu::OpenMenu(MenuType::ARTIFICER_DISASSEMBLE);
Component<RowInventoryScrollableWindowComponent>(MenuType::ARTIFICER_DISASSEMBLE,"Accessory List")->ClearSelectedChild();
artificerWindow->ADD("Refine Button",MenuComponent)(geom2d::rect<float>{{0.f,4.f},{144.f,24.f}},"Refine",[](MenuFuncData data){
Menu::OpenMenu(MenuType::ARTIFICER_REFINE);
return true;
},vf2d{2.f,2.f},ButtonAttr::FIT_TO_LABEL)END;
artificerWindow->ADD("Refine Button",MenuComponent)(geom2d::rect<float>{{0.f,32.f},{144.f,24.f}},"Refine",[](MenuFuncData data){
Menu::OpenMenu(MenuType::ARTIFICER_REFINE);
Component<RowInventoryScrollableWindowComponent>(MenuType::ARTIFICER_REFINE,"Accessory List")->ClearSelectedChild();
artificerWindow->ADD("Disassemble Button",MenuComponent)(geom2d::rect<float>{{0.f,32.f},{144.f,24.f}},"Disassemble",[](MenuFuncData data){
Menu::OpenMenu(MenuType::ARTIFICER_DISASSEMBLE);
return true;
},vf2d{2.f,2.f},ButtonAttr::FIT_TO_LABEL)END;
artificerWindow->ADD("Enchant Button",MenuComponent)(geom2d::rect<float>{{0.f,60.f},{144.f,24.f}},"Enchant",[](MenuFuncData data){
Menu::OpenMenu(MenuType::ARTIFICER_ENCHANT);
Component<RowInventoryScrollableWindowComponent>(MenuType::ARTIFICER_ENCHANT,"Accessory List")->ClearSelectedChild();
return true;
},vf2d{2.f,2.f},ButtonAttr::FIT_TO_LABEL)END;
artificerWindow->ADD("Help Button",MenuComponent)(geom2d::rect<float>{{0.f,88.f},{144.f,24.f}},"Help",[](MenuFuncData data){
@ -76,7 +72,7 @@ void Menu::InitializeArtificerWindow(){
artificerWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="Disassemble Button";
returnData="Refine Button";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
@ -86,20 +82,20 @@ void Menu::InitializeArtificerWindow(){
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
}
,{ //Button Navigation Rules
{"Disassemble Button",{
.up="Leave Button",
.down="Refine Button",}},
{"Refine Button",{
.up="Disassemble Button",
.up="Leave Button",
.down="Disassemble Button",}},
{"Disassemble Button",{
.up="Refine Button",
.down="Enchant Button",}},
{"Enchant Button",{
.up="Refine Button",
.up="Disassemble Button",
.down="Help Button",}},
{"Help Button",{
.up="Enchant Button",
.down="Leave Button",}},
{"Leave Button",{
.up="Help Button",
.down="Disassemble Button",}},
.down="Refine Button",}},
});
}

@ -40,58 +40,50 @@ All rights reserved.
#include "DEFINES.h"
#include <variant>
using namespace std::literals;
class IAttributable{
public:
inline virtual ~IAttributable(){};
std::map<Attribute,std::any>attributes;
std::map<Attribute,std::variant<VARIANTS>>attributes;
inline float&GetFloat(Attribute a){
if(attributes.count(a)==0){
attributes[a]=0.f;
}
return std::any_cast<float&>(attributes[a]);
return std::get<float>(attributes[a]);
};
inline int&GetInt(Attribute a){
if(attributes.count(a)==0){
attributes[a]=0;
}
return std::any_cast<int&>(attributes[a]);
return std::get<int>(attributes[a]);
};
inline std::string&GetString(Attribute a){
if(attributes.count(a)==0){
attributes[a]=""s;
attributes[a]="";
}
return std::any_cast<std::string&>(attributes[a]);
return std::get<std::string>(attributes[a]);
};
inline bool&GetBool(Attribute a){
if(attributes.count(a)==0){
attributes[a]=false;
}
return std::any_cast<bool&>(attributes[a]);
return std::get<bool>(attributes[a]);
};
inline vf2d&GetVf2d(Attribute a){
if(attributes.count(a)==0){
attributes[a]=vf2d{};
}
return std::any_cast<vf2d&>(attributes[a]);
return std::get<vf2d>(attributes[a]);
};
inline std::vector<std::any>&GetVec(Attribute a){
if(attributes.count(a)==0){
attributes[a]=std::vector<std::any>{};
}
return std::any_cast<std::vector<std::any>&>(attributes[a]);
};
inline std::any&GetAny(Attribute a){
if(attributes.count(a)==0){
attributes[a]=std::any{};
}
return attributes[a];
return std::get<std::vector<std::any>>(attributes[a]);
};
inline size_t&GetSizeT(Attribute a){
if(attributes.count(a)==0){
attributes[a]=size_t(0);
}
return std::any_cast<size_t&>(attributes[a]);
return std::get<size_t>(attributes[a]);
};
};

@ -113,9 +113,6 @@ public:
inline auto end()const{
return attributes.end();
}
inline auto size()const{
return attributes.size();
}
inline void clear(){
attributes.clear();
}

@ -41,7 +41,6 @@ All rights reserved.
#include "util.h"
#include "LoadingScreen.h"
#include "Menu.h"
#include "SoundEffect.h"
INCLUDE_game
INCLUDE_DATA
@ -71,12 +70,7 @@ void Audio::Initialize(){
while(data.HasProperty(std::format("channel[{}]",channelCounter))){
std::string channelName=data[std::format("channel[{}]",channelCounter)].GetString();
if(!game->gamepack.Loaded()){
if(!std::filesystem::exists("bgm_directory"_S+channelName))ERR(std::format("WARNING! Could not load file {} for track {}",channelName,songFileName));
if("GENERATE_GAMEPACK"_B){
game->gamepack.AddFile("bgm_directory"_S+channelName);
}
}
if(!std::filesystem::exists("bgm_directory"_S+channelName))ERR(std::format("WARNING! Could not load file {} for track {}",channelName,songFileName));
bgm.AddChannel(channelName);
channelCounter++;
}
@ -127,8 +121,8 @@ MiniAudio&Audio::Engine(){
void Audio::Play(const std::string_view sound){
Engine().Play(std::string(sound));
};
const size_t Audio::LoadAndPlaySFX(const std::string_view sound,const bool loop){
size_t soundID=Engine().LoadSound(std::string(sound),MiniAudio::SFX);
const size_t Audio::LoadAndPlay(const std::string_view sound,const bool loop){
size_t soundID=Engine().LoadSound(std::string(sound));
Engine().Play(soundID,loop);
return soundID;
};
@ -385,7 +379,6 @@ void Audio::SetBGMVolume(float vol){
UpdateBGMVolume();
}
void Audio::SetBGMPitch(float pitch){
if(game->TestingModeEnabled())return;
BGM&track=Self().bgm[Self().playParams.sound];
for(int channelListIndex=0;int trackID:track.GetChannelIDs()){
Engine().SetPitch(trackID,pitch);
@ -417,11 +410,8 @@ float Audio::GetCalculatedBGMVolume(const float channelVol){
}
return channelVol*GetBGMVolume()*GetMuteMult()*pauseMult;
}
float Audio::GetCalculatedSFXVolume(const SoundEffect&sfx){
return sfx.TreatAsBPM()?Audio::GetCalculatedBGMVolume(sfx.GetVolume()):sfx.GetVolume()*GetSFXVolume()*GetMuteMult();
}
float Audio::GetCalculatedSFXVolume(const float sfxVol){
return sfxVol*GetSFXVolume()*GetMuteMult();
float Audio::GetCalculatedSFXVolume(const float vol){
return vol*GetSFXVolume()*GetMuteMult();
}
float Audio::GetMuteMult(){
if(muted)return 0.f;
@ -435,4 +425,4 @@ int Audio::GetPrepareBGMLoopIterations(std::string_view sound){
void Audio::BGM::SetLoopStartTime(const float loopStartTime){
this->loopStartTime=loopStartTime;
}
}

@ -49,8 +49,6 @@ using ChannelIDList=std::vector<ChannelID>;
using Volume=float;
using VolumeList=std::vector<Volume>;
class SoundEffect;
class Audio{
friend class AiL;
public:
@ -61,7 +59,7 @@ public:
static void UpdateLoop();
static void Play(const std::string_view sound);
[[nodiscard]]
static const size_t LoadAndPlaySFX(const std::string_view sound,const bool loop=true);
static const size_t LoadAndPlay(const std::string_view sound,const bool loop=true);
//Prepares a BGM for loading. This means we call UpdateLoop() repeatedly until the loading of the music is complete. Names are found in bgm.txt configuration file.
static void PrepareBGM(const std::string_view sound,const bool loop=true);
//Play immediately a BGM given a name found in bgm.txt configuration file.
@ -82,8 +80,7 @@ public:
//This will get a prepared BGM loop iteration count which is useful for loading stages.
static int GetPrepareBGMLoopIterations(std::string_view sound);
static float GetCalculatedBGMVolume(const float channelVol);
static float GetCalculatedSFXVolume(const SoundEffect&sfx);
static float GetCalculatedSFXVolume(const float sfxVol); //NOTE: This is a more manually invoked function! If you are trying to play a specific sound effect from an event, use the SoundEffect version instead!! This accounts for any additional flags related to volume.
static float GetCalculatedSFXVolume(const float vol);
private:
bool trackLoadStarted=false;
bool trackLoadComplete=false;

@ -52,11 +52,11 @@ INCLUDE_MONSTER_DATA
using A=Attribute;
void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){
switch(PHASE()){
switch(m.I(A::PHASE)){
case 0:{
float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();
if(distToPlayer<operator""_Pixels(ConfigFloat("Attack Range"))){
SETPHASE(1);
m.I(A::PHASE)=1;
m.PerformShootAnimation();
m.F(A::CASTING_TIMER)=ConfigFloat("Chargeup Time");
//The bear slam attack indicator will move with the bear, and the LOCKON_POS variable will hold a polar coordinate indicating distance and angle for where it should be attacking relative to its position.
@ -75,7 +75,7 @@ void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){
case 1:{
m.F(A::CASTING_TIMER)=std::max(0.f,m.F(A::CASTING_TIMER)-fElapsedTime);
if(m.F(A::CASTING_TIMER)==0.f){
SETPHASE(2);
m.I(A::PHASE)=2;
m.F(A::CASTING_TIMER)=ConfigFloat("Attack Animation Wait Time");
m.PerformAnimation("SLAM");
}
@ -85,7 +85,7 @@ void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){
if(m.F(A::CASTING_TIMER)==0.f){
float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();
SoundEffect::PlaySFX("Bear Slam Attack",m.GetPos()+m.V(A::LOCKON_POS).cart());
SETPHASE(0);
m.I(A::PHASE)=0;
m.I(A::BEAR_STOMP_COUNT)++;
geom2d::circle<float>attackCircle={m.GetPos()+m.V(A::LOCKON_POS).cart(),float(operator""_Pixels(ConfigFloat("Smash Attack Diameter"))/2.f)};
if(geom2d::overlaps(attackCircle,game->GetPlayer()->Hitbox())){

@ -1,59 +0,0 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#pragma once
#include "AdventuresInLestoria.h"
#include "util.h"
#include "Effect.h"
INCLUDE_MONSTER_LIST
struct BlackHole:FadeInOutEffect{
inline BlackHole(Oscillator<vf2d>pos,const std::string&img,float lifetime,bool onUpperLevel,Oscillator<vf2d>size,vf2d spd,Oscillator<Pixel>col,float rotation,float rotationSpd,bool additiveBlending=false,float particleSpawnFreq=0.f,const std::function<Effect(const Effect&self)>&particleGenerator={})
:FadeInOutEffect(pos,img,lifetime,onUpperLevel,size,spd,col,rotation,rotationSpd,additiveBlending,particleSpawnFreq,particleGenerator){}
inline bool Update(float fElapsedTime){
for(std::shared_ptr<Monster>&m:MONSTER_LIST){
float distToMonster{util::distance(pos,m->GetPos())};
if(!m->IsSolid()&&m->OnUpperLevel()==OnUpperLevel()&&m->GetZ()<1.f&&distToMonster<="Black Hole"_ENC["PULL IN RADIUS"]/100.f*24){
float pullInForce{util::map_range<float>(distToMonster,0,"Black Hole"_ENC["PULL IN RADIUS"]/100.f*24,"Black Hole"_ENC["PULL IN FORCE MAX"],"Black Hole"_ENC["PULL IN FORCE MIN"])};
m->AddAddedVelocity(util::pointTo(m->GetPos(),pos)*pullInForce);
}
}
return FadeInOutEffect::Update(fElapsedTime);
}
};

@ -57,7 +57,7 @@ void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
RECOVERY,
};
switch(PHASE()){
switch(m.phase){
case PhaseName::MOVE:{
float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos());
if(m.canMove&&distToPlayer>=ConfigInt("Closein Range")/100.f*24){
@ -75,7 +75,7 @@ void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
ScratchPhaseTransition:
m.PerformAnimation("SCRATCH");
m.F(A::CASTING_TIMER)=ConfigInt("Ground Scratch Count")*m.GetCurrentAnimation().GetTotalAnimationDuration();
SETPHASE(PhaseName::SCRATCH);
m.phase=PhaseName::SCRATCH;
vf2d chargeTargetPoint=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).rpoint(ConfigFloat("Charge Distance")/100.f*24);
float distanceToChargePoint=geom2d::line<float>(m.GetPos(),chargeTargetPoint).length();
@ -89,7 +89,7 @@ void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f){
m.PerformShootAnimation();
SETPHASE(PhaseName::CHARGE);
m.phase=PhaseName::CHARGE;
m.AddBuff(BuffType::SPEEDBOOST,INFINITE,ConfigFloat("Charge Movespeed")/100.f-1);
@ -102,7 +102,7 @@ void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
float distToTarget=geom2d::line<float>(m.GetPos(),m.target).length();
auto TransitionToRecoveryPhase=[&](){
SETPHASE(PhaseName::RECOVERY);
m.phase=PhaseName::RECOVERY;
m.F(A::CHARGE_COOLDOWN)=ConfigFloat("Charge Recovery Time");
m.PerformIdleAnimation();
};
@ -120,7 +120,7 @@ void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
m.F(A::CHARGE_COOLDOWN)-=fElapsedTime;
m.targetAcquireTimer=0.f;
m.RemoveBuff(BuffType::SPEEDBOOST);
if(m.F(A::CHARGE_COOLDOWN)<=0)SETPHASE(PhaseName::MOVE);
if(m.F(A::CHARGE_COOLDOWN)<=0)m.phase=PhaseName::MOVE;
}break;
}
}

@ -103,7 +103,7 @@ BulletDestroyState Bomb::MonsterHit(Monster&monster,const uint8_t markStacksBefo
void Bomb::Draw(const Pixel blendCol)const{
Bullet::Draw(blendCol);
game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()},bomb_animation.GetFrame(animation).GetSourceImage()->Decal(),rotates?atan2(vel.y,vel.x)-PI/2:0,bomb_animation.GetFrame(animation).GetSourceRect().size/2,bomb_animation.GetFrame(animation).GetSourceRect().pos,bomb_animation.GetFrame(animation).GetSourceRect().size,scale,fadeOutTime==0?col:Pixel{col.r,col.g,col.b,uint8_t(util::lerp(col.a,uint8_t(0),1-((fadeOutTime-GetFadeoutTimer())/fadeOutTime)))});
game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()},bomb_animation.GetFrame(animation).GetSourceImage()->Decal(),rotates?atan2(vel.y,vel.x)-PI/2:0,bomb_animation.GetFrame(animation).GetSourceRect().size/2,bomb_animation.GetFrame(animation).GetSourceRect().pos,bomb_animation.GetFrame(animation).GetSourceRect().size,scale,fadeOutTime==0?col:Pixel{col.r,col.g,col.b,uint8_t(util::lerp(col.a,0,1-((fadeOutTime-GetFadeoutTimer())/fadeOutTime)))});
}
void Bomb::ModifyOutgoingDamageData(HurtDamageInfo&data){}

@ -56,11 +56,11 @@ void Monster::STRATEGY::BREAKING_PILLAR(Monster&m,float fElapsedTime,std::string
m.animation.ModifyDisplaySprite(m.internal_animState,ConfigString("Break Phase 1 Animation Name"));
}else m.animation.ModifyDisplaySprite(m.internal_animState,ConfigString("Break Phase 2 Animation Name"));
switch(PHASE()){
switch(m.phase){
case INITIALIZE:{
m.F(A::BREAK_TIME)=ConfigFloat("Break Time");
m.F(A::SHAKE_TIMER)=0.2f;
SETPHASE(RUN);
m.phase=RUN;
m.SetStrategyDeathFunction([&](GameEvent&deathEvent,Monster&m,const std::string&strategy){
m.lifetime=0.01f;

@ -41,13 +41,7 @@ All rights reserved.
#include "Monster.h"
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity)
:Buff(attachedTarget,type,duration,intensity,[](std::weak_ptr<Monster>m,Buff&b){}){}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,PlayerBuffExpireCallbackFunction expireCallbackFunc)
:attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity),originalDuration(this->duration),playerBuffCallbackFunc(expireCallbackFunc){}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,MonsterBuffExpireCallbackFunction expireCallbackFunc)
:attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity),originalDuration(this->duration),monsterBuffCallbackFunc(expireCallbackFunc){}
:attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity),originalDuration(this->duration){}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<ItemAttribute> attr)
:attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity),attr(attr),originalDuration(this->duration){}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<std::string> attr)
@ -118,16 +112,13 @@ Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType t
:attachedTarget(attachedTarget),type(type),restorationType(restorationType),duration(duration),intensity(intensity),nextTick(timeBetweenTicks),timeBetweenTicks(timeBetweenTicks),overTimeType(overTimeType),originalDuration(this->duration){}
void Buff::Update(AiL*game,float fElapsedTime){
if(!waitOneTick){
duration-=fElapsedTime;
lifetime+=fElapsedTime;
if(enabled&&overTimeType.has_value()&&lifetime>=nextTick){
BuffTick(game,fElapsedTime);
nextTick+=timeBetweenTicks;
if(restorationType==BuffRestorationType::ONE_OFF)enabled=false;
}
duration-=fElapsedTime;
lifetime+=fElapsedTime;
if(enabled&&overTimeType.has_value()&&lifetime>=nextTick){
BuffTick(game,fElapsedTime);
nextTick+=timeBetweenTicks;
if(restorationType==BuffRestorationType::ONE_OFF)enabled=false;
}
waitOneTick=false;
}
void Buff::BuffTick(AiL*game,float fElapsedTime){

@ -61,10 +61,6 @@ enum BuffType{
DAMAGE_AMPLIFICATION, //Multiplies all incoming damage by this amount.
LETHAL_TEMPO,
BURNING_ARROW_BURN,
SWORD_ENCHANTMENT,
CURSE_OF_PAIN,
CURSE_OF_DEATH,
AFFECTED_BY_LIGHTNING_BOLT, //Intensity indicates number of repeats remaining.
};
enum class BuffRestorationType{
ONE_OFF, //This is used as a hack fix for the RestoreDuringCast Item script since they require us to restore 1 tick immediately. Over time buffs do not apply a tick immediately.
@ -89,7 +85,7 @@ struct Buff{
using MonsterBuffExpireCallbackFunction=std::function<void(std::weak_ptr<Monster>attachedTarget,Buff&b)>;
BuffType type;
BuffRestorationType restorationType{BuffRestorationType::ONE_OFF};
BuffRestorationType restorationType;
float duration=1;
float timeBetweenTicks=1;
float intensity=1;
@ -104,8 +100,6 @@ struct Buff{
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<ItemAttribute> attr);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<std::string> attr);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,PlayerBuffExpireCallbackFunction expireCallbackFunc);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,MonsterBuffExpireCallbackFunction expireCallbackFunc);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc);
@ -115,7 +109,6 @@ struct Buff{
void Update(AiL*game,float fElapsedTime);
private:
bool waitOneTick{true};
bool enabled{true}; //This is only turned off because the ONE_OFF effect. See BuffType::ONE_OFF for more details.
std::optional<BuffOverTimeType::BuffOverTimeType>overTimeType;
void BuffTick(AiL*game,float fElapsedTime);

@ -41,6 +41,6 @@ All rights reserved.
Bullet::Bullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col,vf2d scale,float image_angle)
:IBullet(pos,vel,radius,damage,upperLevel,friendly,col,scale,image_angle){}
//Initializes a bullet with an animation.
Bullet::Bullet(vf2d pos,vf2d vel,float radius,int damage,const std::string&animation,bool upperLevel,bool hitsMultiple,float lifetime,bool rotatesWithAngle,bool friendly,Pixel col,vf2d scale,float image_angle,std::string_view hitSound)
Bullet::Bullet(vf2d pos,vf2d vel,float radius,int damage,std::string animation,bool upperLevel,bool hitsMultiple,float lifetime,bool rotatesWithAngle,bool friendly,Pixel col,vf2d scale,float image_angle,std::string_view hitSound)
:IBullet(pos,vel,radius,damage,animation,upperLevel,hitsMultiple,lifetime,rotatesWithAngle,friendly,col,scale,image_angle,hitSound){}
void Bullet::ModifyOutgoingDamageData(HurtDamageInfo&data){}

@ -43,7 +43,7 @@ class Bullet:public IBullet{
public:
Bullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f);
//Initializes a bullet with an animation.
Bullet(vf2d pos,vf2d vel,float radius,int damage,const std::string&animation,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool rotatesWithAngle=false,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f,std::string_view hitSound="");
Bullet(vf2d pos,vf2d vel,float radius,int damage,std::string animation,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool rotatesWithAngle=false,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f,std::string_view hitSound="");
protected:
virtual void ModifyOutgoingDamageData(HurtDamageInfo&data);
};

@ -39,7 +39,6 @@ All rights reserved.
#include "Bullet.h"
#include "Direction.h"
#include "Effect.h"
#include "TrailEffect.h"
struct EnergyBolt:public Bullet{
float lastParticleSpawn=0;
@ -57,24 +56,15 @@ struct FireBolt:public Bullet{
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
private:
std::optional<std::reference_wrapper<TrailEffect>>flameTrail;
};
struct LightningBolt:public Bullet{
enum ChainLightningStatus{//Any other number represents something not defined here, the number of hits remaining.
ORIGINAL_BOLT=-1,
NO_MORE_HITS=0,
};
float lastParticleSpawn=0;
LightningBolt(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE);
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
private:
/*chainLightningRepeatCount - Set to higher amount to indicate when this monster instead affects monsters due to the enchantment, which changes which shock count variable we look at and will be used for counting down shock times.*/
static void ApplyLightningShock(const Monster&monster,const bool onUpperLevel,const ChainLightningStatus chainLightningRepeatCount=ORIGINAL_BOLT);
};
struct Arrow:public Bullet{
@ -98,13 +88,10 @@ struct Arrow:public Bullet{
struct ChargedArrow:public Bullet{
vf2d lastLaserPos;
ChargedArrow(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE);
ChargedArrow(const std::string&shotArrowGraphic,const std::string&laserGraphic,vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE);
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
private:
std::string laserGraphic;
};
struct FrogTongue:public Bullet{
@ -151,7 +138,7 @@ struct DaggerStab:public Bullet{
float daggerStabDistance;
float knockbackAmt;
DirectionOffsets daggerPositionOffsets;
DaggerStab(Monster&sourceMonster,const std::string&image,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerStabDistance,const DirectionOffsets offsets,bool friendly=false,Pixel col=WHITE);
DaggerStab(Monster&sourceMonster,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerStabDistance,const DirectionOffsets offsets,bool friendly=false,Pixel col=WHITE);
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
@ -164,7 +151,7 @@ struct DaggerSlash:public Bullet{
float frameDuration;
float daggerSlashDistance;
float knockbackAmt;
DaggerSlash(Monster&sourceMonster,const std::string&image,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerSlashDistance,bool friendly=false,Pixel col=WHITE);
DaggerSlash(Monster&sourceMonster,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerSlashDistance,bool friendly=false,Pixel col=WHITE);
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
@ -331,13 +318,11 @@ private:
float automaticDetonationTime{};
float activationWaitTime{};
float lastBeepTime{};
int explosionCount{1};
float rearmTime{};
uint8_t beepCount{1U};
};
struct PurpleEnergyBall:public Bullet{
PurpleEnergyBall(vf2d pos,float radius,float homingRadius,int damage,bool upperLevel,vf2d speed,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1});
PurpleEnergyBall(vf2d pos,float radius,float homingRadius,int damage,bool upperLevel,vf2d speed,bool hitsMultiple=false,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1});
void Update(float fElapsedTime)override;
void Draw(const Pixel blendCol)const override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
@ -345,39 +330,20 @@ struct PurpleEnergyBall:public Bullet{
private:
const vf2d initialScale;
const float homingRadius;
int bounceCount{1};
float lastHitTimer{0.f};
std::optional<std::weak_ptr<Monster>>lastHitTarget;
std::optional<std::weak_ptr<Monster>>homingTarget;
};
struct ThrownProjectile:public Bullet{
ThrownProjectile(vf2d pos,vf2d targetPos,const std::string&img,float explodeRadius,float z,float totalFallTime,float totalRiseZAmt,int damage,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle={0.f},const std::optional<Effect>explodeEffect={},const std::optional<std::string>explodeSoundEffect={},const std::optional<LingeringEffect>lingeringEffect={});
struct PoisonBottle:public Bullet{
PoisonBottle(vf2d pos,vf2d targetPos,float explodeRadius,float z,float totalFallTime,float totalRiseZAmt,int damage,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle={0.f});
void Update(float fElapsedTime)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void _OnGroundLand();
virtual void OnGroundLand(); //This is called when the projectile lands, damage is not dealt yet, default behavior just deals damage in the area.
protected:
private:
const vf2d targetPos;
const vf2d startingPos;
const float totalFallTime;
float originalRisingTime;
float originalFallingTime;
const float originalRisingTime,originalFallingTime;
float risingTime,fallingTime;
const float initialZ;
const float totalRiseZAmt;
const float explodeRadius;
const std::optional<Effect>explodeEffect;
const std::optional<std::string>explodeSoundEffect;
const std::optional<LingeringEffect>lingeringEffect;
const std::string img;
};
struct PoisonBottle:public ThrownProjectile{
PoisonBottle(vf2d pos,vf2d targetPos,const std::string&img,float explodeRadius,float bounceExplodeRadius,float z,float totalFallTime,float totalRiseZAmt,int damage,int additionalBounceCount,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle={0.f});
void ModifyOutgoingDamageData(HurtDamageInfo&data);
virtual void OnGroundLand()override final; //This is called when the projectile lands, damage is not dealt yet, default behavior just deals damage in the area.
private:
const float bounceExplodeRadius;
int additionalBounceCount;
};

@ -76,9 +76,31 @@ namespace CharacterMenuWindow{
std::shared_ptr<RowItemDisplay>GenerateItemDisplay(std::shared_ptr<ScrollableWindowComponent>parent,int invIndex,const std::weak_ptr<Item>it){
auto component=parent->ADD("Equip Item "+std::to_string(invIndex),T)(geom2d::rect<float>{{2,2+invIndex*29.f},{120-15,28}},it,
[&](MenuFuncData data){
const auto SelectedEquipIsDifferent=[](std::weak_ptr<RowItemDisplay>comp){
EquipSlot slot=EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE));
std::weak_ptr<Item>currentItem=Inventory::GetEquip(slot);
switch(slot){
case EquipSlot::RING1:
case EquipSlot::RING2:{
std::weak_ptr<Item>otherItem;
if(slot&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
else
if(slot&EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1);
return ISBLANK(otherItem)||
(&*comp.lock()->GetItem().lock()!=&*otherItem.lock()&&
&*comp.lock()->GetItem().lock()!=&*currentItem.lock());
}break;
default:{
return &*comp.lock()->GetItem().lock()!=&*currentItem.lock();
}
}
};
std::weak_ptr<T>comp=DYNAMIC_POINTER_CAST<T>(data.component.lock());
if(!comp.expired()){
if(Item::SelectedEquipIsDifferent(comp.lock()->GetItem(),EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE)))){ //If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
if(SelectedEquipIsDifferent(comp)){ //If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
Inventory::EquipItem(comp.lock()->GetItem(),EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE)));
#pragma region Fully Decked Out Achievement
@ -177,10 +199,6 @@ void Menu::InitializeCharacterMenuWindow(){
EquipSlot slot=EquipSlot(equipSlot);
auto equipmentSlot=characterMenuWindow->ADD("Equip Slot "+CharacterMenuWindow::slotNames[i],EquipSlotButton)(geom2d::rect<float>{{x,y+28},{24,24}},slot,
[&](MenuFuncData data){
if(game->GetCurrentMap().GetMapType()!=Map::MapType::HUB&&game->GetCurrentMap().GetMapType()!=Map::MapType::WORLD_MAP){
game->AddNotification(AiL::Notification{"Cannot change equipment in a stage!",5.f,YELLOW});
return false;
}
EquipSlot slot=EquipSlot(data.component.lock()->I(Attribute::EQUIP_TYPE));
data.menu.I(A::EQUIP_TYPE)=int(slot);
@ -207,6 +225,28 @@ void Menu::InitializeCharacterMenuWindow(){
equip->SetHoverFunc(
[&](MenuFuncData data){
const auto SelectedEquipIsDifferent=[](std::weak_ptr<RowItemDisplay>comp){
EquipSlot slot=EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE));
std::weak_ptr<Item>currentItem=Inventory::GetEquip(slot);
switch(slot){
case EquipSlot::RING1:
case EquipSlot::RING2:{
std::weak_ptr<Item>otherItem;
if(slot&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
else
if(slot&EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1);
return ISBLANK(otherItem)||
(&*comp.lock()->GetItem().lock()!=&*otherItem.lock()&&
&*comp.lock()->GetItem().lock()!=&*currentItem.lock());
}break;
default:{
return &*comp.lock()->GetItem().lock()!=&*currentItem.lock();
}
}
};
if(!data.component.lock()->GetSubcomponentParent().expired())return true;
std::weak_ptr<RowItemDisplay>button=DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component.lock());
if(!button.expired()){
@ -225,7 +265,7 @@ void Menu::InitializeCharacterMenuWindow(){
if(slot&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
else
if(slot&EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1);
if(Item::SelectedEquipIsDifferent(button.lock()->GetItem(),slot)){ //If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
if(SelectedEquipIsDifferent(button.lock())){ //If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
Inventory::EquipItem(buttonItem,slot);
for(int counter=0;const CharacterMenuWindow::AttributeData&attribute:CharacterMenuWindow::displayAttrs){
std::weak_ptr<StatLabel>statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute.attrName).Name())+" Label");
@ -476,10 +516,28 @@ void Menu::InitializeCharacterMenuWindow(){
,{ //Button Navigation Rules
{"Equip List",{
.up=[](MenuType type,Data&returnData){
Menu::ScrollUp(type,"Equip List",returnData,"Equip Selection Select Button");
if(!Menu::menus[type]->GetSelection().expired()){
auto selection=Menu::menus[type]->GetSelection().lock();
size_t index=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponentIndex(selection);
index--;
if(index>=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents().size()){
returnData="Equip Selection Select Button";
}else{
returnData=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents()[index];
}
}
},
.down=[](MenuType type,Data&returnData){
Menu::ScrollDown(type,"Equip List",returnData,"Equip Selection Select Button");
if(!Menu::menus[type]->GetSelection().expired()){
auto selection=Menu::menus[type]->GetSelection().lock();
size_t index=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponentIndex(selection);
index++;
if(index>=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents().size()){
returnData="Equip Selection Select Button";
}else{
returnData=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents()[index];
}
}
},
.left=[](MenuType type,Data&returnData){
auto equipList=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents();

@ -45,12 +45,9 @@ All rights reserved.
INCLUDE_game
ChargedArrow::ChargedArrow(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col)
:lastLaserPos(pos),laserGraphic("laser.png"),
Bullet(pos,vel,radius,damage,"charged_shot_arrow.png",upperLevel,true,INFINITE,true,friendly,col){}
ChargedArrow::ChargedArrow(const std::string&shotArrowGraphic,const std::string&laserGraphic,vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col)
:lastLaserPos(pos),laserGraphic(laserGraphic),
Bullet(pos,vel,radius,damage,shotArrowGraphic,upperLevel,true,INFINITE,true,friendly,col){}
:lastLaserPos(pos),
Bullet(pos,vel,radius,damage,
"charged_shot_arrow.png",upperLevel,true,INFINITE,true,friendly,col){}
void ChargedArrow::Update(float fElapsedTime){
geom2d::line lineToCurrentPos(geom2d::line(lastLaserPos,pos));
@ -58,10 +55,10 @@ void ChargedArrow::Update(float fElapsedTime){
if(dist>=1){
vf2d midpoint(lineToCurrentPos.rpoint(0.5));
const float normalBeamRadius{12*"Ranger.Ability 2.Radius"_F/100/5};
const float normalBeamRadius{12*"Ranger.Ability 2.Radius"_F/100};
float laserWidth{radius/normalBeamRadius};
game->AddEffect(std::make_unique<Effect>(midpoint,0.1f,laserGraphic,upperLevel,vf2d{laserWidth,dist*2},0.3f,vf2d{},Pixel{192,128,238},atan2(pos.y-lastLaserPos.y,pos.x-lastLaserPos.x)+PI/2,0,true));
game->AddEffect(std::make_unique<Effect>(midpoint,0.1f,"laser.png",upperLevel,vf2d{laserWidth,dist*2},0.3f,vf2d{},Pixel{192,128,238},atan2(pos.y-lastLaserPos.y,pos.x-lastLaserPos.x)+PI/2,0,true));
lastLaserPos=pos;
}
}

@ -50,7 +50,6 @@ All rights reserved.
#endif
INCLUDE_game
INCLUDE_DEMO_BUILD
using A=Attribute;
void Menu::InitializeClassSelectionWindow(){
@ -169,12 +168,6 @@ void Menu::InitializeClassSelectionWindow(){
classSprite->S(A::CLASS_SELECTION)=className;
if(DEMO_BUILD&&i>2){ //Disable the three later classes in the demo build.
classLabel->SetGrayedOut(true);
classButton->SetGrayedOut(true);
classSprite->SetGrayedOut(true);
}
toggleGroup.push_back(classSprite);
}

@ -55,6 +55,6 @@ void ConnectionPoint::ResetVisitedFlag(){
visited=false;
}
[[nodiscard]]const bool ConnectionPoint::Visited()const{
const bool ConnectionPoint::Visited()const{
return visited;
}

@ -1,118 +0,0 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "MonsterStrategyHelpers.h"
#include "util.h"
#include "AdventuresInLestoria.h"
#include "SoundEffect.h"
#include "BulletTypes.h"
using A=Attribute;
INCLUDE_game
void Monster::STRATEGY::CRAB(Monster&m,float fElapsedTime,std::string strategy){
enum PhaseName{
INIT,
MOVE,
PREPARE_CHARGE,
CHARGE,
};
switch(PHASE()){
case INIT:{
m.B(A::RANDOM_DIRECTION)=util::random()%2;
m.F(A::RANDOM_RANGE)=util::random_range(ConfigPixelsArr("Random Direction Range",0),ConfigPixelsArr("Random Direction Range",1));
SETPHASE(MOVE);
}break;
case MOVE:{
m.F(A::ATTACK_COOLDOWN)+=fElapsedTime;
m.F(A::LAST_JUMP_TIMER)+=fElapsedTime;
float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos());
const bool outsideMaxShootingRange=distToPlayer>=ConfigPixelsArr("Stand Still and Shoot Range",1);
if(m.F(A::LAST_JUMP_TIMER)>=ConfigFloat("Stop Check Interval")){
if(util::random(100)<=ConfigFloat("Stop Percent")){
SETPHASE(PREPARE_CHARGE);
m.PerformAnimation("CHARGEUP",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
m.F(A::CASTING_TIMER)=ConfigFloat("Charge Wait Time");
m.target=game->GetPlayer()->GetPos()+util::distance(m.GetPos(),game->GetPlayer()->GetPos())*util::pointTo(m.GetPos(),game->GetPlayer()->GetPos());
}else
if(util::random(100)<=ConfigFloat("Change Direction Chance"))m.B(A::RANDOM_DIRECTION)=!m.B(A::RANDOM_DIRECTION);
m.F(A::LAST_JUMP_TIMER)=0.f;
}else
if(outsideMaxShootingRange){
m.target=game->GetPlayer()->GetPos();
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
}else
if(distToPlayer<ConfigPixels("Run Away Range")){
m.target=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).upoint(-1);
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
}else
if(distToPlayer>=ConfigPixelsArr("Random Direction Range",0)&&distToPlayer<ConfigPixelsArr("Random Direction Range",1)){
#define CW true
#define CCW false
//We are going to walk in a circular direction either CW or CCW (determined in windup phase)
float dirFromPlayer=util::angleTo(game->GetPlayer()->GetPos(),m.GetPos());
float targetDir=m.B(A::RANDOM_DIRECTION)==CW?dirFromPlayer+PI/4:dirFromPlayer-PI/4;
m.target=game->GetPlayer()->GetPos()+vf2d{m.F(A::RANDOM_RANGE),targetDir}.cart();
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
m.F(A::CHASE_TIMER)=1.f;
}
}break;
case PREPARE_CHARGE:{
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f){
SETPHASE(CHARGE);
m.F(A::CHASE_TIMER)=ConfigFloat("Charge Max Time");
m.PerformAnimation("PINCER");
}
}break;
case CHARGE:{
m.F(A::CHASE_TIMER)-=fElapsedTime;
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
if(m.F(A::CHASE_TIMER)<=0.f||m.ReachedTargetPos()){
SETPHASE(MOVE);
m.B(A::RANDOM_DIRECTION)=util::random()%2;
m.F(A::RANDOM_RANGE)=util::random_range(ConfigPixelsArr("Random Direction Range",0),ConfigPixelsArr("Random Direction Range",1));
}
}break;
}
}

@ -70,7 +70,7 @@ void Menu::InitializeCraftItemWindow(){
for(const auto&[name,amt]:consumedResources.GetItems()){
Inventory::RemoveItem(Inventory::GetItem(name)[0],amt);
}
game->GetPlayer()->RemoveMoney(consumedResources.GetCost());
game->GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()-consumedResources.GetCost());
Inventory::UpdateBlacksmithInventoryLists();
SoundEffect::PlaySFX("Craft Equip",SoundEffect::CENTERED);

@ -36,7 +36,7 @@ Artificer has 4 Dialog Options
Refining Gear
Enhancing Gear
- increases the stats of an item.
- Increases a random stat by a random amount that did not reach its cap. (cant go higher then 20% of its current amount in on Enhance attempt and cant go higher then the max stat value of the item)
- Costs 1 piece of the Ring you want to enhance + 20g

@ -62,8 +62,6 @@ using MonsterSpawnerID=int;
#define INCLUDE_WINDOW_SIZE extern vi2d WINDOW_SIZE;
#define INCLUDE_ITEM_CONVERSIONS extern safemap<std::string,IT>ITEM_CONVERSIONS;
#define INCLUDE_ADMIN_MODE extern bool ADMIN_MODE;
#define INCLUDE_DEMO_BUILD extern bool DEMO_BUILD;
#define INCLUDE_ITEM_SCRIPTS extern safemap<std::string,ItemScript>ITEM_SCRIPTS;
#define INCLUDE_PACK_KEY extern std::string PACK_KEY;
@ -88,16 +86,11 @@ 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; \
original_rightClickAbility.isOriginalAbility=true; \
original_ability1.isOriginalAbility=true; \
original_ability2.isOriginalAbility=true; \
original_ability3.isOriginalAbility=true; \
original_ability4.isOriginalAbility=true; \
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; \
@ -122,19 +115,6 @@ Ability&class::GetAbility1(){return ability1;}; \
Ability&class::GetAbility2(){return ability2;}; \
Ability&class::GetAbility3(){return ability3;}; \
Ability&class::GetAbility4(){return ability4;}; \
Ability&class::_GetOriginalAbility1(){return GetOriginalAbility1();}; \
Ability&class::_GetOriginalAbility2(){return GetOriginalAbility2();}; \
Ability&class::_GetOriginalAbility3(){return GetOriginalAbility2();}; \
Ability&class::_GetOriginalRightClickAbility(){return GetOriginalRightClickAbility();}; \
Ability&class::GetOriginalAbility1(){return original_ability1;}; \
Ability&class::GetOriginalAbility2(){return original_ability2;}; \
Ability&class::GetOriginalAbility3(){return original_ability3;}; \
Ability&class::GetOriginalRightClickAbility(){return original_rightClickAbility;}; \
void class::SetAbility4(const Ability&originalAbility){ \
auto it=std::find(Player::ABILITY_LIST.begin(),Player::ABILITY_LIST.end(),originalAbility); \
if(it==Player::ABILITY_LIST.end())ERR(std::format("WARNING! Could not find ability {} in original ability list! This function MUST be supplied with an original ability. Was original ability: {}",originalAbility.name,originalAbility.isOriginalAbility)); \
class::original_ability4=class::ability4=originalAbility; \
} \
std::string&class::GetWalkNAnimation(){return walk_n;}; \
std::string&class::GetWalkEAnimation(){return walk_e;}; \
std::string&class::GetWalkSAnimation(){return walk_s;}; \

@ -45,8 +45,8 @@ All rights reserved.
INCLUDE_game
INCLUDE_ANIMATION_DATA
DaggerSlash::DaggerSlash(Monster&sourceMonster,const std::string&image,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerSlashDistance,bool friendly,Pixel col)
:Bullet(sourceMonster.GetPos(),{},radius,damage,image,upperLevel,false,daggerFrameDuration*ANIMATION_DATA["goblin_sword_slash.png"].GetFrameCountBasedOnAnimationStyle(),true,friendly,col),
DaggerSlash::DaggerSlash(Monster&sourceMonster,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerSlashDistance,bool friendly,Pixel col)
:Bullet(sourceMonster.GetPos(),{},radius,damage,"goblin_sword_slash.png",upperLevel,false,daggerFrameDuration*ANIMATION_DATA["goblin_sword_slash.png"].GetFrameCountBasedOnAnimationStyle(),true,friendly,col),
sourceMonster(sourceMonster),frameDuration(daggerFrameDuration),daggerSlashDistance(daggerSlashDistance),facingDir(facingDir),knockbackAmt(knockbackAmt){}
void DaggerSlash::Update(float fElapsedTime){
ANIMATION_DATA["goblin_sword_slash.png"].ChangeFrameDuration(frameDuration);

@ -45,8 +45,8 @@ All rights reserved.
INCLUDE_game
INCLUDE_ANIMATION_DATA
DaggerStab::DaggerStab(Monster&sourceMonster,const std::string&image,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerStabDistance,const DirectionOffsets offsets,bool friendly,Pixel col)
:Bullet(sourceMonster.GetPos(),{},radius,damage,image,upperLevel,false,daggerFrameDuration*ANIMATION_DATA["dagger_stab.png"].GetFrameCountBasedOnAnimationStyle(),true,friendly,col),
DaggerStab::DaggerStab(Monster&sourceMonster,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerStabDistance,const DirectionOffsets offsets,bool friendly,Pixel col)
:Bullet(sourceMonster.GetPos(),{},radius,damage,"dagger_stab.png",upperLevel,false,daggerFrameDuration*ANIMATION_DATA["dagger_stab.png"].GetFrameCountBasedOnAnimationStyle(),true,friendly,col),
sourceMonster(sourceMonster),frameDuration(daggerFrameDuration),daggerStabDistance(daggerStabDistance),facingDir(facingDir),daggerPositionOffsets(offsets),knockbackAmt(knockbackAmt){}
void DaggerStab::Update(float fElapsedTime){
ANIMATION_DATA["dagger_stab.png"].ChangeFrameDuration(frameDuration);

@ -127,15 +127,11 @@ void DamageNumber::Draw(){
}break;
case DOT:{
std::string text=std::to_string(damage);
DrawDamageNumber(NormalNumber,text,std::pair<Pixel,Pixel>{0xE1BEE7,0x1F083A},std::pair<Pixel,Pixel>{0xDCE775,0x37320A});
DrawDamageNumber(NormalNumber,text,{0xE1BEE7,0x1F083A},{0xDCE775,0x37320A});
}break;
case BACKSTAB:{
std::string text=std::to_string(damage);
DrawDamageNumber(NumberScalesWithDamage,text,std::pair<Pixel,Pixel>{0x888093,0x150035},{BLACK,{0,0,0,0}});
}break;
case SHIELD_LOSS:{
std::string text=std::to_string(damage);
DrawDamageNumber(NumberScalesWithDamage,text,std::pair<Pixel,Pixel>{DARK_BLUE,0x68d7ef},std::pair<Pixel,Pixel>{BLUE,0x4141be});
DrawDamageNumber(NumberScalesWithDamage,text,{0x888093,0x150035},{BLACK,{0,0,0,0}});
}break;
default:ERR(std::format("Damage Number Type {} is not implemented! THIS SHOULD NOT BE HAPPENING!",int(type)));
}
@ -143,7 +139,7 @@ void DamageNumber::Draw(){
float DamageNumber::GetOriginalRiseSpd(){
float riseSpd{20.f};
if(type==INTERRUPT||type==MANA_GAIN||type==HEALTH_GAIN)riseSpd=40.f;
if(type==INTERRUPT||type==MANA_GAIN)riseSpd=40.f;
if(type==DOT)riseSpd=-10.f;
return riseSpd;
}

@ -47,7 +47,6 @@ namespace DamageNumberType{
CRIT,
DOT,
BACKSTAB,
SHIELD_LOSS,
};
}

@ -79,7 +79,7 @@ void DeadlyDash::Draw(const Pixel blendCol)const{
for(int i:std::ranges::iota_view(0,afterImageCount)){
const float fadeTimeBegins{(i+1)*afterImagesLingeringTime};
uint8_t alpha{255U};
if(GetAliveTime()>fadeTimeBegins)alpha=std::max(0.f,float(util::lerp(255,0,(GetAliveTime()-fadeTimeBegins)/afterImagesLingeringTime)));
if(GetAliveTime()>fadeTimeBegins)alpha=std::max(0.f,util::lerp(255,0,(GetAliveTime()-fadeTimeBegins)/afterImagesLingeringTime));
const Animate2D::FrameSequence&animation{ANIMATION_DATA[this->animation]};
const float animationFrameTimer{i*animation.m_fFrameDuration};

@ -1,57 +0,0 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#pragma once
#include "MenuLabel.h"
//A class holding a lambda function where you return an updated menu label that is declared on creation to reduce boilerplate for menu labels that just need to update text differently..
class DynamicMenuLabel:public MenuLabel{
public:
inline DynamicMenuLabel(geom2d::rect<float>rect,std::function<const std::string()>labelUpdateFunc,float scale=1,ComponentAttr attributes=ComponentAttr::NONE)
:labelUpdateFunc(labelUpdateFunc),MenuLabel(rect,"",scale,attributes){}
inline void SetLabelUpdateFunction(std::function<const std::string()>labelUpdateFunc){
this->labelUpdateFunc=labelUpdateFunc;
}
protected:
inline virtual void Update(AiL*game)override{
SetLabel(labelUpdateFunc());
MenuLabel::Update(game);
}
private:
std::function<const std::string()>labelUpdateFunc;
};

@ -44,17 +44,17 @@ INCLUDE_ANIMATION_DATA
INCLUDE_game
INCLUDE_GFX
Effect::Effect(vf2d pos,float lifetime,const std::string&imgFile,bool upperLevel,float size,float fadeout,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:Effect(pos,lifetime,imgFile,upperLevel,0.f,fadeout,vf2d{size,size},spd,col,rotation,rotationSpd,additiveBlending){}
Effect::Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float size,float fadeout,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:Effect::Effect(pos,lifetime,imgFile,upperLevel,0.f,fadeout,vf2d{size,size},spd,col,rotation,rotationSpd,additiveBlending){}
Effect::Effect(vf2d pos,float lifetime,const std::string&imgFile,bool upperLevel,vf2d size,float fadeout,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:Effect(pos,lifetime,imgFile,upperLevel,0.f,fadeout,size,spd,col,rotation,rotationSpd,additiveBlending){}
Effect::Effect(vf2d pos,float lifetime,const std::string&imgFile,bool upperLevel,float fadein,float fadeout,vf2d size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:Effect(pos,lifetime,imgFile,upperLevel,fadein,fadeout,size,spd,EffectType::NONE,col,rotation,rotationSpd,additiveBlending){}
Effect::Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size,float fadeout,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:pos(pos),lifetime(lifetime),upperLevel(upperLevel),size(size),fadeout(fadeout),original_fadeOutTime(fadeout),spd(spd),col(col),rotation(rotation),rotationSpd(rotationSpd),additiveBlending(additiveBlending){
this->animation.AddState(imgFile,ANIMATION_DATA.at(imgFile));
this->animation.ChangeState(internal_animState,imgFile);
}
Effect::Effect(vf2d pos,float lifetime,const std::string&imgFile,bool upperLevel,float fadein,float fadeout,vf2d size,vf2d spd,EffectType type,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:pos(pos),lifetime(lifetime),upperLevel(upperLevel),size(size),fadein(fadein),original_fadeInTime(fadein),fadeout(fadeout),original_fadeOutTime(fadeout),spd(spd),col(col),rotation(rotation),rotationSpd(rotationSpd),additiveBlending(additiveBlending),type(type){
Effect::Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float fadein,float fadeout,vf2d size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:pos(pos),lifetime(lifetime),upperLevel(upperLevel),size(size),fadein(fadein),original_fadeInTime(fadein),fadeout(fadeout),original_fadeOutTime(fadeout),spd(spd),col(col),rotation(rotation),rotationSpd(rotationSpd),additiveBlending(additiveBlending){
this->animation.AddState(imgFile,ANIMATION_DATA.at(imgFile));
this->animation.ChangeState(internal_animState,imgFile);
}
@ -80,7 +80,7 @@ bool Effect::Update(float fElapsedTime){
return true;
}
void Effect::_Draw()const{
void Effect::Draw()const{
if(additiveBlending)game->SetDecalMode(DecalMode::ADDITIVE);
const bool FadeInFinished{original_fadeInTime==0||fadein==original_fadeInTime};
const bool HasFadeout{fadeout>0};
@ -90,22 +90,17 @@ void Effect::_Draw()const{
game->view.DrawDecal(pos-vf2d{3,3}*shadowScale/2+vf2d{0,12*size.y},GFX["circle.png"].Decal(),shadowScale,BLACK);
}
Pixel blendCol{col};
[[unlikely]]if(!FadeInFinished){
blendCol={col.r,col.g,col.b,uint8_t(fadein/original_fadeInTime*col.a)};
game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()},GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,{col.r,col.g,col.b,uint8_t(fadein/original_fadeInTime*col.a)});
}else
[[likely]]if(HasFadeout){
blendCol={col.r,col.g,col.b,uint8_t(fadeout/original_fadeOutTime*col.a)};
game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()},GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,{col.r,col.g,col.b,uint8_t(fadeout/original_fadeOutTime*col.a)});
}else{
game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()},GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,col);
}
Draw(blendCol);
game->SetDecalMode(DecalMode::NORMAL);
}
void Effect::Draw(const Pixel blendCol)const{
game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()},GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,blendCol);
}
Animate2D::Frame Effect::GetFrame()const{
return animation.GetFrame(internal_animState);
}
@ -120,12 +115,4 @@ const EffectType Effect::GetType()const{
const float Effect::GetZ()const{
return z;
}
void Effect::SetType(const EffectType type){
this->type=type;
}
const bool&Effect::OnUpperLevel()const{
return upperLevel;
}

@ -37,10 +37,8 @@ All rights reserved.
#pragma endregion
#pragma once
#include "Animation.h"
#include "IBullet.h"
#include <unordered_set>
#include <variant>
#include "Oscillator.h"
class Monster;
class Player;
using HitList=std::unordered_set<std::variant<Monster*,Player*>>;
@ -49,8 +47,6 @@ enum class EffectType{
NONE,
SPELL_CIRCLE,
MONSTER_SOUL,
BLINK_PORTAL,
TRAIL_OF_FIRE,
};
struct Effect{
@ -71,62 +67,41 @@ struct Effect{
private:
bool dead=false;
public:
Effect(vf2d pos,float lifetime,const std::string&imgFile,bool upperLevel,float size=1.0f,float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
Effect(vf2d pos,float lifetime,const std::string&imgFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
Effect(vf2d pos,float lifetime,const std::string&imgFile,bool upperLevel,float fadein,float fadeout,vf2d size,vf2d spd,Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
Effect(vf2d pos,float lifetime,const std::string&imgFile,bool upperLevel,float fadein,float fadeout,vf2d size,vf2d spd,EffectType type,Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float size=1.0f,float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float fadein,float fadeout,vf2d size,vf2d spd,Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
virtual bool Update(float fElapsedTime);
Animate2D::Frame GetFrame()const;
void _Draw()const;
virtual void Draw(const Pixel blendCol)const;
virtual void Draw()const;
bool OnUpperLevel();
const EffectType GetType()const;
const float GetZ()const;
const bool&OnUpperLevel()const;
void SetType(const EffectType type);
protected:
float original_fadeOutTime;
float original_fadeInTime{};
EffectType type{EffectType::NONE};
private:
Animate2D::Animation<std::string>animation;
Animate2D::AnimationState internal_animState;
private:
bool upperLevel=false;
double aliveTime{};
};
struct Meteor:Effect{
enum MeteorSetting{
METEOR,
COMET,
SOLAR_FLARE,
COMET_FLARE,
};
Meteor(const vf2d pos,const float lifetime,const bool upperLevel,const MeteorSetting setting,const float fadeout=0.0f,const vf2d spd={},const Pixel col=WHITE,const float rotation=0,const float rotationSpd=0,const bool additiveBlending=false);
Meteor(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
float startLifetime=0;
bool shakeField=false;
bool Update(float fElapsedTime)override;
void Draw(const Pixel blendCol)const override;
private:
int meteorImpactParticles{};
float randomColorTintR{};
float randomColorTintG{};
float randomColorTintB{};
float meteorRadius{};
float fallSpdMult{1.f};
float damageMult{1.f};
float fireRingLifetime{};
std::string meteorCrashSFX{};
void Draw()const override;
};
struct PulsatingFire:Effect{
PulsatingFire(vf2d pos,float lifetime,const float radius,std::string imgFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
PulsatingFire(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
std::vector<float>pulsatingFireValues;
float lastParticleTimer=0;
float lastDamageTimer=0;
float radius;
bool Update(float fElapsedTime)override;
void Draw(const Pixel blendCol)const override;
void Draw()const override;
};
struct SwordSlash:Effect{
@ -142,7 +117,7 @@ private:
struct ForegroundEffect:Effect{
ForegroundEffect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float size=1.0f,float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
ForegroundEffect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
virtual void Draw(const Pixel blendCol)const override final;
virtual void Draw()const override final;
};
struct SpellCircle:Effect{
@ -150,7 +125,7 @@ struct SpellCircle:Effect{
SpellCircle(vf2d pos,float lifetime,std::string imgFile,std::string spellInsigniaFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false,vf2d insigniaSize={1,1},float insigniaFadeout=0.0f,vf2d insigniaSpd={},Pixel insigniaCol=WHITE,float insigniaRotation=0,float insigniaRotationSpd=0,bool insigniaAdditiveBlending=false);
Effect spellInsignia{vf2d{},0.f,"spell_insignia.png",false,{}};
virtual bool Update(float fElapsedTime)override final;
virtual void Draw(const Pixel blendCol)const override final;
virtual void Draw()const override final;
};
struct RockLaunch:Effect{
@ -175,7 +150,7 @@ private:
struct MonsterSoul:Effect{
MonsterSoul(vf2d pos,float fadeoutTime,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending=false);
virtual bool Update(float fElapsedTime)override final;
virtual void Draw(const Pixel blendCol)const override final;
virtual void Draw()const override final;
public:
enum Phase{
RISING,
@ -186,31 +161,4 @@ public:
Phase phase{RISING};
float moveSpd{24.f};
float fadeoutTime;
};
struct FadeInOutEffect:Effect{
//cycleSpd is how long it takes to get from fully opaque to fully transparent, and back to fully opaque
FadeInOutEffect(vf2d pos,const std::string&img,float lifetime,float cycleSpd,bool onUpperLevel,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending=false,float particleSpawnFreq=0.f,const std::function<Effect(const Effect&self)>&particleGenerator={});
//A version with oscillators for position and colors, for extra animation effects!
FadeInOutEffect(Oscillator<vf2d>pos,const std::string&img,float lifetime,bool onUpperLevel,Oscillator<vf2d>size,vf2d spd,Oscillator<Pixel>col,float rotation,float rotationSpd,bool additiveBlending=false,float particleSpawnFreq=0.f,const std::function<Effect(const Effect&self)>&particleGenerator={});
virtual bool Update(float fElapsedTime)override;
virtual void Draw(const Pixel blendCol)const override;
std::function<Effect(const Effect&self)>particleGenerator;
const float particleSpawnFreq;
Oscillator<vf2d>posOscillator;
Oscillator<vf2d>sizeOscillator;
Oscillator<Pixel>colOscillator;
float particleSpawnTimer{};
const float originalParticleSpawnTimer{};
};
struct LingeringEffect:FadeInOutEffect{
LingeringEffect(vf2d pos,const std::string&img,const std::string&soundEffect,float radius,int damage,float damageFreq,const HurtType friendly,float lifetime,float cycleSpd,bool onUpperLevel,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending=false,float particleSpawnFreq=0.f,const std::function<Effect(const Effect&self)>&particleGenerator={});
virtual bool Update(float fElapsedTime)override final;
const int damage;
float damageTimer{};
const float originalDamageTimer{};
const float radius;
HurtType friendly;
const size_t sfxID{};
};

@ -45,7 +45,7 @@ INCLUDE_game
EnergyBolt::EnergyBolt(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col)
:Bullet(pos,vel,radius,damage,
"energy_bolt.png",upperLevel,game->GetPlayer()->HasEnchant("Piercing Bolt")?true:false,INFINITE,true,friendly,col){}
"energy_bolt.png",upperLevel,false,INFINITE,true,friendly,col){}
void EnergyBolt::Update(float fElapsedTime){
lastParticleSpawn=std::max(0.f,lastParticleSpawn-fElapsedTime);
@ -67,9 +67,8 @@ BulletDestroyState EnergyBolt::PlayerHit(Player*player)
BulletDestroyState EnergyBolt::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)
{
if(!game->GetPlayer()->HasEnchant("Piercing Bolt"))fadeOutTime="Wizard.Auto Attack.BulletHitFadeoutTime"_F;
fadeOutTime="Wizard.Auto Attack.BulletHitFadeoutTime"_F;
game->AddEffect(std::make_unique<Effect>(monster.GetPos(),0,"splash_effect.png",upperLevel,monster.GetSizeMult(),"Wizard.Auto Attack.SplashEffectFadeoutTime"_F));
if(hitList.size()==1)damage=ceil(damage*"Piercing Bolt"_ENC["MULTI TARGET DAMAGE"]/100.f);
return BulletDestroyState::KEEP_ALIVE;
}

@ -67,8 +67,7 @@ void EnvironmentalAudio::SetAudioName(const std::string_view audioName){
}
void EnvironmentalAudio::Activate(){
if(activated)return;
soundInstance=Audio::LoadAndPlaySFX(operator""_SFX(SOUND_DATA[audioName].file.c_str(),SOUND_DATA[audioName].file.length()),true);
Audio::Engine().SetPitch(soundInstance,util::random_range(0.9f,1.1f));
soundInstance=Audio::LoadAndPlay(operator""_SFX(SOUND_DATA[audioName].file.c_str(),SOUND_DATA[audioName].file.length()),true);
activated=true;
}
void EnvironmentalAudio::Deactivate(){

@ -52,7 +52,7 @@ public:
inline void OnEquipStatsUpdate()override{
const std::weak_ptr<Item>equip=Inventory::GetEquip(slot);
if(!ISBLANK(equip)){
icon=const_cast<Decal*>(equip.lock()->Icon().Decal());
icon=const_cast<Decal*>(equip.lock()->Decal());
itemRef=equip;
}else{
icon=nullptr;

@ -115,15 +115,15 @@ type DYNAMIC_CAST(auto variable){
}
template<typename T,typename U>
const std::shared_ptr<T>DYNAMIC_POINTER_CAST(const std::shared_ptr<U>&variable){
const std::shared_ptr<T> newVariable=dynamic_pointer_cast<T>(variable);
std::shared_ptr<T>DYNAMIC_POINTER_CAST(const std::shared_ptr<U>&variable){
std::shared_ptr<T> newVariable=dynamic_pointer_cast<T>(variable);
if(!newVariable)ERR("Could not dynamic cast to pointer type "<<typeid(newVariable).name()<<"!");
return newVariable;
}
template<typename T,typename U>
const std::shared_ptr<T>DYNAMIC_POINTER_CAST(const std::weak_ptr<U>&variable){
const std::shared_ptr<T> newVariable=dynamic_pointer_cast<T>(variable.lock());
std::shared_ptr<T>DYNAMIC_POINTER_CAST(const std::weak_ptr<U>&variable){
std::shared_ptr<T> newVariable=dynamic_pointer_cast<T>(variable.lock());
if(!newVariable)ERR("Could not dynamic cast to pointer type "<<typeid(newVariable).name()<<"!");
return newVariable;
}

@ -47,7 +47,7 @@ INCLUDE_ANIMATION_DATA
INCLUDE_game
ExplosiveTrap::ExplosiveTrap(vf2d pos,float radius,float explosionRadius,float automaticDetonationTime,int damage,float fadeinTime,float fadeoutTime,float activationWaitTime,bool upperLevel,bool hitsMultiple,float lifetime,bool friendly,Pixel col,vf2d scale)
:activationWaitTime(activationWaitTime),automaticDetonationTime(automaticDetonationTime),activationRadius(radius),explosionRadius(explosionRadius),explosionCount(game->GetPlayer()->HasEnchant("Concussive Trap")?"Concussive Trap"_ENC["TRAP EXPLODE COUNT"]:1),Bullet(pos,{},0.f,damage,"Ability Icons/explosive_trap.png",upperLevel,hitsMultiple,INFINITE,false,friendly,col,scale,0.f,"Trap Hit"){
:activationWaitTime(activationWaitTime),automaticDetonationTime(automaticDetonationTime),activationRadius(radius),explosionRadius(explosionRadius),Bullet(pos,{},0.f,damage,"Ability Icons/explosive_trap.png",upperLevel,hitsMultiple,lifetime,false,friendly,col,scale,0.f,"Trap Hit"){
fadeInTime=fadeinTime;
animation.AddState("explosive_trap.png",ANIMATION_DATA["explosive_trap.png"]);
if(!friendly)ERR("WARNING! Trying to use unimplemented enemy version of the Explosive Trap Bullet!");
@ -58,10 +58,7 @@ void ExplosiveTrap::Update(float fElapsedTime){
if(IsDeactivated())return;
if(rearmTime>0.f){
rearmTime-=fElapsedTime;
if(rearmTime<=0.f)Detonate();
}else if(!trapActivated){
if(!trapActivated){
activationWaitTime-=fElapsedTime;
if(activationWaitTime<=0.f){
radius=activationRadius;
@ -92,14 +89,13 @@ BulletDestroyState ExplosiveTrap::PlayerHit(Player*player){
}
void ExplosiveTrap::Detonate(){
const HurtList list{game->Hurt(pos,"Trapper.Ability 3.Explosion Radius"_F/100.f*24,damage,OnUpperLevel(),GetZ(),HurtType::MONSTER,HurtFlag::PLAYER_ABILITY)};
fadeOutTime=0.5f;
const HurtList list{game->HurtNotHit(pos,"Trapper.Ability 3.Explosion Radius"_F/100.f*24,damage,hitList,OnUpperLevel(),GetZ(),HurtType::MONSTER,HurtFlag::PLAYER_ABILITY)};
for(const auto&[targetPtr,wasHit]:list){
if(wasHit){
std::get<Monster*>(targetPtr)->ApplyMark("Trapper.Ability 3.Explosion Mark Stack Time"_F,"Trapper.Ability 3.Explosion Mark Stack Increase"_I);
if(explosionCount==1)std::get<Monster*>(targetPtr)->ProximityKnockback(pos,"Trapper.Ability 3.Explosion Knockback Amount"_F);
else std::get<Monster*>(targetPtr)->ProximityKnockback(pos,20.f);
if(explosionCount==1)std::get<Monster*>(targetPtr)->Knockup("Trapper.Ability 3.Explosion Knockup Amount"_F);
else std::get<Monster*>(targetPtr)->Knockup(0.1f);
std::get<Monster*>(targetPtr)->ProximityKnockback(pos,"Trapper.Ability 3.Explosion Knockback Amount"_F);
std::get<Monster*>(targetPtr)->Knockup("Trapper.Ability 3.Explosion Knockup Amount"_F);
}
}
@ -107,13 +103,7 @@ void ExplosiveTrap::Detonate(){
explodeEffect->scaleSpd={0.125f,0.125f};
game->AddEffect(std::move(explodeEffect));
SoundEffect::PlaySFX("Explosion",pos);
explosionCount--;
if(explosionCount>0)rearmTime=0.6f;
else{
fadeOutTime=0.5f;
Deactivate();
lifetime=0.f;
}
Deactivate();
}
BulletDestroyState ExplosiveTrap::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit){
@ -121,6 +111,9 @@ BulletDestroyState ExplosiveTrap::MonsterHit(Monster&monster,const uint8_t markS
monster.Hurt(0,monster.OnUpperLevel(),monster.GetZ(),HurtFlag::PLAYER_ABILITY);//Triggers mark multiple times after the first mark.
}
monster.ProximityKnockback(pos,"Trapper.Ability 3.Explosion Knockback Amount"_F);
monster.Knockup("Trapper.Ability 3.Explosion Knockup Amount"_F);
Detonate();
return BulletDestroyState::KEEP_ALIVE;

@ -1,65 +0,0 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "Effect.h"
#include "AdventuresInLestoria.h"
#include "DEFINES.h"
#include "util.h"
INCLUDE_game
FadeInOutEffect::FadeInOutEffect(vf2d pos,const std::string&img,float lifetime,float cycleSpd,bool onUpperLevel,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending,float particleSpawnFreq,const std::function<Effect(const Effect&self)>&particleGenerator)
:FadeInOutEffect({pos,pos,cycleSpd},img,lifetime,onUpperLevel,{{size,size},{size,size},cycleSpd},spd,{col,{col.r,col.g,col.b,0},cycleSpd},rotation,rotationSpd,additiveBlending,particleSpawnFreq,particleGenerator){}
FadeInOutEffect::FadeInOutEffect(Oscillator<vf2d>pos,const std::string&img,float lifetime,bool onUpperLevel,Oscillator<vf2d>size,vf2d spd,Oscillator<Pixel>col,float rotation,float rotationSpd,bool additiveBlending,float particleSpawnFreq,const std::function<Effect(const Effect&self)>&particleGenerator)
:particleSpawnFreq(particleSpawnFreq),particleGenerator(particleGenerator),particleSpawnTimer(particleSpawnFreq),originalParticleSpawnTimer(particleSpawnTimer),posOscillator(pos),colOscillator(col),sizeOscillator(size),Effect(pos.first,lifetime,img,onUpperLevel,size.first,0.25f,spd,col.first,rotation,rotationSpd,additiveBlending){}
bool FadeInOutEffect::Update(float fElapsedTime){
if(particleGenerator){
particleSpawnTimer-=fElapsedTime;
if(particleSpawnTimer<=0.f){
particleSpawnTimer+=originalParticleSpawnTimer;
game->AddEffect(std::make_unique<Effect>(particleGenerator(*this)));
}
}
pos=posOscillator.Update(fElapsedTime);
size=sizeOscillator.Update(fElapsedTime);
col=colOscillator.Update(fElapsedTime);
return Effect::Update(fElapsedTime);
}
void FadeInOutEffect::Draw(const Pixel blendCol)const{
Effect::Draw(blendCol);
}

@ -84,7 +84,7 @@ void FallingStone::Update(float fElapsedTime){
}
void FallingStone::Draw(const Pixel blendCol)const{
if(lifetime<=indicatorDisplayTime){
indicator._Draw();
indicator.Draw();
}
Bullet::Draw(blendCol);
}

@ -41,16 +41,13 @@ All rights reserved.
#include "DEFINES.h"
#include "util.h"
#include "SoundEffect.h"
#include "TrailEffect.h"
INCLUDE_game
INCLUDE_MONSTER_LIST
FireBolt::FireBolt(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col)
:Bullet(pos,vel,radius,damage,
"energy_bolt.png",upperLevel,false,INFINITE,true,friendly,col){
if(game->GetPlayer()->HasEnchant("Trail of Fire"))flameTrail=dynamic_cast<TrailEffect&>(game->AddEffect(std::make_unique<TrailEffect>(pos,"Trail of Fire"_ENC["TRAIL DURATION"],"FlamesTexture.png","Trail of Fire"_ENC["TRAIL DAMAGE"]/100.f*game->GetPlayer()->GetAttack(),"Trail of Fire"_ENC["TRAIL TICK FREQUENCY"],upperLevel,1.f,vf2d{1.f,2.f},30000.f,Oscillator<Pixel>{{255,0,0,128},Pixel(0xE74F30),2.f},EffectType::TRAIL_OF_FIRE,true),true));
}
"energy_bolt.png",upperLevel,false,INFINITE,true,friendly,col){}
void FireBolt::Update(float fElapsedTime){
lastParticleSpawn=std::max(0.f,lastParticleSpawn-fElapsedTime);
@ -58,7 +55,7 @@ void FireBolt::Update(float fElapsedTime){
lastParticleSpawn="Wizard.Ability 1.ParticleFrequency"_F;
game->AddEffect(std::make_unique<Effect>(pos,"Wizard.Ability 1.ParticleLifetimeRange"_FRange,"energy_particle.png",upperLevel,"Wizard.Ability 1.ParticleSizeRange"_FRange,"Wizard.Ability 1.ParticleFadeoutTime"_F,vf2d{"Wizard.Ability 1.ParticleXSpeedRange"_FRange,"Wizard.Ability 1.ParticleYSpeedRange"_FRange},Pixel{uint8_t("Wizard.Ability 1.ParticleRedRange"_FRange),uint8_t("Wizard.Ability 1.ParticleGreenRange"_FRange),uint8_t("Wizard.Ability 1.ParticleBlueRange"_FRange),uint8_t("Wizard.Ability 1.ParticleAlphaRange"_FRange)}));
}
if(distanceTraveled>"Wizard.Ability 1.Max Range"_F&&IsActivated()){
if(distanceTraveled>"Wizard.Ability 1.Range"_F&&IsActivated()){
fadeOutTime="Wizard.Ability 1.BulletHitFadeoutTime"_F;
for(int i=0;i<"Wizard.Ability 1.BulletHitExplosionParticleCount"_I;i++){
game->AddEffect(std::make_unique<Effect>(pos,"Wizard.Ability 1.BulletHitExplosionParticleLifetimeRange"_FRange,"circle.png",upperLevel,"Wizard.Ability 1.BulletHitExplosionParticleSizeRange"_FRange,"Wizard.Ability 1.BulletHitExplosionParticleFadeoutTimeRange"_FRange,vf2d{"Wizard.Ability 1.BulletHitExplosionParticleSpeedRange"_FRange,"Wizard.Ability 1.BulletHitExplosionParticleSpeedRange"_FRange},Pixel{uint8_t("Wizard.Ability 1.BulletHitExplosionParticleRedRange"_FRange),uint8_t("Wizard.Ability 1.BulletHitExplosionParticleGreenRange"_FRange),uint8_t("Wizard.Ability 1.BulletHitExplosionParticleBlueRange"_FRange),uint8_t("Wizard.Ability 1.BulletHitExplosionParticleAlphaRange"_FRange)}));
@ -75,8 +72,6 @@ void FireBolt::Update(float fElapsedTime){
SoundEffect::PlaySFX("Wizard Fire Bolt Hit",pos);
}
if(flameTrail)flameTrail.value().get().SetEndPos(pos);
}
BulletDestroyState FireBolt::PlayerHit(Player*player)

@ -47,6 +47,12 @@ ForegroundEffect::ForegroundEffect(vf2d pos,float lifetime,std::string imgFile,b
:Effect(pos,lifetime,imgFile,upperLevel,size,fadeout,spd,col,rotation,rotationSpd,additiveBlending){}
ForegroundEffect::ForegroundEffect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size,float fadeout,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:Effect(pos,lifetime,imgFile,upperLevel,size,fadeout,spd,col,rotation,rotationSpd,additiveBlending){}
void ForegroundEffect::Draw(const Pixel blendCol)const{
game->DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,blendCol);
void ForegroundEffect::Draw()const{
if(additiveBlending)game->SetDecalMode(DecalMode::ADDITIVE);
if(fadeout==0){
game->DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,col);
} else {
game->DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,{col.r,col.g,col.b,uint8_t(fadeout/original_fadeOutTime*255)});
}
game->SetDecalMode(DecalMode::NORMAL);
}

@ -51,11 +51,11 @@ using A=Attribute;
void Monster::STRATEGY::FROG(Monster&m,float fElapsedTime,std::string strategy){
m.F(A::LOCKON_WAITTIME)=std::max(0.0f,m.F(A::LOCKON_WAITTIME)-fElapsedTime);
phase:
switch(PHASE()){
switch(m.I(A::PHASE)){
case 0:{ //Move towards phase.
float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();
if(distToPlayer<24*ConfigInt("Range")/100.f){
SETPHASE(PHASE()+1);
m.I(A::PHASE)++;
m.F(A::LOCKON_WAITTIME)=ConfigFloat("Lockon Wait Time");
m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos();
m.RotateTowardsPos(m.V(A::LOCKON_POS));
@ -72,23 +72,23 @@ void Monster::STRATEGY::FROG(Monster&m,float fElapsedTime,std::string strategy){
SoundEffect::PlaySFX("Slime Shoot",m.pos);
CreateBullet(FrogTongue)(m,tongueMaxRangePos,ConfigFloat("Attack Duration"),m.GetAttack(),m.OnUpperLevel(),ConfigFloat("Tongue Knockback Strength"),false,ConfigPixel("Tongue Color"))EndBullet;
m.PerformShootAnimation();
SETPHASE(2);
m.I(A::PHASE)=2;
}
m.PerformIdleAnimation();
}break;
case 2:{
if(m.F(A::LOCKON_WAITTIME)==0.0f){
m.F(A::LOCKON_WAITTIME)=ConfigFloat("Attack Recovery Time");
SETPHASE(3);
m.I(A::PHASE)=3;
}
}break;
case 3:{
if(m.F(A::LOCKON_WAITTIME)==0.0f){
SETPHASE(0);
m.I(A::PHASE)=0;
}
}break;
default:{
ERR(std::format("Unhandled phase {} for {} strategy!",PHASE(),strategy));
ERR(std::format("Unhandled phase {} for {} strategy!",m.I(A::PHASE),strategy));
}
}
}

@ -56,7 +56,7 @@ void FrogTongue::Update(float fElapsedTime){
geom2d::line<float>lineToTarget(pos,targetPos);
vf2d drawVec=lineToTarget.vector().norm()*3;
tongueLength=util::lerp(0.f,lineToTarget.length(),pow(sin((lifetime*PI)/duration),20.f));
tongueLength=util::lerp(0,lineToTarget.length(),pow(sin((lifetime*PI)/duration),20.f));
vf2d tongueEndPos=geom2d::line<float>(pos+drawVec,targetPos).upoint(pow(sin((lifetime*PI)/duration),20.f));
geom2d::line<float>tongueLine(pos+drawVec,tongueEndPos);

@ -1,84 +0,0 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "MonsterStrategyHelpers.h"
#include "util.h"
#include "AdventuresInLestoria.h"
#include "SoundEffect.h"
#include "BulletTypes.h"
using A=Attribute;
INCLUDE_game
void Monster::STRATEGY::GIANT_CRAB(Monster&m,float fElapsedTime,std::string strategy){
enum PhaseName{
PREPARE_CHARGE,
CHARGE,
};
switch(PHASE()){
case PREPARE_CHARGE:{
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f){
m.AddBuff(BuffType::SPEEDBOOST,ConfigFloat("Charge Time"),0.f);
m.F(A::SPEED_RAMPUP_TIMER)=0.1f;
SETPHASE(CHARGE);
}
}break;
case CHARGE:{
m.F(A::CHASE_TIMER)+=fElapsedTime;
m.F(A::SPEED_RAMPUP_TIMER)-=fElapsedTime;
if(m.F(A::CHASE_TIMER)>=ConfigFloat("Charge Time")||m.ReachedTargetPos()){
m.F(A::CHASE_TIMER)=0.f;
m.target=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).rpoint(util::distance(m.GetPos(),game->GetPlayer()->GetPos())+ConfigPixels("Charge Extend Distance"));
m.RemoveBuff(BuffType::SPEEDBOOST);
m.F(A::CASTING_TIMER)=ConfigFloat("Charge Wait Time");
SETPHASE(PREPARE_CHARGE);
}
if(m.F(A::SPEED_RAMPUP_TIMER)<=0.f){
m.F(A::SPEED_RAMPUP_TIMER)=0.1f;
if(m.HasBuff(BuffType::SPEEDBOOST)){
const float buffIntensity{m.GetBuffs(BuffType::SPEEDBOOST)[0].intensity};
m.EditBuff(BuffType::SPEEDBOOST,0).intensity=std::min(buffIntensity+ConfigFloat("Movespeed Rampup Final Amount")/100.f/(ConfigFloat("Movespeed Rampup Time")/0.1f),ConfigFloat("Movespeed Rampup Final Amount")/100.f);
}
}
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
}break;
}
}

@ -1,85 +0,0 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "MonsterStrategyHelpers.h"
#include "util.h"
#include "AdventuresInLestoria.h"
#include "SoundEffect.h"
#include "BulletTypes.h"
using A=Attribute;
INCLUDE_game
INCLUDE_MONSTER_LIST
void Monster::STRATEGY::GIANT_OCTOPUS(Monster&m,float fElapsedTime,std::string strategy){
enum PhaseName{
IDENTIFY_ARMS,
NORMAL,
};
switch(PHASE()){
case IDENTIFY_ARMS:{
m.F(A::CASTING_TIMER)=util::random_range(ConfigFloatArr("Tentacle Move Timer",0),ConfigFloatArr("Tentacle Move Timer",1));
for(std::shared_ptr<Monster>&arm:MONSTER_LIST){
const std::string OCTOPUS_ARM_NAME{"Octopus Arm"};
if(arm->GetName()==OCTOPUS_ARM_NAME){
std::weak_ptr<Monster>armPtr{arm};
m.VEC(A::ARM_LIST).emplace_back(armPtr);
m.VEC(A::ARM_LOCATIONS).emplace_back(armPtr.lock()->GetPos());
}
}
}break;
case NORMAL:{
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f){
int deadMonsterCount{0};
std::vector<vf2d>tempArmLocs;
for(size_t i=0U;std::any&arm:m.VEC(A::ARM_LIST)){
const std::weak_ptr<Monster>&m{std::any_cast<std::weak_ptr<Monster>>(arm)};
if(m.expired()||m.lock()->IsDead()){
deadMonsterCount++;
tempArmLocs.emplace_back(std::any_cast<vf2d>(m.lock()->VEC(A::ARM_LOCATIONS)));
}
}
if(deadMonsterCount>0){
}
}
}break;
}
}

@ -52,16 +52,17 @@ void Monster::STRATEGY::GOBLIN_BOAR_RIDER(Monster&m,float fElapsedTime,std::stri
if(!m.B(A::INITIALIZED_MOUNTED_MONSTER)){
m.B(A::INITIALIZED_MOUNTED_MONSTER)=true;
m.F(A::PERCEPTION_LEVEL)=ConfigFloat("Starting Perception Level");
m.internal_mounted_animState=Animate2D::AnimationState{};
m.mounted_animation=Animate2D::Animation<std::string>{};
for(bool firstAnimation=true;const std::string&animation:Config("Imposed Monster Animations").GetValues()){
m.mounted_animation.value().AddState(animation,ANIMATION_DATA.at(animation));
if(firstAnimation)m.mounted_animation.value().ChangeState(m.internal_mounted_animState,animation);
if(firstAnimation)m.mounted_animation.value().ChangeState(m.internal_mounted_animState.value(),animation);
firstAnimation=false;
}
m.mountedSprOffset=ConfigVec("Imposed Monster Offset");
m.deathData.emplace_back(ConfigString("Spawned Monster"),1U);
m.deathData.push_back(DeathSpawnInfo{ConfigString("Spawned Monster"),1U});
}
BOAR(m,fElapsedTime,"Boar");
@ -78,11 +79,11 @@ void Monster::STRATEGY::GOBLIN_BOAR_RIDER(Monster&m,float fElapsedTime,std::stri
m.F(A::ATTACK_COOLDOWN)=0.f;
m.F(A::PERCEPTION_LEVEL)=std::min(ConfigFloat("Maximum Perception Level"),m.F(A::PERCEPTION_LEVEL)+ConfigFloat("Perception Level Increase"));
m.mounted_animation.value().ChangeState(m.internal_mounted_animState,std::format("GOBLIN_BOW_MOUNTED_{}",int(m.GetFacingDirection())));
m.mounted_animation.value().ChangeState(m.internal_mounted_animState.value(),std::format("GOBLIN_BOW_MOUNTED_{}",int(m.GetFacingDirection())));
}
}else
if(m.F(A::ATTACK_COOLDOWN)>=ConfigFloat("Attack Reload Time")){
m.F(A::SHOOT_TIMER)=ConfigFloat("Attack Windup Time");
m.mounted_animation.value().ChangeState(m.internal_mounted_animState,std::format("GOBLIN_BOW_ATTACK_{}",int(m.GetFacingDirection())));
m.mounted_animation.value().ChangeState(m.internal_mounted_animState.value(),std::format("GOBLIN_BOW_ATTACK_{}",int(m.GetFacingDirection())));
}
}

@ -60,12 +60,12 @@ void Monster::STRATEGY::GOBLIN_BOMB(Monster&m,float fElapsedTime,std::string str
RUN,
};
switch(PHASE()){
switch(m.phase){
case INITIALIZE:{
m.F(A::SHOOT_TIMER)=m.randomFrameOffset;
SETPHASE(RUN);
m.phase=RUN;
}break;
case RUN:{
case RUN:{
m.F(A::SHOOT_TIMER)+=fElapsedTime;
m.F(A::SHOOT_ANIMATION_TIME)-=fElapsedTime;
float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();

@ -66,10 +66,10 @@ void Monster::STRATEGY::GOBLIN_BOW(Monster&m,float fElapsedTime,std::string stra
};
#pragma endregion
switch(PHASE()){
switch(m.phase){
case INITIALIZE_PERCEPTION:{
m.F(A::PERCEPTION_LEVEL)=ConfigFloat("Starting Perception Level");
SETPHASE(MOVE);
m.phase=MOVE;
}break;
case MOVE:{
m.F(A::ATTACK_COOLDOWN)+=fElapsedTime;
@ -79,7 +79,7 @@ void Monster::STRATEGY::GOBLIN_BOW(Monster&m,float fElapsedTime,std::string stra
const bool outsideMaxShootingRange=distToPlayer>=ConfigPixelsArr("Stand Still and Shoot Range",1);
auto PrepareToShoot=[&](){
SETPHASE(LOCKON);
m.phase=LOCKON;
m.F(A::SHOOT_TIMER)=ConfigFloat("Attack Windup Time");
m.PerformAnimation("SHOOT",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
@ -118,7 +118,7 @@ void Monster::STRATEGY::GOBLIN_BOW(Monster&m,float fElapsedTime,std::string stra
m.V(A::EXTENDED_LINE)=extendedLine;
Arrow tempArrow{m.GetPos(),extendedLine,pointTowardsPlayer.vector().norm()*ConfigFloat("Arrow Spd"),"goblin_arrow.png",ConfigFloat("Arrow Hitbox Radius"),m.GetAttack(),m.OnUpperLevel()};
m.V(A::FIRE_VELOCITY)=tempArrow.PointToBestTargetPath(m.F(A::PERCEPTION_LEVEL));
SETPHASE(WINDUP);
m.phase=WINDUP;
}
}break;
case WINDUP:{
@ -128,7 +128,7 @@ void Monster::STRATEGY::GOBLIN_BOW(Monster&m,float fElapsedTime,std::string stra
CreateBullet(Arrow)(m.GetPos(),m.V(A::EXTENDED_LINE),m.V(A::FIRE_VELOCITY),"goblin_arrow.png",ConfigFloat("Arrow Hitbox Radius"),m.GetAttack(),m.OnUpperLevel())EndBullet;
m.F(A::PERCEPTION_LEVEL)=std::min(ConfigFloat("Maximum Perception Level"),m.F(A::PERCEPTION_LEVEL)+ConfigFloat("Perception Level Increase"));
m.PerformAnimation("IDLE",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
SETPHASE(MOVE);
m.phase=MOVE;
}
m.B(A::RANDOM_DIRECTION)=util::random()%2;
m.F(A::RANDOM_RANGE)=util::random_range(ConfigPixelsArr("Random Direction Range",0),ConfigPixelsArr("Random Direction Range",1));

@ -66,13 +66,13 @@ void Monster::STRATEGY::GOBLIN_DAGGER(Monster&m,float fElapsedTime,std::string s
SLASH
};
switch(PHASE()){
switch(m.phase){
case MOVE:{
float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos());
if(distToPlayer>ConfigFloat("Attack Spacing")/100.f*24){
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
}else{
SETPHASE(WINDUP);
m.phase=WINDUP;
m.I(A::ATTACK_TYPE)=util::random()%2; //Choose randomly between stab or slash.
switch(m.I(A::ATTACK_TYPE)){
case STAB:{
@ -90,18 +90,18 @@ void Monster::STRATEGY::GOBLIN_DAGGER(Monster&m,float fElapsedTime,std::string s
case WINDUP:{
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0){
SETPHASE(RECOVERY);
m.phase=RECOVERY;
switch(m.I(A::ATTACK_TYPE)){
case STAB:{
vf2d stabTarget=game->GetPlayer()->GetPos();
m.PerformAnimation("STABBING",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
CreateBullet(DaggerStab)(m,ConfigString("Dagger Stab Image"),ConfigFloat("Dagger Hit Radius"),m.GetAttack(),ConfigFloat("Dagger Stab Knockback"),m.OnUpperLevel(),m.GetFacingDirectionToTarget(stabTarget),ConfigFloat("Dagger Frame Duration"),ConfigFloat("Dagger Stab Distance"),
CreateBullet(DaggerStab)(m,ConfigFloat("Dagger Hit Radius"),m.GetAttack(),ConfigFloat("Dagger Stab Knockback"),m.OnUpperLevel(),m.GetFacingDirectionToTarget(stabTarget),ConfigFloat("Dagger Frame Duration"),ConfigFloat("Dagger Stab Distance"),
DaggerStab::DirectionOffsets{ConfigVec("Dagger Up Offset"),ConfigVec("Dagger Down Offset"),ConfigVec("Dagger Right Offset"),ConfigVec("Dagger Left Offset")})EndBullet;
}break;
case SLASH:{
vf2d slashTarget=game->GetPlayer()->GetPos();
m.PerformAnimation("SLASHING",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
CreateBullet(DaggerSlash)(m,ConfigString("Dagger Slash Image"),ConfigFloat("Dagger Hit Radius"),m.GetAttack(),ConfigFloat("Dagger Slash Knockback"),m.OnUpperLevel(),m.GetFacingDirectionToTarget(slashTarget),ConfigFloat("Dagger Frame Duration"),ConfigFloat("Dagger Slash Distance"))EndBullet;
CreateBullet(DaggerSlash)(m,ConfigFloat("Dagger Hit Radius"),m.GetAttack(),ConfigFloat("Dagger Slash Knockback"),m.OnUpperLevel(),m.GetFacingDirectionToTarget(slashTarget),ConfigFloat("Dagger Frame Duration"),ConfigFloat("Dagger Slash Distance"))EndBullet;
}break;
default:ERR(std::format("WARNING! Invalid Attack type {} provided. THIS SHOULD NOT BE HAPPENING!",m.I(A::ATTACK_TYPE)));
}
@ -113,7 +113,7 @@ void Monster::STRATEGY::GOBLIN_DAGGER(Monster&m,float fElapsedTime,std::string s
m.F(A::CASTING_TIMER)-=fElapsedTime;
m.F(A::RECOVERY_TIME)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0){m.PerformIdleAnimation(m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));}
if(m.F(A::RECOVERY_TIME)<=0)SETPHASE(MOVE);
if(m.F(A::RECOVERY_TIME)<=0)m.phase=MOVE;
}break;
}
}

@ -63,11 +63,11 @@ void Monster::STRATEGY::HAWK(Monster&m,float fElapsedTime,std::string strategy){
m.SetZ(std::max(float(m.F(A::FLYING_HEIGHT)+ConfigFloat("Flight Oscillation Amount")*sin((PI*m.TimeSpentAlive())/1.5f)),0.f));
#pragma endregion
switch(PHASE()){
switch(m.phase){
case INITIALIZE:{
m.F(A::TARGET_FLYING_HEIGHT)=m.F(A::FLYING_HEIGHT)=ConfigFloat("Flight Height")-ConfigFloat("Flight Height Variance")+util::random_range(0,ConfigFloat("Flight Height Variance")*2);
m.B(A::RANDOM_DIRECTION)=int(util::random_range(0,2));
SETPHASE(FLYING);
m.phase=FLYING;
m.AddBuff(BuffType::SELF_INFLICTED_SLOWDOWN,INFINITE,util::random_range(0,ConfigFloat("Flight Speed Variance")/100));
m.F(A::ATTACK_COOLDOWN)=util::random_range(1.f,ConfigFloat("Flight Charge Cooldown"));
}break;
@ -76,7 +76,7 @@ void Monster::STRATEGY::HAWK(Monster&m,float fElapsedTime,std::string strategy){
if(m.F(A::FLYING_HEIGHT)<m.F(A::TARGET_FLYING_HEIGHT))m.F(A::FLYING_HEIGHT)=std::min(m.F(A::TARGET_FLYING_HEIGHT),m.F(A::FLYING_HEIGHT)+ConfigFloat("Attack Z Speed")*fElapsedTime);
if(m.F(A::ATTACK_COOLDOWN)<=0){
m.F(A::CASTING_TIMER)=ConfigFloat("Attack Wait Time");
SETPHASE(PREPARE_CHARGE);
m.phase=PREPARE_CHARGE;
}else{
float dirToPlayer{util::pointTo(game->GetPlayer()->GetPos(),m.GetPos()).polar().y};
dirToPlayer+=m.B(A::RANDOM_DIRECTION)?0.25*PI:-0.25*PI;
@ -89,7 +89,7 @@ void Monster::STRATEGY::HAWK(Monster&m,float fElapsedTime,std::string strategy){
m.UpdateFacingDirection(game->GetPlayer()->GetPos());
m.PerformAnimation("ATTACK");
if(m.F(A::CASTING_TIMER)<=0){
SETPHASE(CHARGE);
m.phase=CHARGE;
m.PerformAnimation("ATTACKING");
m.target=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).rpoint(ConfigFloat("Flight Distance")*2.f);
m.UpdateFacingDirection(m.target);
@ -103,7 +103,7 @@ void Monster::STRATEGY::HAWK(Monster&m,float fElapsedTime,std::string strategy){
float distToTarget=geom2d::line<float>(m.GetPos(),m.target).length();
if(distToTarget<12.f){
m.F(A::TARGET_FLYING_HEIGHT)=ConfigFloat("Flight Height")-ConfigFloat("Flight Height Variance")+util::random_range(0,ConfigFloat("Flight Height Variance")*2);
SETPHASE(FLYING);
m.phase=FLYING;
m.F(A::ATTACK_COOLDOWN)=ConfigFloat("Flight Charge Cooldown");
m.PerformJumpAnimation();
}

@ -51,7 +51,7 @@ INCLUDE_WINDOW_SIZE
IBullet::IBullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col,vf2d scale,float image_angle)
:pos(pos),vel(vel),radius(radius),damage(damage),col(col),friendly(friendly),upperLevel(upperLevel),scale(scale),image_angle(image_angle){};
IBullet::IBullet(vf2d pos,vf2d vel,float radius,int damage,const std::string&animation,bool upperLevel,bool hitsMultiple,float lifetime,bool rotatesWithAngle,bool friendly,Pixel col,vf2d scale,float image_angle,std::string_view hitSound)
IBullet::IBullet(vf2d pos,vf2d vel,float radius,int damage,std::string animation,bool upperLevel,bool hitsMultiple,float lifetime,bool rotatesWithAngle,bool friendly,Pixel col,vf2d scale,float image_angle,std::string_view hitSound)
:pos(pos),vel(vel),radius(radius),damage(damage),col(col),animated(true),rotates(rotatesWithAngle),lifetime(lifetime),hitsMultiple(hitsMultiple),friendly(friendly),upperLevel(upperLevel),scale(scale),image_angle(image_angle),hitSound(std::string(hitSound)){
this->animation.AddState(animation,ANIMATION_DATA.at(animation));
this->animation.ChangeState(internal_animState,animation);
@ -88,8 +88,8 @@ void IBullet::_Update(const float fElapsedTime){
UpdateFadeTime(fElapsedTime);
Update(fElapsedTime);
animation.UpdateState(internal_animState,fElapsedTime);
const auto CollisionCheckRequired=[this](){return IsActivated()&&fadeInTimer==fadeInTime&&radius!=0.f;};
if(CollisionCheckRequired()){
const bool CollisionCheckRequired=IsActivated()&&fadeInTimer==fadeInTime&&radius!=0.f;
if(CollisionCheckRequired){
float totalDistance=(vel*fElapsedTime).mag();
int iterations=int(std::max(1.f,(vel*fElapsedTime).mag()));
int totalIterations=iterations;
@ -106,14 +106,13 @@ void IBullet::_Update(const float fElapsedTime){
ModifyOutgoingDamageData(damageData);
uint8_t markStacksBeforeHit{m->GetMarkStacks()};
if(m->Hurt(damageData)){
hitList.insert(&*m);
if(!hitsMultiple){
if(_MonsterHit(*m,markStacksBeforeHit)==BulletDestroyState::DESTROY){
dead=true;
}
return false;
}else _MonsterHit(*m,markStacksBeforeHit);
if(!CollisionCheckRequired())return false;
hitList.insert(&*m);
}
}
}
@ -125,14 +124,13 @@ void IBullet::_Update(const float fElapsedTime){
//NOTE: OnHurt() will potentially change the Damage Data, passing it along to bullet children that might need to modify the flags!
ModifyOutgoingDamageData(damageData);
if(game->GetPlayer()->Hurt(damageData)){
hitList.insert(game->GetPlayer());
if(!hitsMultiple){
if(_PlayerHit(&*game->GetPlayer())==BulletDestroyState::DESTROY){
dead=true;
}
return false;
}else _PlayerHit(&*game->GetPlayer());
if(!CollisionCheckRequired())return false;
hitList.insert(game->GetPlayer());
}
}
}
@ -173,8 +171,8 @@ void IBullet::_Draw()const{
Pixel blendCol=col;
if(fadeInTime==0&&fadeOutTime==0)blendCol.a=col.a;
else if(fadeOutTime>0)blendCol.a=uint8_t(util::lerp(col.a,uint8_t(0),1-((fadeOutTime-fadeOutTimer)/fadeOutTime)));
else if(fadeInTime>0)blendCol.a=uint8_t(util::lerp(col.a,uint8_t(0),((fadeInTime-fadeInTimer)/fadeInTime)));
else if(fadeOutTime>0)blendCol.a=uint8_t(util::lerp(col.a,0,1-((fadeOutTime-fadeOutTimer)/fadeOutTime)));
else if(fadeInTime>0)blendCol.a=uint8_t(util::lerp(col.a,0,((fadeInTime-fadeInTimer)/fadeInTime)));
if(GetZ()>0){
vf2d shadowScale=vf2d{8*scale.x/3.f,1}/std::max(1.f,GetZ()/8);
@ -276,10 +274,6 @@ void IBullet::Deactivate(){
deactivated=true;
}
void IBullet::Activate(){
deactivated=false;
}
const double IBullet::GetTimeAlive()const{
return aliveTime;
}

@ -42,11 +42,6 @@ All rights reserved.
#include "DEFINES.h"
using HitList=std::unordered_set<std::variant<Monster*,Player*>>;
enum class HurtType{
PLAYER=0b01,
MONSTER=0b10,
};
enum class BulletType{
UNDEFINED,
FEATHER,
@ -81,7 +76,6 @@ protected:
float fadeInTime=0; //Setting the fade in time causes the bullet to be disabled and the bullet's alpha will fade in from zero to the actual alpha of the bullet. When the fade in timer reaches the fade in time automatically, the bullet will be enabled.
virtual void Update(float fElapsedTime);
void Deactivate();
void Activate();
private:
float fadeOutTimer=0;
float fadeInTimer=0;
@ -110,7 +104,7 @@ public:
virtual ~IBullet()=default;
IBullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f);
//Initializes a bullet with an animation.
IBullet(vf2d pos,vf2d vel,float radius,int damage,const std::string&animation,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool rotatesWithAngle=false,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f,std::string_view hitSound="");
IBullet(vf2d pos,vf2d vel,float radius,int damage,std::string animation,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool rotatesWithAngle=false,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f,std::string_view hitSound="");
public:
void SimulateUpdate(const float fElapsedTime);

@ -49,14 +49,9 @@ IT::IT(std::string name)
itemName=ITEM_CONVERSIONS.at(name); //Convert the item if it's using an old name.
}
}
IT::IT(std::string_view name)
:IT(std::string(name)){}
IT::operator std::string(){
return itemName;
}
IT::operator std::string_view(){
return itemName;
}
std::ostream&operator<<(std::ostream&rhs,IT&item){
rhs<<item.itemName;
return rhs;

@ -49,9 +49,7 @@ class IT{
public:
IT();
IT(std::string name);
IT(std::string_view name);
operator std::string();
operator std::string_view();
const bool operator==(const std::shared_ptr<Item>&rhs);
const bool operator==(const std::weak_ptr<Item>&rhs);
friend std::ostream&operator<<(std::ostream&rhs,IT&item);

@ -80,7 +80,7 @@ std::vector<std::string>ItemSortRules::secondarySort;
Stats Stats::NO_MAX_HIGHLIGHT;
ItemInfo::ItemInfo()
:customProps({nullptr,nullptr}){}
:customProps({nullptr,nullptr}),img(nullptr){}
void ItemInfo::InitializeItems(){
Stats::NO_MAX_HIGHLIGHT={};
@ -142,8 +142,6 @@ void ItemInfo::InitializeItems(){
std::string scriptName="",description="",category="";
std::string setName="";
float castTime=0;
float castRange=0;
float castSize=0;
std::vector<std::string> slot;
float cooldownTime="Item.Item Cooldown Time"_F;
std::vector<ItemAttribute>statValueList;
@ -155,7 +153,6 @@ void ItemInfo::InitializeItems(){
std::unordered_set<std::string>equippableClass;
EventName useSound;
std::optional<std::string>fragmentName;
std::optional<std::string>fragmentIcon;
for(auto&[itemKey,itemValue]:data[key].GetKeys()){
std::string keyName=itemKey;
if(keyName=="Description"){
@ -169,12 +166,6 @@ void ItemInfo::InitializeItems(){
}else
if(keyName=="Cast Time"){
castTime=float(data[key][keyName].GetReal());
}
if(keyName=="Cast Range"){
castRange=float(data[key][keyName].GetReal());
}else
if(keyName=="Cast Size"){
castSize=float(data[key][keyName].GetReal())/100.f*24;
}else
if(keyName=="Cooldown Time"){
cooldownTime=float(data[key][keyName].GetReal());
@ -213,9 +204,6 @@ void ItemInfo::InitializeItems(){
}else
if(keyName.starts_with("Fragment Name")){
fragmentName=data[key][keyName].GetString();
}else
if(keyName.starts_with("Fragment Icon")){
fragmentIcon=data[key][keyName].GetString();
}else{ //THis is a custom override modifier for a script. NO-OP
}
}
@ -310,8 +298,6 @@ void ItemInfo::InitializeItems(){
it.description=description;
it.category=category;
it.castTime=castTime;
it.castRange=castRange;
it.castSize=castSize;
it.cooldownTime=cooldownTime;
it.slot=EquipSlot::NONE;
it.set=setName;
@ -330,7 +316,7 @@ void ItemInfo::InitializeItems(){
ERR("WARNING! Tried to add item "<<it.name<<" to category "<<it.category<<" which does not exist!")
}
ITEM_CATEGORIES.at(it.category).insert(it.name);
it.img=img;
it.img=img.Decal();
ItemProps&props=it.customProps;
if(scriptName!=""){
props.scriptProps=&DATA["ItemScript"][scriptName];
@ -340,7 +326,6 @@ void ItemInfo::InitializeItems(){
it.minStats=minStats;
it.maxStats=maxStats;
it.fragmentName=fragmentName;
it.fragmentIcon=fragmentIcon;
#pragma region Equipment Category Verification Tests
int equipmentCategories=0;
@ -381,28 +366,19 @@ void ItemInfo::InitializeItems(){
for(const auto&itemName:ITEM_CATEGORIES.at("Accessories")){
const std::string fragmentName{ITEM_DATA.at(itemName).fragmentName.value()};
ItemInfo&it{ITEM_DATA[fragmentName]};
GFX["item_img_directory"_S+fragmentName].Create(24,24);
it.img=GFX.at("item_img_directory"_S+fragmentName);
GFX[fragmentName].Create(24,24);
if(!game->TestingModeEnabled()){
if(it.FragmentIcon()){
game->SetDrawTarget(GFX.at("item_img_directory"_S+fragmentName).Sprite());
game->Clear(BLANK);
game->SetPixelMode(Pixel::ALPHA);
game->DrawSprite({},GFX.at(it.FragmentIcon().value()).Sprite());
game->SetDrawTarget(nullptr);
GFX.at("item_img_directory"_S+fragmentName).Decal()->Update();
}else{
#pragma region Collect colors from source ring image
std::set<Pixel>colors;
for(int y=0;y<24;y++){
for(int x=0;x<24;x++){
colors.insert(ITEM_DATA.at(itemName).Icon().Sprite()->GetPixel(x,y));
}
#pragma region Collect colors from source ring image
std::set<Pixel>colors;
for(int y=0;y<24;y++){
for(int x=0;x<24;x++){
colors.insert(ITEM_DATA.at(itemName).img->sprite->GetPixel(x,y));
}
colors.erase(BLANK);
#pragma endregion
#pragma region Generate fragment with randomly sampled pixels from the source ring image
game->SetDrawTarget(GFX.at("item_img_directory"_S+fragmentName).Sprite());
}
colors.erase(BLANK);
#pragma endregion
#pragma region Generate fragment with randomly sampled pixels from the source ring image
game->SetDrawTarget(GFX.at(fragmentName).Sprite());
game->DrawSprite({},GFX.at("items/Fragment.png").Sprite(),1U,0U,[colors](const Pixel&in){
if(in==BLANK)return in;
for(const Pixel&p:colors){
@ -411,12 +387,11 @@ void ItemInfo::InitializeItems(){
return in;
});
game->SetDrawTarget(nullptr);
GFX.at("item_img_directory"_S+fragmentName).Decal()->Update();
GFX.at(fragmentName).Decal()->Update();
#pragma endregion
}
}
it.img=GFX.at(fragmentName).Decal();
it.name=fragmentName;
it.fragmentIcon="item_img_directory"_S+fragmentName;
it.description="Fragment Description"_S;
it.category="Materials";
LOG(std::format("Item Fragment {} generated...",fragmentName));
@ -437,15 +412,15 @@ void ItemInfo::InitializeItems(){
ItemProps::ItemProps(utils::datafile*scriptProps,utils::datafile*customProps)
:scriptProps(scriptProps),customProps(customProps){}
int ItemProps::GetInt(const std::string&prop,size_t index)const{
int ItemProps::GetIntProp(const std::string&prop,size_t index)const{
if(customProps->HasProperty(prop)) return (*customProps)[prop].GetInt(index);
else return (*scriptProps)[prop].GetInt(index);
};
float ItemProps::GetFloat(const std::string&prop,size_t index)const{
float ItemProps::GetFloatProp(const std::string&prop,size_t index)const{
if(customProps->HasProperty(prop)) return float((*customProps)[prop].GetReal(index));
else return float((*scriptProps)[prop].GetReal(index));
};
std::string ItemProps::GetString(const std::string&prop,size_t index)const{
std::string ItemProps::GetStringProp(const std::string&prop,size_t index)const{
if(customProps->HasProperty(prop)) return (*customProps)[prop].GetString(index);
else return (*scriptProps)[prop].GetString(index);
};
@ -462,6 +437,51 @@ const std::unordered_map<std::string,BuffOverTimeType::BuffOverTimeType>ItemInfo
{"MP % Restore",BuffOverTimeType::MP_PCT_RESTORATION},
};
void ItemInfo::InitializeScripts(){
ITEM_SCRIPTS["Restore"]=[](AiL*game,ItemProps props){
for(const auto&[propName,buffType]:NameToBuffType){
int restoreAmt=props.GetIntProp(propName);
game->GetPlayer()->AddBuff(BuffRestorationType::ONE_OFF,NameToBuffType.at(propName),0.01f,float(restoreAmt),0.0f);
if(restoreAmt>0&&props.PropCount(propName)==3){
game->GetPlayer()->AddBuff(BuffRestorationType::OVER_TIME,NameToBuffType.at(propName),props.GetFloatProp(propName,2),float(restoreAmt),props.GetFloatProp(propName,1));
}
}
return true;
};
for(auto&[key,value]:ItemAttribute::attributes){
if(!DATA.GetProperty("ItemScript.Buff").HasProperty(key)){
ERR("WARNING! Buff Item Script does not support Buff "<<std::quoted(value.Name())<<"!");
}
}
ITEM_SCRIPTS["Buff"]=[](AiL*game,ItemProps props){
for(auto&[key,value]:ItemAttribute::attributes){
float intensity=props.GetFloatProp(key,0);
if(intensity==0.f)continue;
if(ItemAttribute::Get(key).DisplayAsPercent())intensity/=100;
game->GetPlayer()->AddBuff(BuffType::STAT_UP,props.GetFloatProp(key,1),intensity,{ItemAttribute::Get(key)});
}
return true;
};
ITEM_SCRIPTS["RestoreDuringCast"]=[](AiL*game,ItemProps props){
for(const auto&[propName,buffType]:NameToBuffType){
int restoreAmt=props.GetIntProp(propName);
game->GetPlayer()->AddBuff(BuffRestorationType::ONE_OFF,NameToBuffType.at(propName),0.01f,float(restoreAmt),0.0f);
if(restoreAmt>0&&props.PropCount(propName)==3){
game->GetPlayer()->AddBuff(BuffRestorationType::OVER_TIME_DURING_CAST,NameToBuffType.at(propName),props.GetFloatProp(propName,2),float(restoreAmt),props.GetFloatProp(propName,1));
}
}
return true;
};
ITEM_SCRIPTS.SetInitialized();
LOG(ITEM_SCRIPTS.size()<<" item scripts have been loaded.");
}
Item::Item()
:amt(0),it(nullptr),enhancementLevel(0){}
@ -526,11 +546,11 @@ uint32_t Inventory::GetItemCount(IT it){
}
//Returns true if the item has been consumed completely and there are 0 remaining of that type in our inventory.
bool Inventory::UseItem(IT it,uint32_t amt,const std::optional<vf2d>targetingPos){
bool Inventory::UseItem(IT it,uint32_t amt){
if(!_inventory.count(it))return false;
//There are two places to manipulate items in (Both the sorted inventory and the actual inventory)
for(uint32_t i=0;i<amt;i++){
if(ExecuteAction(it,targetingPos)){
if(ExecuteAction(it)){
return RemoveItem(GetItem(it)[0]);
}
}
@ -646,9 +666,9 @@ void Inventory::InsertIntoStageInventoryCategory(std::shared_ptr<Item>itemRef,co
Menu::InventorySlotsUpdated(stageInventoryCategory);
}
bool Inventory::ExecuteAction(IT item,const std::optional<vf2d>targetingPos){
bool Inventory::ExecuteAction(IT item){
if(ITEM_SCRIPTS.count(ITEM_DATA.at(item).useFunc)){
return ITEM_SCRIPTS.at(ITEM_DATA.at(item).useFunc)(game,targetingPos,ITEM_DATA[item].customProps);
return ITEM_SCRIPTS.at(ITEM_DATA.at(item).useFunc)(game,ITEM_DATA[item].customProps);
}else{
return false;
}
@ -756,8 +776,8 @@ const std::string Item::Description(CompactText compact)const{
const ITCategory Item::Category()const{
return it->Category();
}
const::Renderable&Item::Icon()const{
return it->Icon();
const::Decal*const Item::Decal()const{
return it->Decal();
}
const ItemScript&Item::OnUseAction()const{
return it->OnUseAction();
@ -772,8 +792,8 @@ const std::string&ItemInfo::Description()const{
const ITCategory ItemInfo::Category()const{
return category;
}
const::Renderable&ItemInfo::Icon()const{
return img.value().get();
const::Decal*const ItemInfo::Decal()const{
return img;
}
const ItemScript&ItemInfo::OnUseAction()const{
return ITEM_SCRIPTS.at(useFunc);
@ -820,7 +840,7 @@ void ItemOverlay::Draw(){
Pixel lightCol=Menu::GetCurrentTheme().GetButtonCol()*1.2f;
game->GradientFillRectDecal(pos,{item.width,8},darkCol,darkCol,darkCol,lightCol);
game->DrawRectDecal(pos,{item.width,8},Menu::GetCurrentTheme().GetHighlightCol());
game->DrawDecal(pos,const_cast<Decal*>(item.it.Icon().Decal()),{itemScale,itemScale});
game->DrawDecal(pos,const_cast<Decal*>(item.it.Decal()),{itemScale,itemScale});
game->DrawShadowStringPropDecal(pos+vf2d{itemScale*24+2,2},item.it.Name(),WHITE,BLACK,{0.5f,0.7f});
counter++;
}
@ -1000,7 +1020,7 @@ void Item::EnhanceItem(uint8_t qty){
for(const auto&[name,amt]:consumedResources.GetItems()){
Inventory::RemoveItem(Inventory::GetItem(name)[0],amt);
}
game->GetPlayer()->RemoveMoney(consumedResources.GetCost());
game->GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()-consumedResources.GetCost());
SoundEffect::PlaySFX("Enhance Item",SoundEffect::CENTERED);
@ -1013,7 +1033,7 @@ void Item::EnhanceItem(uint8_t qty){
for(const auto&[name,amt]:consumedResources.GetItems()){
Inventory::RemoveItem(Inventory::GetItem(name)[0],amt);
}
game->GetPlayer()->RemoveMoney(consumedResources.GetCost());
game->GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()-consumedResources.GetCost());
}
}
SoundEffect::PlaySFX("Craft Item",SoundEffect::CENTERED);
@ -1053,7 +1073,8 @@ const std::string Stats::GetStatsString(const Stats&maxStats,CompactText compact
std::string col="";
if(maxStats.attributes.count(attr)&&int(val)>=int(maxStats.attributes.at(attr))){
col=Stats::GetShimmeringColor().toHTMLColorCode();
Pixel shimmeringCol=PixelLerp({255,196,60},{254,217,133},sin((70*game->GetRunTime())/2.f+0.5f));
col=shimmeringCol.toHTMLColorCode();
}
description+=col+std::string(attr.Name())+": "+statNumber+(attr.DisplayAsPercent()?"%":"")+"#FFFFFF";
@ -1379,14 +1400,12 @@ const EquipSlot ItemInfo::StringToEquipSlot(std::string_view slotName){
return nameToEquipSlot[std::string(slotName)];
}
const Inventory::DisassembleResult Inventory::Disassemble(std::weak_ptr<Item>itemRef){
void Inventory::Disassemble(std::weak_ptr<Item>itemRef){
if(ISBLANK(itemRef))ERR(std::format("WARNING! Trying to feed a blank item into the Disassemble function! THIS SHOULD NOT BE HAPPENING!"));
const std::shared_ptr<Item>&currentItem{itemRef.lock()};
if(!currentItem->IsAccessory())ERR(std::format("WARNING! Trying to disassemble Item {} which is not an accessory! THIS SHOULD NOT BE HAPPENING!",currentItem->ActualName()));
if(currentItem->IsLocked())return DisassembleResult::FAILED;
Inventory::RemoveItem(itemRef);
Inventory::AddItem(currentItem->FragmentName(),"Fragment Disassemble Gain Amount"_I);
return DisassembleResult::SUCCESS;
}
const std::string&Item::FragmentName()const{
@ -1407,14 +1426,10 @@ const bool Item::CanBeRefined()const{
return false; //Maxed out, so cannot be refined.
}
const bool Item::CanBeEnchanted()const{
return IsAccessory()&&Inventory::GetItemCount(FragmentName())>="Fragment Enchant Cost"_i[0]&&game->GetPlayer()->GetMoney()>="Fragment Enchant Cost"_i[1];
}
RefineResult Item::Refine(){
if(!CanBeRefined())ERR("WARNING! Trying to refine an item that cannot be refined! Make sure you are checking with CanBeRefined() before calling this function!");
Inventory::RemoveItem(FragmentName(),"Fragment Refine Cost"_i[0]);
game->GetPlayer()->RemoveMoney("Fragment Refine Cost"_i[1]);
game->GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()-"Fragment Refine Cost"_i[1]);
std::vector<ItemAttribute>statsAvailableForRefining;
for(const auto&[attr,val]:randomizedStats){
float maxVal{ITEM_DATA[ActualName()].GetMaxStats().A_Read(attr)};
@ -1427,17 +1442,16 @@ RefineResult Item::Refine(){
const float oldAmt{randomizedStats.A_Read(chosenAttr)};
randomizedStats.A(chosenAttr)=std::min(maxStat,oldAmt+increaseAmt);
const float newAmt{randomizedStats.A_Read(chosenAttr)};
game->GetPlayer()->RecalculateEquipStats();
return RefineResult{chosenAttr,newAmt-oldAmt};
}
void Item::_EnchantItem(const ItemEnchant&newEnchant){
enchant=newEnchant;
void Item::EnchantItem(const std::string_view enchantName){
enchant=ItemEnchant{enchantName};
game->GetPlayer()->RecalculateEquipStats();
}
const std::optional<ItemEnchant>&Item::GetEnchant()const{
std::optional<ItemEnchant>Item::GetEnchant()const{
return enchant;
}
const bool Item::HasEnchant()const{
@ -1447,77 +1461,4 @@ const Pixel Item::GetDisplayNameColor()const{
Pixel col{WHITE};
if(HasEnchant())col=enchant.value().DisplayCol();
return col;
}
const bool Item::SelectedEquipIsDifferent(const std::weak_ptr<Item>equipAttemptingToEquip,const EquipSlot slotToEquipTo){
std::weak_ptr<Item>currentItem=Inventory::GetEquip(slotToEquipTo);
switch(slotToEquipTo){
case EquipSlot::RING1:
case EquipSlot::RING2:{
std::weak_ptr<Item>otherItem;
if(slotToEquipTo&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
else
if(slotToEquipTo&EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1);
const auto EquipIsNotSameAsOppositeRingSlot=[&equip=equipAttemptingToEquip,&otherItem](){
return &*equip.lock()!=&*otherItem.lock();
};
const auto EquipIsNotSameAsCurrentRingSlot=[&equip=equipAttemptingToEquip,&currentItem](){
return &*equip.lock()!=&*currentItem.lock();
};
const auto OneOfBothRingsDoesNotHaveEnchant=[&equip=equipAttemptingToEquip,&otherItem](){
return !equip.lock()->HasEnchant()||!otherItem.lock()->HasEnchant();
};
const auto BothRingsAreCompatible=[&equip=equipAttemptingToEquip,&otherItem](){ //Check if enchants are compatible... Currently unused.
return true;
};
return (ISBLANK(otherItem)&&(OneOfBothRingsDoesNotHaveEnchant()||BothRingsAreCompatible())||
EquipIsNotSameAsOppositeRingSlot()&&
EquipIsNotSameAsCurrentRingSlot()&&
(OneOfBothRingsDoesNotHaveEnchant()||BothRingsAreCompatible()));
}break;
default:{
return &*equipAttemptingToEquip.lock()!=&*currentItem.lock();
}
}
}
const std::optional<std::string>&ItemInfo::FragmentIcon()const{
return fragmentIcon;
}
const Pixel Stats::GetShimmeringColor(){
return PixelLerp({255,196,60},{254,217,133},sin((70*game->GetRunTime())/2.f+0.5f));
}
const std::optional<std::string>&Item::FragmentIcon()const{
return ITEM_DATA.at(FragmentName()).FragmentIcon();
}
const ItemEnchant&Item::ApplyRandomEnchant(){
if(!CanBeEnchanted())ERR("WARNING! Trying to enchant an item that cannot be enchanted! Make sure you are checking with CanBeEnchanted() before calling this function!");
ItemEnchant randomEnchant{ItemEnchant::RollRandomEnchant(GetEnchant())};
_EnchantItem(randomEnchant);
Inventory::RemoveItem(FragmentName(),"Fragment Enchant Cost"_i[0]);
game->GetPlayer()->RemoveMoney("Fragment Enchant Cost"_i[1]);
return GetEnchant().value();
}
void Item::RemoveEnchant(){
enchant.reset();
}
const float Item::CastRange()const{
return ITEM_DATA.at(ActualName()).CastRange();
}
const float ItemInfo::CastRange()const{
return castRange;
}
const float Item::CastSize()const{
return ITEM_DATA.at(ActualName()).CastSize();
}
const float ItemInfo::CastSize()const{
return castSize;
}

@ -57,9 +57,8 @@ class ItemProps;
using ITCategory=std::string;
using EventName=std::string;
using EnchantName=std::string;
using ItemScript=std::function<bool(AiL*game,std::optional<vf2d>targetingPos,ItemProps props)>;
using ItemScript=std::function<bool(AiL*,ItemProps)>;
enum class EquipSlot{
HELMET= 0b0000'0001,
@ -112,7 +111,6 @@ public:
}
const std::string GetStatsString(const Stats&maxStats,CompactText compact=NON_COMPACT)const;
friend const bool operator==(const Stats&lhs,const Stats&rhs){return lhs.attributes==rhs.attributes;}
static const Pixel GetShimmeringColor();
};
struct Stats;
@ -213,12 +211,10 @@ public:
const EventName&UseSound()const;
const ITCategory Category()const;
const EquipSlot GetEquipSlot()const;
const::Renderable&Icon()const;
const::Decal*const Decal()const;
const Stats GetStats()const;
const ItemScript&OnUseAction()const;
const float CastTime()const;
const float CastRange()const;
const float CastSize()const;
const bool UseDuringCast()const; //When true, the item is activated during the cast instead of after the cast.
const float CooldownTime()const;
//Use ISBLANK macro instead!! This should not be called directly!!
@ -256,16 +252,10 @@ public:
//Assumes an item can be refined. CHECK WITH CanBeRefined() First!!!
//Refines a random stat by the parameters defined in the Accessories.txt configuration file. Also takes away the costs required to refine the item.
RefineResult Refine();
const bool CanBeEnchanted()const;
void Lock();
void Unlock();
void RemoveEnchant();
void _EnchantItem(const ItemEnchant&newEnchant);
//Applies a random valid enchant to an item and consumes the materials in the process.
//Assumes an item can be enchanted. CHECK WITH CanBeEnchanted() First!!!
//Enchants the item based on its class parameters defined in the ItemEnchants.txt configuration file. Also takes away the costs required to refine the item (Located in Accessories.txt).
const ItemEnchant&ApplyRandomEnchant();
const std::optional<ItemEnchant>&GetEnchant()const;
void EnchantItem(const std::string_view enchantName);
std::optional<ItemEnchant>GetEnchant()const;
const bool HasEnchant()const;
friend const bool operator==(std::shared_ptr<Item>lhs,std::shared_ptr<Item>rhs){return lhs->it==rhs->it&&lhs->randomizedStats==rhs->randomizedStats;};
friend const bool operator==(std::shared_ptr<Item>lhs,const IT&rhs){return lhs->ActualName()==const_cast<IT&>(rhs);};
@ -274,8 +264,6 @@ public:
friend const bool operator==(const IT&lhs,std::weak_ptr<Item>rhs){return operator==(rhs,lhs);};
friend const bool operator==(const IT&lhs,std::shared_ptr<Item>rhs){return operator==(rhs,lhs);};
const Pixel GetDisplayNameColor()const;
static const bool SelectedEquipIsDifferent(const std::weak_ptr<Item>equipAttemptingToEquip,const EquipSlot slotToEquipTo);
const std::optional<std::string>&FragmentIcon()const;
};
class Inventory{
@ -285,19 +273,13 @@ class Inventory{
friend class InventoryCreator;
friend class ItemTests::ItemTest;
public:
enum class DisassembleResult{
SUCCESS,
FAILED, //Failure is due to an item being locked.
};
static std::weak_ptr<Item>AddItem(IT it,uint32_t amt=1,bool monsterDrop=false);
//Returns the actual amount available in your main inventory.
[[nodiscard]] static uint32_t GetItemCount(IT it);
static std::vector<std::shared_ptr<Item>>CopyItem(IT it);
static std::vector<std::weak_ptr<Item>>GetItem(IT it);
//Auto-executes its use function and removes the amt specified from the inventory. Multiple amounts will cause the item to execute its useFunc multiple times.
static bool UseItem(IT it,uint32_t amt=1,const std::optional<vf2d>targetingPos={});
static bool UseItem(IT it,uint32_t amt=1);
//Returns true if the item has been consumed completely and there are 0 remaining of that type in our inventory.
static bool RemoveItem(std::weak_ptr<Item>itemRef,ITCategory inventory,uint32_t amt = 1);
//Returns true if the item has been consumed completely and there are 0 remaining of that type in our inventory.
@ -319,7 +301,7 @@ public:
static void ResetLoadoutItemsUsed();
static void GivePlayerLoadoutItemsUsed();
static const bool EquipsFullyMaxedOut(int maxWeaponLevel=10,int maxArmorLevel=10);
[[nodiscard]]static const Inventory::DisassembleResult Disassemble(std::weak_ptr<Item>itemRef);
static void Disassemble(std::weak_ptr<Item>itemRef);
static bool SwapItems(ITCategory itemCategory,uint32_t slot1,uint32_t slot2);
//Makes sure this is a valid category. Will error out if it doesn't exist! Use for ERROR HANDLING!
@ -330,7 +312,7 @@ public:
private:
static void InsertIntoSortedInv(std::shared_ptr<Item>itemRef);
static void InsertIntoStageInventoryCategory(std::shared_ptr<Item>itemRef,const bool monsterDrop);
static bool ExecuteAction(IT item,const std::optional<vf2d>targetingPos);
static bool ExecuteAction(IT item);
static std::multimap<IT,std::shared_ptr<Item>>_inventory;
static std::vector<std::shared_ptr<Item>>blacksmithInventory;
static std::map<EquipSlot,std::weak_ptr<Item>>equipment;
@ -345,9 +327,9 @@ class ItemProps{
utils::datafile*customProps;
public:
ItemProps(utils::datafile*scriptProps,utils::datafile*customProps);
int GetInt(const std::string&prop,size_t index=0)const;
float GetFloat(const std::string&prop,size_t index=0)const;
std::string GetString(const std::string&prop,size_t index=0)const;
int GetIntProp(const std::string&prop,size_t index=0)const;
float GetFloatProp(const std::string&prop,size_t index=0)const;
std::string GetStringProp(const std::string&prop,size_t index=0)const;
const uint32_t PropCount(const std::string&prop)const;
};
@ -358,12 +340,10 @@ class ItemInfo{
std::string description;
std::string category;
float castTime=0;
float castRange=0;
float castSize=0;
float cooldownTime=0;
EquipSlot slot=EquipSlot::NONE;
EnhancementInfo enhancement;
std::optional<std::reference_wrapper<Renderable>>img;
::Decal*img;
EventName useSound="";
std::string set="";
//Returns true if the item can be used, false otherwise
@ -380,7 +360,6 @@ class ItemInfo{
Stats minStats;
Stats maxStats;
std::optional<std::string>fragmentName;
std::optional<std::string>fragmentIcon;
private:
static void InitializeScripts();
static void InitializeSets();
@ -393,7 +372,7 @@ public:
const std::string&Name()const;
const std::string&Description()const;
const ITCategory Category()const;
const::Renderable& Icon()const;
const::Decal*const Decal()const;
static const EquipSlot StringToEquipSlot(std::string_view slotName);
/*
For the useFunc, return true if the item can be used, false otherwise.
@ -402,8 +381,6 @@ public:
const ItemScript&OnUseAction()const;
const Stats GetStats(int enhancementLevel)const;
const float CastTime()const;
const float CastRange()const;
const float CastSize()const;
const float CooldownTime()const;
const EquipSlot Slot()const;
const std::optional<const::ItemSet*const>ItemSet()const;
@ -425,7 +402,6 @@ public:
const std::unordered_set<std::string>&GetClass()const;
Stats RandomizeStats();
const std::string&FragmentName()const;
const std::optional<std::string>&FragmentIcon()const;
};
class ItemOverlay{
@ -443,4 +419,4 @@ public:
void ResetTimer();
};
#define ISBLANK(itemRef) Item::IsBlank(itemRef)
#define ISBLANK(itemRef) Item::IsBlank(itemRef)

@ -47,13 +47,12 @@ float ItemDrop::gravity;
std::vector<ItemDrop>ItemDrop::drops;
void ItemDrop::Initialize(){
drops.clear();
gravity="ItemDrop.Item Drop Gravity"_F;
}
ItemDrop::ItemDrop(const ItemInfo*item,const vf2d pos,const bool isUpper)
:item(item),pos(pos),upperLevel(isUpper){
const bool HasBossArenaBounds=game->GetCurrentMap().GetMapType()==Map::MapType::BOSS;
const bool HasBossArenaBounds=game->GetCurrentMap().GetMapType()=="Boss";
if(HasBossArenaBounds){
const geom2d::rect<int>arenaBounds=game->GetZones().at("BossArena")[0].zone;
this->pos.x=std::clamp(this->pos.x,float(arenaBounds.pos.x),float(arenaBounds.pos.x+arenaBounds.size.x));
@ -86,9 +85,9 @@ void ItemDrop::Draw()const{
yOffset=sin((game->levelTime+randomSpinOffset)*3)*0.5f;
}
game->view.DrawRotatedDecal(pos-vf2d{0,GetZ()+yOffset},GFX["skill_overlay_icon_overlay.png"].Decal(),0,GFX["skill_overlay_icon_overlay.png"].Decal()->sprite->Size()/2,{"ItemDrop.Item Drop Scale"_F,"ItemDrop.Item Drop Scale"_F},YELLOW);
game->view.DrawRotatedDecal(pos-vf2d{0,GetZ()+yOffset},const_cast<Decal*>(item->Icon().Decal()),0,item->Icon().Sprite()->Size()/2,{"ItemDrop.Item Drop Scale"_F,"ItemDrop.Item Drop Scale"_F},{255,255,255,128});
game->view.DrawRotatedDecal(pos-vf2d{0,GetZ()+yOffset},const_cast<Decal*>(item->Decal()),0,item->Decal()->sprite->Size()/2,{"ItemDrop.Item Drop Scale"_F,"ItemDrop.Item Drop Scale"_F},{255,255,255,128});
game->SetDecalMode(DecalMode::ADDITIVE);
game->view.DrawRotatedDecal(pos-vf2d{0,GetZ()+yOffset},const_cast<Decal*>(item->Icon().Decal()),0,item->Icon().Sprite()->Size()/2,{"ItemDrop.Item Drop Scale"_F,"ItemDrop.Item Drop Scale"_F},{uint8_t(abs(sin(game->levelTime*1.5)*255.f)),uint8_t(abs(sin(game->levelTime*1.5)*255.f)),uint8_t(abs(sin(game->levelTime*1.5)*255.f)),128});
game->view.DrawRotatedDecal(pos-vf2d{0,GetZ()+yOffset},const_cast<Decal*>(item->Decal()),0,item->Decal()->sprite->Size()/2,{"ItemDrop.Item Drop Scale"_F,"ItemDrop.Item Drop Scale"_F},{uint8_t(abs(sin(game->levelTime*1.5)*255.f)),uint8_t(abs(sin(game->levelTime*1.5)*255.f)),uint8_t(abs(sin(game->levelTime*1.5)*255.f)),128});
game->SetDecalMode(DecalMode::NORMAL);
}
@ -110,7 +109,7 @@ void ItemDrop::UpdateDrops(float fElapsedTime){
if(drop.zSpeed==0&&drop.OnUpperLevel()==game->GetPlayer()->OnUpperLevel()){
geom2d::line<float>lineTo=geom2d::line<float>(drop.pos,game->GetPlayer()->GetPos());
float dist=lineTo.length();
if(dist<="ItemDrop.Item Drop Suction Range"_F&&dist>0){
if(dist<="ItemDrop.Item Drop Suction Range"_F){
vf2d pointVel=lineTo.vector().norm();
float moveDistance=(1.f/std::min(48.f,dist))*"ItemDrop.Item Drop Suction Strength"_F*fElapsedTime;
if(moveDistance>dist){

@ -52,7 +52,6 @@ INCLUDE_DATA
const Pixel ItemEnchantInfo::enchantAttributeCol{0x00DFE2};
std::unordered_map<std::string,ItemEnchantInfo>ItemEnchantInfo::ENCHANT_LIST;
std::unordered_map<ItemEnchantInfo::ItemEnchantCategory,ItemEnchantInfo::ItemEnchantCategoryData>ItemEnchantInfo::ENCHANT_CATEGORIES;
std::unordered_map<ItemEnchantInfo::ItemEnchantCategory,Pixel>ItemEnchantInfo::enchantTextDisplayCol;
const ItemEnchantInfo&operator ""_ENC(const char*key,std::size_t len){
return ItemEnchantInfo::GetEnchant(std::string(key));
@ -80,8 +79,6 @@ void ItemEnchantInfo::Initialize(){
enchantCategory=ItemEnchantCategory::UNIQUE;
}else ERR(std::format("WARNING! Enchant type {} is not supported! THIS SHOULD NOT BE HAPPENING! Please check ItemEnchants.txt",key));
enchantTextDisplayCol[enchantCategory]=DATA["Item Enchants"][key]["Enchant Display Color"].GetPixel();
datafile&enchantData=DATA["Item Enchants"][key];
ItemEnchantCategoryData categoryData;
@ -229,7 +226,7 @@ const std::unordered_map<std::string,ItemEnchantInfo>&ItemEnchantInfo::GetEnchan
return ENCHANT_LIST;
}
const std::vector<ItemEnchantInfo>ItemEnchant::GetAvailableEnchants(){
const std::string ItemEnchant::RollRandomEnchant(){
std::vector<ItemEnchantInfo>filteredEnchants;
std::for_each(ItemEnchantInfo::GetEnchants().begin(),ItemEnchantInfo::GetEnchants().end(),[&filteredEnchants](const std::pair<std::string,ItemEnchantInfo>&data){
@ -243,41 +240,7 @@ const std::vector<ItemEnchantInfo>ItemEnchant::GetAvailableEnchants(){
}
});
return filteredEnchants;
}
const ItemEnchant ItemEnchant::RollRandomEnchant(const std::optional<ItemEnchant>previousEnchant){
const std::vector<ItemEnchantInfo>filteredEnchants{GetAvailableEnchants()};
int randomRoll{int(util::random_range(0,100))};
const std::pair<int,int>generalEnchantRange{0,"Item Enchants.General Enchants.Percent Chance"_I};
const std::pair<int,int>classEnchantRange{generalEnchantRange.second,generalEnchantRange.second+"Item Enchants.Class Enchants.Percent Chance"_I};
const std::pair<int,int>uniqueEnchantRange{classEnchantRange.second,classEnchantRange.second+"Item Enchants.Unique Enchants.Percent Chance"_I};
std::vector<ItemEnchantInfo>remainingAvailableEnchants;
if(randomRoll>=generalEnchantRange.first&&randomRoll<generalEnchantRange.second){
std::copy_if(filteredEnchants.begin(),filteredEnchants.end(),std::back_inserter(remainingAvailableEnchants),[](const ItemEnchantInfo&enchant){return enchant.Category()==ItemEnchantInfo::ItemEnchantCategory::GENERAL;});
}else if(randomRoll>=classEnchantRange.first&&randomRoll<classEnchantRange.second){
std::copy_if(filteredEnchants.begin(),filteredEnchants.end(),std::back_inserter(remainingAvailableEnchants),[](const ItemEnchantInfo&enchant){return enchant.Category()==ItemEnchantInfo::ItemEnchantCategory::CLASS;});
}else if(randomRoll>=uniqueEnchantRange.first&&randomRoll<uniqueEnchantRange.second){
std::copy_if(filteredEnchants.begin(),filteredEnchants.end(),std::back_inserter(remainingAvailableEnchants),[](const ItemEnchantInfo&enchant){return enchant.Category()==ItemEnchantInfo::ItemEnchantCategory::UNIQUE;});
}else ERR(std::format("WARNING! Somehow did not pick a value in any of the given ranges. Rolled value was: {}. Ranges were {}-{}, {}-{}, {}-{}",randomRoll,generalEnchantRange.first,generalEnchantRange.second,classEnchantRange.first,classEnchantRange.second,uniqueEnchantRange.first,uniqueEnchantRange.second));
const auto AtLeastOneAttributeIncreased=[](const ItemEnchant&oldEnchant,const ItemEnchant&newEnchant){
if(oldEnchant.Name()!=newEnchant.Name())ERR(std::format("WARNING! Not intended to be used with enchants of varying names!! Old:{} New:{}",oldEnchant.Name(),newEnchant.Name()));
for(const auto&[attr,val]:oldEnchant){
if(newEnchant.GetAttribute(attr.ActualName())>val)return true;
}
return false;
};
for(;;){
ItemEnchantInfo&randomEnchant{remainingAvailableEnchants[util::random()%remainingAvailableEnchants.size()]};
ItemEnchant newEnchant{randomEnchant.Name()};
if(!previousEnchant||
previousEnchant.value().Name()!=newEnchant.Name()||
AtLeastOneAttributeIncreased(previousEnchant.value(),newEnchant))return newEnchant;
}
return filteredEnchants[util::random()%filteredEnchants.size()].Name();
}
void ItemEnchant::UpdateDescription(){
@ -307,7 +270,7 @@ std::map<ItemAttribute,float>::const_iterator ItemEnchant::end()const{
}
const Pixel&ItemEnchant::DisplayCol()const{
return ItemEnchantInfo::enchantTextDisplayCol.at(Category());
return ItemEnchantInfo::GetEnchant(Name()).enchantAttributeCol;
}
const std::optional<ItemEnchantInfo::AbilitySlot>&ItemEnchantInfo::GetAbilitySlot()const{
@ -338,16 +301,4 @@ const std::optional<Ability*>ItemEnchantInfo::GetAbility()const{
const std::optional<std::reference_wrapper<Ability>>ItemEnchant::Ability()const{
if(GetEnchantInfo().GetAbility())return **GetEnchantInfo().GetAbility();
return {};
}
const Pixel&ItemEnchantInfo::DisplayCol()const{
return ItemEnchantInfo::enchantTextDisplayCol.at(Category());
}
const bool ItemEnchant::HasAttributes()const{
return stats.size()>0;
}
const std::optional<Class>&ItemEnchant::GetClass()const{
return ItemEnchantInfo::GetEnchant(Name()).GetClass();
}

@ -69,7 +69,6 @@ public:
static void Initialize();
const static ItemEnchantInfo&GetEnchant(const std::string_view enchantName);
const static std::unordered_map<std::string,ItemEnchantInfo>&GetEnchants();
static std::unordered_map<ItemEnchantCategory,Pixel>enchantTextDisplayCol;
const std::string Name(TextStyle style=TextStyle::NORMAL)const;
const std::string_view Description()const;
@ -77,9 +76,8 @@ public:
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 Pixel&DisplayCol()const;
const float GetConfigValue(const std::string_view keyName)const;
const float operator[](const std::string&name)const;
const float operator[](const std::string& name)const;
private:
class ItemEnchantCategoryData{
friend class ItemEnchantInfo;
@ -100,8 +98,6 @@ private:
static std::unordered_map<ItemEnchantCategory,ItemEnchantCategoryData>ENCHANT_CATEGORIES;
};
class Item;
class ItemEnchant{
public:
ItemEnchant(const std::string_view enchantName);
@ -109,21 +105,18 @@ public:
const std::string_view Description()const;
const ItemEnchantInfo::ItemEnchantCategory&Category()const;
const std::optional<ItemEnchantInfo::AbilitySlot>&AbilitySlot()const;
const static std::vector<ItemEnchantInfo>GetAvailableEnchants();
//Rolls a class-appropriate random enchant.
const static ItemEnchant RollRandomEnchant(const std::optional<ItemEnchant>previousEnchant={});
const static std::string RollRandomEnchant();
void SetAttribute(const std::string_view attr,const float val);
const float&GetAttribute(const std::string_view attr)const;
std::map<ItemAttribute,float>::const_iterator begin()const;
std::map<ItemAttribute,float>::const_iterator end()const;
const bool HasAttributes()const;
const Pixel&DisplayCol()const;
const std::optional<std::reference_wrapper<::Ability>>Ability()const;
const std::optional<Class>&GetClass()const;
const std::optional<std::reference_wrapper<Ability>>Ability()const;
private:
void UpdateDescription();
const ItemEnchantInfo&GetEnchantInfo()const;
std::string enchantName;
std::string description;
ItemAttributable stats;
};
};

@ -1,102 +0,0 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "AdventuresInLestoria.h"
#include "BulletTypes.h"
INCLUDE_ITEM_SCRIPTS
INCLUDE_game
INCLUDE_ANIMATION_DATA
void ItemInfo::InitializeScripts(){
ITEM_SCRIPTS["Restore"]=[](AiL*game,std::optional<vf2d>targetingPos,ItemProps props){
for(const auto&[propName,buffType]:NameToBuffType){
int restoreAmt=props.GetInt(propName);
game->GetPlayer()->AddBuff(BuffRestorationType::ONE_OFF,NameToBuffType.at(propName),0.01f,float(restoreAmt),0.0f);
if(restoreAmt>0&&props.PropCount(propName)==3){
game->GetPlayer()->AddBuff(BuffRestorationType::OVER_TIME,NameToBuffType.at(propName),props.GetFloat(propName,2),float(restoreAmt),props.GetFloat(propName,1));
}
}
return true;
};
for(auto&[key,value]:ItemAttribute::attributes){
if(!DATA.GetProperty("ItemScript.Buff").HasProperty(key)){
ERR("WARNING! Buff Item Script does not support Buff "<<std::quoted(value.Name())<<"!");
}
}
ITEM_SCRIPTS["Buff"]=[](AiL*game,std::optional<vf2d>targetingPos,ItemProps props){
for(auto&[key,value]:ItemAttribute::attributes){
float intensity=props.GetFloat(key,0);
if(intensity==0.f)continue;
if(ItemAttribute::Get(key).DisplayAsPercent())intensity/=100;
game->GetPlayer()->AddBuff(BuffType::STAT_UP,props.GetFloat(key,1),intensity,{ItemAttribute::Get(key)});
}
return true;
};
ITEM_SCRIPTS["RestoreDuringCast"]=[](AiL*game,std::optional<vf2d>targetingPos,ItemProps props){
for(const auto&[propName,buffType]:NameToBuffType){
int restoreAmt=props.GetInt(propName);
game->GetPlayer()->AddBuff(BuffRestorationType::ONE_OFF,NameToBuffType.at(propName),0.01f,float(restoreAmt),0.0f);
if(restoreAmt>0&&props.PropCount(propName)==3){
game->GetPlayer()->AddBuff(BuffRestorationType::OVER_TIME_DURING_CAST,NameToBuffType.at(propName),props.GetFloat(propName,2),float(restoreAmt),props.GetFloat(propName,1));
}
}
return true;
};
ITEM_SCRIPTS["Projectile"]=[](AiL*game,std::optional<vf2d>targetingPos,ItemProps props){
const int projectileDamage{props.GetInt("Base Damage")+int(game->GetPlayer()->GetAttack()*props.GetFloat("Player Damage Mult"))};
std::optional<LingeringEffect>lingeringEffect{};
if(props.GetFloat("Linger Time")>0.f){
const int damage{props.GetInt("Tick Base Damage")+int(props.GetFloat("Tick Player Damage Mult")*game->GetPlayer()->GetAttack())};
lingeringEffect.emplace(vf2d{},props.GetString("Lingering Effect"),props.GetString("Lingering Sound"),props.GetFloat("Linger Radius")/100.f*24,damage,props.GetFloat("Tick Rate"),HurtType::MONSTER,props.GetFloat("Linger Time"),5.f,game->GetPlayer()->OnUpperLevel(),1.f,vf2d{},DARK_RED,util::random(2*PI),0.f,false,0.2f,
[](const Effect&self){
return Effect{self.pos,0.5f,"fire_ring.png",self.OnUpperLevel(),util::random_range(0.7f,1.f),0.1f,{},PixelLerp(Pixel(0xF7B752),Pixel(0xE74F30),util::random(1.f)),util::random(2*PI),0.f,true};
});
}
CreateBullet(ThrownProjectile)(game->GetPlayer()->GetPos(),targetingPos.value(),props.GetString("Image"),props.GetFloat("Cast Size")/100.f*24,game->GetPlayer()->GetZ(),0.3f,8.f,projectileDamage,game->GetPlayer()->OnUpperLevel(),false,INFINITE,true,WHITE,props.GetFloat("Cast Size")/100.f*vf2d{1.f,1.f},0.f,
Effect{vf2d{},ANIMATION_DATA.at("explosionframes.png").GetTotalAnimationDuration(),"explosionframes.png",game->GetPlayer()->OnUpperLevel(),props.GetFloat("Cast Size")/100.f*vf2d{1.f,1.f}},props.GetString("Explode Sound Effect"),lingeringEffect)EndBullet;
return true;
};
ITEM_SCRIPTS.SetInitialized();
LOG(ITEM_SCRIPTS.size()<<" item scripts have been loaded.");
}

@ -48,7 +48,8 @@ INCLUDE_MONSTER_LIST
INCLUDE_EMITTER_LIST
LightningBolt::LightningBolt(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col)
:Bullet(pos,vel,radius,damage,"lightning_bolt.png",upperLevel,false,INFINITE,true,friendly,col){}
:Bullet(pos,vel,radius,damage,
"lightning_bolt.png",upperLevel,false,INFINITE,true,friendly,col){}
void LightningBolt::Update(float fElapsedTime){
lastParticleSpawn=std::max(0.f,lastParticleSpawn-fElapsedTime);
@ -71,56 +72,43 @@ void LightningBolt::Update(float fElapsedTime){
}break;
}
}
if(distanceTraveled>"Wizard.Ability 2.Max Range"_F&&IsActivated()){
if(distanceTraveled>"Wizard.Ability 2.Range"_F&&IsActivated()){
fadeOutTime="Wizard.Ability 2.BulletFadeoutTime"_F;
}
}
BulletDestroyState LightningBolt::PlayerHit(Player*player){
BulletDestroyState LightningBolt::PlayerHit(Player*player)
{
fadeOutTime="Wizard.Ability 2.BulletFadeoutTime"_F;
game->AddEffect(std::make_unique<Effect>(player->GetPos(),"Wizard.Ability 2.SplashLifetime"_F,"lightning_splash_effect.png",upperLevel,player->GetSizeMult(),"Wizard.Ability 2.SplashFadeoutTime"_F,vf2d{},WHITE,"Wizard.Ability 2.SplashRotationRange"_FRange));
SoundEffect::PlaySFX("Wizard Lightning Bolt Hit",pos);
SoundEffect::PlaySFX("Wizard Lightning Bolt Hit",pos);
return BulletDestroyState::KEEP_ALIVE;
}
BulletDestroyState LightningBolt::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit){
BulletDestroyState LightningBolt::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)
{
fadeOutTime="Wizard.Ability 2.BulletFadeoutTime"_F;
game->AddEffect(std::make_unique<Effect>(monster.GetPos(),"Wizard.Ability 2.SplashLifetime"_F,"lightning_splash_effect.png",upperLevel,monster.GetSizeMult(),"Wizard.Ability 2.SplashFadeoutTime"_F,vf2d{},WHITE,"Wizard.Ability 2.SplashRotationRange"_FRange));
LightningBolt::ApplyLightningShock(monster,monster.OnUpperLevel());
SoundEffect::PlaySFX("Wizard Lightning Bolt Hit",pos);
return BulletDestroyState::KEEP_ALIVE;
}
void LightningBolt::ModifyOutgoingDamageData(HurtDamageInfo&data){
if(friendly)data.hurtFlags|=HurtFlag::PLAYER_ABILITY;
}
void LightningBolt::ApplyLightningShock(const Monster&monster,const bool onUpperLevel,const ChainLightningStatus chainLightningRepeatCount/*Set to higher amount to indicate when this monster instead affects monsters due to the enchantment, which changes which shock count variable we look at and will be used for counting down shock times.*/){
int targetsHit=0;
for(std::shared_ptr<Monster>&m:MONSTER_LIST){
if(&*m==&monster||m->InUndamageableState(m->OnUpperLevel(),m->GetZ()))continue;
if(&*m==&monster||monster.OnUpperLevel()!=m->OnUpperLevel())continue;
geom2d::line<float>lineToTarget=geom2d::line<float>(monster.GetPos(),m->GetPos());
float dist=lineToTarget.length();
if(dist<="Wizard.Ability 2.LightningChainRadius"_F/100*24){
int lightningChainDamage{int(game->GetPlayer()->GetAttack()*"Wizard.Ability 2.LightningChainDamageMult"_F)};
if(chainLightningRepeatCount>0)lightningChainDamage=int(game->GetPlayer()->GetAttack()*"Chain Lightning"_ENC["SHOCK DAMAGE"]/100.f);
if(m->Hurt(lightningChainDamage,onUpperLevel,0,HurtFlag::PLAYER_ABILITY)){
EMITTER_LIST.emplace_back(std::make_unique<LightningBoltEmitter>(LightningBoltEmitter(monster.GetPos(),m->GetPos(),"Wizard.Ability 2.LightningChainFrequency"_F,"Wizard.Ability 2.LightningChainLifetime"_F,onUpperLevel)));
game->AddEffect(std::make_unique<Effect>(m->GetPos(),"Wizard.Ability 2.LightningChainSplashLifetime"_F,"lightning_splash_effect.png",onUpperLevel,monster.GetSizeMult(),"Wizard.Ability 2.LightningChainSplashFadeoutTime"_F,vf2d{},WHITE,"Wizard.Ability 2.LightningChainSplashRotationRange"_FRange));
int chainLightningRepeat{};
if(chainLightningRepeatCount>0)chainLightningRepeat=chainLightningRepeatCount-1;
else if(game->GetPlayer()->HasEnchant("Chain Lightning")&&chainLightningRepeatCount==ORIGINAL_BOLT)chainLightningRepeat=int("Chain Lightning"_ENC["REPEAT COUNT"]);
if(chainLightningRepeat>0&&game->GetPlayer()->HasEnchant("Chain Lightning"))m->AddBuff(BuffType::AFFECTED_BY_LIGHTNING_BOLT,"Chain Lightning"_ENC["ADDITIONAL SHOCK DELAY"],chainLightningRepeat,[](std::weak_ptr<Monster>attachedTarget,Buff&b){
LightningBolt::ApplyLightningShock(*attachedTarget.lock(),attachedTarget.lock()->OnUpperLevel(),ChainLightningStatus(b.intensity));
});
if(m->Hurt(int(game->GetPlayer()->GetAttack()*"Wizard.Ability 2.LightningChainDamageMult"_F),OnUpperLevel(),0,HurtFlag::PLAYER_ABILITY)){
EMITTER_LIST.push_back(std::make_unique<LightningBoltEmitter>(LightningBoltEmitter(monster.GetPos(),m->GetPos(),"Wizard.Ability 2.LightningChainFrequency"_F,"Wizard.Ability 2.LightningChainLifetime"_F,upperLevel)));
game->AddEffect(std::make_unique<Effect>(m->GetPos(),"Wizard.Ability 2.LightningChainSplashLifetime"_F,"lightning_splash_effect.png",upperLevel,monster.GetSizeMult(),"Wizard.Ability 2.LightningChainSplashFadeoutTime"_F,vf2d{},WHITE,"Wizard.Ability 2.LightningChainSplashRotationRange"_FRange));
targetsHit++;
}
}
int targetHitLimit{"Wizard.Ability 2.LightningChainTargets"_I};
if(chainLightningRepeatCount>0)targetHitLimit="Chain Lightning"_ENC["ADDITIONAL SHOCK COUNT"];
if(targetsHit>=targetHitLimit)break;
if(targetsHit>=2)break;
}
SoundEffect::PlaySFX("Wizard Lightning Bolt Hit",pos);
return BulletDestroyState::KEEP_ALIVE;
}
void LightningBolt::ModifyOutgoingDamageData(HurtDamageInfo&data){
if(friendly)data.hurtFlags|=HurtFlag::PLAYER_ABILITY;
}

@ -120,8 +120,9 @@ void Menu::InitializeMenus(){
InitializeCreditsWindow();
InitializeArtificerWindow();
InitializeArtificerRefineWindow();
InitializeArtificerRefineResultWindow();
InitializeArtificerRefineConfirmWindow();
InitializeArtificerDisassembleWindow();
InitializeArtificerDisassembleConfirmWindow();
InitializeArtificerEnchantWindow();
InitializeArtificerEnchantConfirmWindow();
@ -409,8 +410,8 @@ void Menu::KeyboardButtonNavigation(AiL*game,vf2d menuPos){
}
if(navigationGroups.count(selectionButtonName)){
Navigation nav=navigationGroups[selectionButtonName];
const float scrollVertAnalogDAS{game->KEY_SCROLLVERT_L.AnalogDAS(0.5f)};
if(game->KEY_UP.PressedDAS()||scrollVertAnalogDAS<-0.5f){
if(game->KEY_UP.PressedDAS()||game->KEY_SCROLLUP.AnalogDAS(0.5f)<-0.5f){
SoundEffect::PlaySFX("Menu Navigate",SoundEffect::CENTERED);
SetMouseNavigation(false);
if(std::holds_alternative<std::string>(nav.up)&&std::get<std::string>(nav.up).length()>0)SetSelection(std::string_view(std::get<std::string>(nav.up)));
@ -418,11 +419,10 @@ void Menu::KeyboardButtonNavigation(AiL*game,vf2d menuPos){
if(std::holds_alternative<MenuDataFunc>(nav.up)){
Data returnData;
std::get<MenuDataFunc>(nav.up)(type,returnData);
if(!std::holds_alternative<ButtonName>(returnData)&&!std::holds_alternative<std::weak_ptr<MenuComponent>>(returnData))ERR(std::format("WARNING! No valid return type set for up button navigation in menu {}",int(type)));
SetSelection(returnData);
}
}
if(game->KEY_DOWN.PressedDAS()||scrollVertAnalogDAS>0.5f){
if(game->KEY_DOWN.PressedDAS()||game->KEY_SCROLLDOWN.AnalogDAS(0.5f)>0.5f){
SoundEffect::PlaySFX("Menu Navigate",SoundEffect::CENTERED);
SetMouseNavigation(false);
if(std::holds_alternative<std::string>(nav.down)&&std::get<std::string>(nav.down).length()>0)SetSelection(std::string_view(std::get<std::string>(nav.down)));
@ -430,7 +430,6 @@ void Menu::KeyboardButtonNavigation(AiL*game,vf2d menuPos){
if(std::holds_alternative<MenuDataFunc>(nav.down)){
Data returnData;
std::get<MenuDataFunc>(nav.down)(type,returnData);
if(!std::holds_alternative<ButtonName>(returnData)&&!std::holds_alternative<std::weak_ptr<MenuComponent>>(returnData))ERR(std::format("WARNING! No valid return type set for down button navigation in menu {}",int(type)));
SetSelection(returnData);
}
}
@ -442,7 +441,6 @@ void Menu::KeyboardButtonNavigation(AiL*game,vf2d menuPos){
if(std::holds_alternative<MenuDataFunc>(nav.left)){
Data returnData;
std::get<MenuDataFunc>(nav.left)(type,returnData);
if(!std::holds_alternative<ButtonName>(returnData)&&!std::holds_alternative<std::weak_ptr<MenuComponent>>(returnData))ERR(std::format("WARNING! No valid return type set for left button navigation in menu {}",int(type)));
SetSelection(returnData);
}
}
@ -454,7 +452,6 @@ void Menu::KeyboardButtonNavigation(AiL*game,vf2d menuPos){
if(std::holds_alternative<MenuDataFunc>(nav.right)){
Data returnData;
std::get<MenuDataFunc>(nav.right)(type,returnData);
if(!std::holds_alternative<ButtonName>(returnData)&&!std::holds_alternative<std::weak_ptr<MenuComponent>>(returnData))ERR(std::format("WARNING! No valid return type set for right button navigation in menu {}",int(type)));
SetSelection(returnData);
}
}
@ -818,32 +815,4 @@ const bool Menu::GameInitialized()const{
const bool Menu::IsMouseOverMenu(){
if(Menu::stack.size()==0)return false;
return geom2d::overlaps(game->GetMousePos(),geom2d::rect<float>{Menu::stack.back()->pos-"Interface.9PatchSize"_V.x,Menu::stack.back()->size+"Interface.9PatchSize"_V.y*2});
}
void Menu::ScrollUp(const MenuType type,const std::string&componentName,Data&returnData,const std::optional<std::string>wrapDestination){
if(!Menu::menus[type]->GetSelection().expired()){
auto selection=Menu::menus[type]->GetSelection().lock();
size_t index=Component<ScrollableWindowComponent>(type,componentName)->GetComponentIndex(selection);
index--;
if(index>=Component<ScrollableWindowComponent>(type,componentName)->GetComponents().size()){
if(wrapDestination)returnData=wrapDestination.value();
else returnData=Component<ScrollableWindowComponent>(type,componentName)->GetComponents().back();
}else{
returnData=Component<ScrollableWindowComponent>(type,componentName)->GetComponents()[index];
}
}
}
void Menu::ScrollDown(const MenuType type,const std::string&componentName,Data&returnData,const std::optional<std::string>wrapDestination){
if(!Menu::menus[type]->GetSelection().expired()){
auto selection=Menu::menus[type]->GetSelection().lock();
size_t index=Component<ScrollableWindowComponent>(type,componentName)->GetComponentIndex(selection);
index++;
if(index>=Component<ScrollableWindowComponent>(type,componentName)->GetComponents().size()){
if(wrapDestination)returnData=wrapDestination.value();
else returnData=Component<ScrollableWindowComponent>(type,componentName)->GetComponents().front();
}else{
returnData=Component<ScrollableWindowComponent>(type,componentName)->GetComponents()[index];
}
}
}

@ -113,8 +113,9 @@ class Menu:public IAttributable{
static void InitializeCreditsWindow();
static void InitializeArtificerWindow();
static void InitializeArtificerRefineWindow();
static void InitializeArtificerRefineResultWindow();
static void InitializeArtificerRefineConfirmWindow();
static void InitializeArtificerDisassembleWindow();
static void InitializeArtificerDisassembleConfirmWindow();
static void InitializeArtificerEnchantWindow();
static void InitializeArtificerEnchantConfirmWindow();
@ -208,10 +209,6 @@ public:
//Returns whether or not this menu type is currently in the foreground of the game, and thus being interacted with by the user.
static bool IsCurrentlyActive(MenuType type);
static const bool IsMouseOverMenu(); //Returns whether the mouse is hovering over any portion of the menu, thus can be used if a menu is not supposed to cover up everything to determine mouse interactions.
//If a wrap destination is specified, will resolve the selection to that button when the top is reached, otherwise will default to wrapping around.
static void ScrollUp(const MenuType type,const std::string&componentName,Data&returnData,const std::optional<std::string>wrapDestination="");
//If a wrap destination is specified, will resolve the selection to that button when the bottom is reached, otherwise will default to wrapping around.
static void ScrollDown(const MenuType type,const std::string&componentName,Data&returnData,const std::optional<std::string>wrapDestination="");
private:
Menu(vf2d pos,vf2d size);
static MenuType lastMenuTypeCreated;

@ -99,7 +99,6 @@ void MenuComponent::DrawDecal(ViewPort&window,bool focused){
window.FillRectDecal(rect.pos+V(A::DRAW_OFFSET)+vf2d{rect.size.x-1+1,0},{1,rect.size.y+1},borderCol);
window.FillRectDecal(rect.pos+V(A::DRAW_OFFSET)+vf2d{0,rect.size.y-1+1},{rect.size.x+1,1},borderCol);
}
if(showDefaultLabel){
vf2d adjustedScale=labelScaling;
vi2d labelTextSize=vf2d(game->GetTextSizeProp(label))*adjustedScale;

@ -81,6 +81,8 @@ class MenuComponent:public IAttributable{
friend class RowItemDisplay;
friend class InventoryConsumableWindow;
friend class FloatingMenuComponent;
MenuFunc onHover=[](MenuFuncData dat){return true;};
MenuFunc onMouseOut=[](MenuFuncData dat){return true;};
bool hoverState=false;
bool runHoverFunctions=false;
private:
@ -134,8 +136,6 @@ protected:
virtual void DisableOutsideWindow();
const bool IsEnabledOutsideWindow()const;
const bool IsDisabledOutsideWindow()const;
MenuFunc onHover=[](MenuFuncData dat){return true;};
MenuFunc onMouseOut=[](MenuFuncData dat){return true;};
public:
MenuType parentMenu=MenuType::ENUM_END;
std::weak_ptr<ScrollableWindowComponent>parentComponent{};

@ -51,7 +51,7 @@ struct MenuFuncData{
AiL*const game;
const std::weak_ptr<MenuComponent> component;
const std::weak_ptr<ScrollableWindowComponent> parentComponent={};
MenuFuncData(Menu&menu,AiL*const game,std::weak_ptr<MenuComponent>component,std::weak_ptr<ScrollableWindowComponent>parentComponent={});
MenuFuncData(Menu&menu,AiL*const game,std::weak_ptr<MenuComponent> component,std::weak_ptr<ScrollableWindowComponent>parentComponent={});
};
using InputGroupActionDisplayName=std::variant<std::string,std::function<std::string(MenuFuncData)>>;

@ -59,13 +59,13 @@ private:
public:
int selected=-1; //0-2 representing which loadout slot this item consumes. -1 means not selected.
inline MenuItemButton(geom2d::rect<float>rect,const std::vector<std::shared_ptr<Item>>&invRef,int invIndex,MenuFunc onClick,MenuType itemDescriptionMenu,std::string itemNameLabelName,std::string itemDescriptionLabelName,IconButtonAttr attributes=IconButtonAttr::SELECTABLE)
:MenuIconButton(rect,invRef.size()>invIndex?const_cast<Decal*>(invRef[invIndex]->Icon().Decal()):nullptr,onClick,attributes),invRef(invRef),itemDescriptionMenu(itemDescriptionMenu),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName){
:MenuIconButton(rect,invRef.size()>invIndex?const_cast<Decal*>(invRef[invIndex]->Decal()):nullptr,onClick,attributes),invRef(invRef),itemDescriptionMenu(itemDescriptionMenu),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName){
draggable=false;
inventoryIndex=invIndex;
valid=invRef.size()>invIndex;
}
inline MenuItemButton(geom2d::rect<float>rect,const std::vector<std::shared_ptr<Item>>&invRef,int invIndex,MenuFunc onClick,MenuFunc onHover,MenuFunc onMouseOut,MenuType itemDescriptionMenu,std::string itemNameLabelName,std::string itemDescriptionLabelName,IconButtonAttr attributes=IconButtonAttr::SELECTABLE)
:MenuIconButton(rect,invRef.size()>invIndex?const_cast<Decal*>(invRef[invIndex]->Icon().Decal()):nullptr,onClick,attributes),invRef(invRef),itemDescriptionMenu(itemDescriptionMenu),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName){
:MenuIconButton(rect,invRef.size()>invIndex?const_cast<Decal*>(invRef[invIndex]->Decal()):nullptr,onClick,attributes),invRef(invRef),itemDescriptionMenu(itemDescriptionMenu),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName){
draggable=false;
inventoryIndex=invIndex;
runHoverFunctions=true;
@ -108,7 +108,7 @@ protected:
std::string labelNameText;
std::string labelDescriptionText;
if(valid){
icon=const_cast<Decal*>(invRef[inventoryIndex]->Icon().Decal());
icon=const_cast<Decal*>(invRef[inventoryIndex]->Decal());
labelNameText=invRef[inventoryIndex]->DisplayName();
labelDescriptionText=invRef[inventoryIndex]->Description(compact);
}else{

@ -58,12 +58,12 @@ protected:
std::weak_ptr<Item>itemRef;
public:
inline MenuItemItemButton(geom2d::rect<float>rect,const std::weak_ptr<Item>itemRef,MenuFunc onClick,std::string itemNameLabelName,std::string itemDescriptionLabelName,IconButtonAttr attributes=IconButtonAttr::SELECTABLE)
:MenuIconButton(rect,!ISBLANK(itemRef)?const_cast<Decal*>(itemRef.lock()->Icon().Decal()):nullptr,onClick,attributes),itemRef(itemRef),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName){
:MenuIconButton(rect,!ISBLANK(itemRef)?const_cast<Decal*>(itemRef.lock()->Decal()):nullptr,onClick,attributes),itemRef(itemRef),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName){
draggable=false;
valid=!ISBLANK(itemRef);
}
inline MenuItemItemButton(geom2d::rect<float>rect,const std::weak_ptr<Item>itemRef,MenuFunc onClick,MenuFunc onHover,MenuFunc onMouseOut,std::string itemNameLabelName="",std::string itemDescriptionLabelName="",IconButtonAttr attributes=IconButtonAttr::SELECTABLE)
:MenuIconButton(rect,!ISBLANK(itemRef)?const_cast<Decal*>(itemRef.lock()->Icon().Decal()):nullptr,onClick,attributes),itemRef(itemRef),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName){
:MenuIconButton(rect,!ISBLANK(itemRef)?const_cast<Decal*>(itemRef.lock()->Decal()):nullptr,onClick,attributes),itemRef(itemRef),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName){
runHoverFunctions=true;
draggable=false;
valid=!ISBLANK(itemRef);
@ -75,7 +75,6 @@ public:
}
inline const std::weak_ptr<Item>SetItem(const std::weak_ptr<Item>newItem,bool labelUpdate=true){
itemRef=newItem;
borderCol=newItem.lock()->HasEnchant()?newItem.lock()->GetEnchant().value().DisplayCol():WHITE;
if(labelUpdate){
UpdateLabel();
}
@ -94,7 +93,7 @@ public:
icon=nullptr;
return;
}
icon=const_cast<Decal*>(itemRef.lock()->Icon().Decal());
icon=const_cast<Decal*>(itemRef.lock()->Decal());
}
inline void SetHideDetails(bool hideDetails){
this->hideDetails=hideDetails;
@ -124,24 +123,22 @@ protected:
icon=nullptr;
labelNameText="";
labelDescriptionText="";
}else{
icon=const_cast<Decal*>(itemRef.lock()->Icon().Decal());
labelNameText=itemRef.lock()->DisplayName();
labelDescriptionText=itemRef.lock()->Description(compact);
return;
}
icon=const_cast<Decal*>(itemRef.lock()->Decal());
labelNameText=itemRef.lock()->DisplayName();
labelDescriptionText=itemRef.lock()->Description(compact);
if(hideDetails){
std::for_each(labelNameText.begin(),labelNameText.end(),[](char&c){c='?';});
std::for_each(labelDescriptionText.begin(),labelDescriptionText.end(),[](char&c){c='?';});
}
if(itemNameLabelName!=""){
Component<MenuLabel>(parentMenu,itemNameLabelName)->SetLabel(labelNameText);
Component<MenuLabel>(parentMenu,itemNameLabelName)->SetBorderCol(borderCol);
Component<MenuLabel>(parentMenu,itemNameLabelName)->Enable();
}
if(itemDescriptionLabelName!=""){
Component<MenuLabel>(parentMenu,itemDescriptionLabelName)->SetLabel(labelDescriptionText);
Component<MenuLabel>(parentMenu,itemDescriptionLabelName)->SetBorderCol(borderCol);
Component<MenuLabel>(parentMenu,itemDescriptionLabelName)->Enable();
}
}

@ -50,52 +50,26 @@ namespace ItemLabelDescriptionType{
using namespace ItemLabelDescriptionType;
class MenuItemLabel:public MenuLabel{
std::variant<Item,std::weak_ptr<Item>>itemRef;
std::weak_ptr<Item>itemRef;
ItemDescriptionType labelType;
public:
inline MenuItemLabel(geom2d::rect<float>rect,std::weak_ptr<Item>weakItemRef,ItemDescriptionType labelType=ITEM_DESCRIPTION,float scale=1,ComponentAttr attributes=ComponentAttr::NONE)
:MenuLabel(rect,"",scale,attributes),itemRef(weakItemRef),labelType(labelType){}
//NOTE: This version of the constructor will create a COPY of an item as opposed to the weakItemRef version which will reference an item say from an inventory...
inline MenuItemLabel(geom2d::rect<float>rect,const Item&strongItemRef,ItemDescriptionType labelType=ITEM_DESCRIPTION,float scale=1,ComponentAttr attributes=ComponentAttr::NONE)
:MenuLabel(rect,"",scale,attributes),itemRef(strongItemRef),labelType(labelType){}
inline MenuItemLabel(geom2d::rect<float>rect,std::weak_ptr<Item>itemRef,ItemDescriptionType labelType=ITEM_DESCRIPTION,float scale=1,ComponentAttr attributes=ComponentAttr::NONE)
:MenuLabel(rect,"",scale,attributes),itemRef(itemRef),labelType(labelType){}
inline virtual void Update(AiL*game)override{
MenuLabel::Update(game);
std::string displayStr{};
if(std::holds_alternative<Item>(itemRef)){
Item&item{std::get<Item>(itemRef)};
if(!itemRef.expired()){
switch(labelType){
case ITEM_DESCRIPTION:{
displayStr=item.Description(NON_COMPACT);
SetLabel(itemRef.lock()->Description(NON_COMPACT));
}break;
case ITEM_NAME:{
displayStr=item.DisplayName();
SetLabel(itemRef.lock()->DisplayName());
}break;
}
}else{
std::weak_ptr<Item>&ref{std::get<std::weak_ptr<Item>>(itemRef)};
if(!ref.expired()){
switch(labelType){
case ITEM_DESCRIPTION:{
displayStr=ref.lock()->Description(NON_COMPACT);
}break;
case ITEM_NAME:{
displayStr=ref.lock()->DisplayName();
}break;
}
}
}
MenuLabel::SetLabel(displayStr);
}
inline virtual void SetItem(std::variant<Item,std::weak_ptr<Item>>itemRef){
inline virtual void SetItem(std::weak_ptr<Item>itemRef){
this->itemRef=itemRef;
}
inline const std::variant<Item,std::weak_ptr<Item>>&GetItem()const{
return itemRef;
}
protected:
//DO NOT USE! Use MenuLabel::SetLabel() instead!
inline virtual void SetLabel(std::string text){
ERR("DO NOT SET THE LABEL DIRECTLY for a MenuItemLabel component! THIS IS NOT ALLOWED!")
};
};

@ -81,9 +81,6 @@ public:
CalculateWrappedLabel();
if(runOnLabelChangeFunc)onLabelChangeFunc(text);
}
inline void SetBorderCol(const Pixel col){
borderCol=col;
}
protected:
inline virtual void Update(AiL*game)override{
MenuComponent::Update(game);
@ -97,15 +94,15 @@ protected:
vf2d adjustedShadowScale={shadowScale,shadowScale};
vf2d labelTextSize=
proportional?
vf2d(game->GetWrappedTextSizeProp(finalLabel,rect.size.x-8,adjustedScale)):
vf2d(game->GetWrappedTextSize(finalLabel,rect.size.x-8,adjustedScale));
vf2d(game->GetWrappedTextSizeProp(finalLabel,rect.size.x-4,adjustedScale)):
vf2d(game->GetWrappedTextSize(finalLabel,rect.size.x-4,adjustedScale));
if(fitToLabel){
labelTextSize=
proportional?
vf2d(game->GetTextSizeProp(finalLabel)*adjustedScale):
vf2d(game->GetTextSize(finalLabel)*adjustedScale);
float sizeRatio=(labelTextSize.x)/(rect.size.x-6);
float sizeRatio=(labelTextSize.x)/(rect.size.x-4);
if(sizeRatio>1){
adjustedScale.x/=sizeRatio;
adjustedShadowScale.x/=sizeRatio;
@ -116,7 +113,7 @@ protected:
vf2d drawPos=vf2d{-1,0}+rect.middle()-vf2d{labelTextSize}/2; //Assume centered.
if(!centered){
if(rightAlign){
drawPos=vf2d{rect.pos.x+rect.size.x-labelTextSize.x,rect.middle().y-labelTextSize.y/2}; //We should at least vertically align here.
drawPos=vf2d{rect.pos.x+rect.size.x-labelTextSize.x-4,rect.middle().y-labelTextSize.y/2}; //We should at least vertically align here.
}else{ //Left Align
drawPos=vf2d{rect.pos.x+2,rect.middle().y-labelTextSize.y/2}; //We should at least vertically align here.
}
@ -125,7 +122,7 @@ protected:
if(shadow){
if(proportional){
for(int counter=0;const std::string_view&text:lines){
window.DrawShadowStringPropDecal(rect.pos+vf2d{offsets[counter],counter*10.f},text,WHITE,BLACK,{scale,scale});
window.DrawShadowStringPropDecal(rect.pos+vf2d{offsets[counter],counter*10.f},text,WHITE,BLACK);
counter++;
}
}else{
@ -134,7 +131,7 @@ protected:
}else{
if(proportional){
for(int counter=0;const std::string_view&text:lines){
window.DrawStringPropDecal(rect.pos+vf2d{offsets[counter],counter*10.f},text,WHITE,{scale,scale});
window.DrawStringPropDecal(rect.pos+vf2d{offsets[counter],counter*10.f},text,WHITE);
counter++;
}
}else{
@ -144,15 +141,15 @@ protected:
}else{
if(shadow){
if(proportional){
window.DrawShadowStringPropDecal(drawPos,finalLabel,WHITE,BLACK,adjustedScale,adjustedShadowScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x-8);
window.DrawShadowStringPropDecal(drawPos,finalLabel,WHITE,BLACK,adjustedScale,adjustedShadowScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x-4);
}else{
window.DrawShadowStringDecal(drawPos,finalLabel,WHITE,BLACK,adjustedScale,adjustedShadowScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x-8);
window.DrawShadowStringDecal(drawPos,finalLabel,WHITE,BLACK,adjustedScale,adjustedShadowScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x-4);
}
}else{
if(proportional){
window.DrawStringPropDecal(drawPos,finalLabel,WHITE,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x-8);
window.DrawStringPropDecal(drawPos,finalLabel,WHITE,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x-4);
}else{
window.DrawStringDecal(drawPos,finalLabel,WHITE,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x-8);
window.DrawStringDecal(drawPos,finalLabel,WHITE,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x-4);
}
}
}
@ -162,7 +159,7 @@ private:
if(multiLineCentered){
lines.clear();
offsets.clear();
wrappedLabel=util::WrapText(game,GetLabel(),rect.size.x,true,{scale,scale});
wrappedLabel=util::WrapText(game,GetLabel(),rect.size.x,true,{1.f,1.f});
int labelInd=0;
float largestWidth=0;
while(labelInd!=wrappedLabel.length()){
@ -179,11 +176,11 @@ private:
}
}
for(const std::string_view&text:lines){
float width=game->GetTextSizeProp(text).x*scale;
float width=game->GetTextSizeProp(text).x;
offsets.push_back(rect.size.x/2.f-width/2);
}
//Recalculate label height based on line count.
rect.size.y=lines.size()*10.f*scale;
rect.size.y=lines.size()*10.f;
if(!parentComponent.expired()){
parentComponent.lock()->CalculateBounds();
}

@ -1,78 +0,0 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#pragma once
#include "MenuLabel.h"
INCLUDE_DATA
INCLUDE_ITEM_DATA
class MenuRefineLabel:public MenuLabel{
public:
inline MenuRefineLabel(const geom2d::rect<float>rect,const std::weak_ptr<Item>itemRef,const float scale=1,ComponentAttr attributes=ComponentAttr::NONE)
:itemRef(itemRef),MenuLabel(rect,"",scale,attributes){}
inline virtual void Update(AiL*game)override{
std::string label{};
if(!ISBLANK(itemRef)){
std::string longestStatName{};
const Stats&maxStats{ITEM_DATA.at(itemRef.lock()->ActualName()).GetMaxStats()};
for(const auto&[attr,val]:maxStats){
const std::string displayName{attr.Name()};
if(displayName.size()>longestStatName.size())longestStatName=displayName;
}
const std::string statNameVFormatStr{"{:"+std::to_string(longestStatName.size()+1)+"}"};
for(const auto&[attr,val]:itemRef.lock()->RandomStats()){
const bool IsMaxedOut{val>=maxStats.A_Read(attr.ActualName())};
label+=util::vformat("{}"+statNameVFormatStr+"+{:3}{:1}{}{:>5}\n",IsMaxedOut?Stats::GetShimmeringColor().toHTMLColorCode():"#FFFFFF",attr.Name(),int(round(val)),attr.DisplayAsPercent()?"%":"",IsMaxedOut?"#FF0000":"#00FF00",IsMaxedOut?"MAX":" +???");
}
}
SetLabel(label);
MenuLabel::Update(game);
}
inline void SetItem(const std::weak_ptr<Item>itemRef){
this->itemRef=itemRef;
}
private:
std::weak_ptr<Item>itemRef;
};

@ -70,11 +70,12 @@ enum MenuType{
DEATH, //100% Controller Compatibility
CREDITS, //100% Controller Compatibility
ARTIFICER, //100% Controller Compatibility
ARTIFICER_REFINE, //100% Controller Compatibility
ARTIFICER_REFINE_RESULT, //100% Controller Compatibility
ARTIFICER_DISASSEMBLE, //100% Controller Compatibility
ARTIFICER_ENCHANT, //100% Controller Compatibility
ARTIFICER_ENCHANT_CONFIRM, //100% Controller Compatibility
ARTIFICER_REFINE,
ARTIFICER_REFINE_CONFIRM,
ARTIFICER_DISASSEMBLE,
ARTIFICER_DISASSEMBLE_CONFIRM,
ARTIFICER_ENCHANT,
ARTIFICER_ENCHANT_CONFIRM,
///////////////////////////////////////////////////////////
/*DO NOT REMOVE!!*/ENUM_END////////////////////////////////
///////////////////////////////////////////////////////////

@ -184,7 +184,7 @@ void Merchant::PurchaseItem(IT item,uint32_t amt){
}
Inventory::AddItem(item,amt);
game->GetPlayer()->RemoveMoney(totalCost);
game->GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()-totalCost);
}
void Merchant::SellItem(std::weak_ptr<Item>item,uint32_t amt){
sellFunctionPrimed.Validate(item.lock()->ActualName(),amt);
@ -222,7 +222,7 @@ void Merchant::SellItem(std::weak_ptr<Item>item,uint32_t amt){
std::string itemName=item.lock()->ActualName(); //We need the item name since our reference to the item is about to be deleted.
Inventory::RemoveItem(item,amt);
game->GetPlayer()->AddMoney(totalCost);
game->GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()+totalCost);
//If we still have some in our inventory, we'll add them back in.
if(foundLoadoutSlot!=-1&&Inventory::GetItemCount(itemName)>0){

@ -46,91 +46,38 @@ INCLUDE_game
INCLUDE_MONSTER_LIST
INCLUDE_GFX
Meteor::Meteor(const vf2d pos,const float lifetime,const bool upperLevel,const MeteorSetting setting,const float fadeout,const vf2d spd,const Pixel col,const float rotation,const float rotationSpd,const bool additiveBlending)
:Effect(pos,lifetime,setting==COMET?"comet.png":setting==COMET_FLARE?"comet_flare.png":"meteor.png",upperLevel,{},fadeout,spd,col,rotation,rotationSpd,additiveBlending),startLifetime(lifetime){
switch(setting){
case METEOR:{
meteorImpactParticles="Wizard.Ability 3.MeteorImpactParticles"_I;
randomColorTintR=255;
randomColorTintG=256*(1-util::random("Wizard.Ability 3.MeteorImpactParticleColorGVariance"_F))*(1-util::random("Wizard.Ability 3.MeteorImpactParticleColorGVariance"_F));
randomColorTintB="Wizard.Ability 3.MeteorImpactParticleColorBlueRange"_FRange;
meteorRadius="Wizard.Ability 3.MeteorRadius"_F/100*24;
fallSpdMult=1.f;
size=vf2d{meteorRadius/96,meteorRadius/96};
damageMult="Wizard.Ability 3.MeteorDamageMult"_F;
fireRingLifetime="Wizard.Ability 3.FireRingLifetime"_F;
meteorCrashSFX="Wizard Meteor";
}break;
case COMET:{
meteorImpactParticles="Wizard.Ability 3.MeteorImpactParticles"_I/2;
randomColorTintR=0.f;
randomColorTintG=256*(1-util::random("Wizard.Ability 3.MeteorImpactParticleColorGVariance"_F))*(1-util::random("Wizard.Ability 3.MeteorImpactParticleColorGVariance"_F));
randomColorTintB="Wizard.Ability 3.MeteorImpactParticleColorBlueRange"_FRange*2; //0-254
meteorRadius="Wizard.Ability 3.MeteorRadius"_F/100*24/2/1.5f;
fallSpdMult="Summon Comet"_ENC["FALL SPEED MULT"];
size=vf2d{meteorRadius/96*1.5f,meteorRadius/96*1.5f};
damageMult="Wizard.Ability 3.MeteorDamageMult"_F;
damageMult*=1.f-"Summon Comet"_ENC["DAMAGE REDUCTION PCT"]/100.f;
fireRingLifetime="Wizard.Ability 3.FireRingLifetime"_F;
meteorCrashSFX="Wizard Comet";
}break;
case SOLAR_FLARE:{
meteorImpactParticles="Wizard.Ability 3.MeteorImpactParticles"_I*4;
randomColorTintR=255;
randomColorTintG=256*(1-util::random("Wizard.Ability 3.MeteorImpactParticleColorGVariance"_F))*(1-util::random("Wizard.Ability 3.MeteorImpactParticleColorGVariance"_F));
randomColorTintB="Wizard.Ability 3.MeteorImpactParticleColorBlueRange"_FRange;
meteorRadius="Wizard.Ability 3.MeteorRadius"_F/100*24*2;
fallSpdMult=1.f;
size=vf2d{meteorRadius/96,meteorRadius/96};
damageMult="Solar Flare"_ENC["METEOR ATTACK"]/100.f;
fireRingLifetime="Wizard.Ability 3.FireRingLifetime"_F*2;
meteorCrashSFX="Wizard Meteor";
this->col=RED;
}break;
case COMET_FLARE:{
meteorImpactParticles="Wizard.Ability 3.MeteorImpactParticles"_I*4;
randomColorTintR=util::random_range(128,255);
randomColorTintG=util::random_range(128,255);
randomColorTintB=util::random_range(128,255);
meteorRadius="Wizard.Ability 3.MeteorRadius"_F/100*24*2;
fallSpdMult=1.5f;
size=vf2d{meteorRadius/96,meteorRadius/96};
damageMult="Solar Flare"_ENC["METEOR ATTACK"]/100.f;
damageMult*=1.f-"Summon Comet"_ENC["DAMAGE REDUCTION PCT"]/100.f;
fireRingLifetime="Wizard.Ability 3.FireRingLifetime"_F*1.5f;
meteorCrashSFX="Wizard Meteor";
}break;
}
this->lifetime/=fallSpdMult;
startLifetime=this->lifetime;
Meteor::Meteor(vf2d pos, float lifetime, std::string imgFile, bool upperLevel, vf2d size, float fadeout, vf2d spd, Pixel col, float rotation, float rotationSpd, bool additiveBlending)
:Effect(pos,lifetime,imgFile,upperLevel,size,fadeout,spd,col,rotation,rotationSpd,additiveBlending),startLifetime(lifetime){
}
bool Meteor::Update(float fElapsedTime){
if(lifetime<=0&&!shakeField){
shakeField=true;
game->SetupWorldShake("Wizard.Ability 3.WorldShakeTime"_F);
vf2d meteorOffset=pos+vf2d{lifetime*"Wizard.Ability 3.MeteorXMovementMult"_I,lifetime*"Wizard.Ability 3.MeteorYMovementMult"_I}*"Wizard.Ability 3.MeteorStartingDist"_F/fallSpdMult-vf2d{0,meteorRadius/2};
for(int i=0;i<meteorImpactParticles;i++){
vf2d meteorOffset=pos+vf2d{lifetime*"Wizard.Ability 3.MeteorXMovementMult"_I,lifetime*"Wizard.Ability 3.MeteorYMovementMult"_I}*"Wizard.Ability 3.MeteorStartingDist"_F-vf2d{0,"Wizard.Ability 3.MeteorRadius"_F/100*12};
for(int i=0;i<"Wizard.Ability 3.MeteorImpactParticles"_I;i++){
float randomAngle="Wizard.Ability 3.MeteorImpactParticleAngleRange"_FRange;
float randomRange=100*size.x*(1-util::random("Wizard.Ability 3.MeteorImpactParticleRandomVariance"_F))*(1-util::random("Wizard.Ability 3.MeteorImpactParticleRandomVariance"_F));
float randomColorTintG=256*(1-util::random("Wizard.Ability 3.MeteorImpactParticleColorGVariance"_F))*(1-util::random("Wizard.Ability 3.MeteorImpactParticleColorGVariance"_F));
float randomColorTintB="Wizard.Ability 3.MeteorImpactParticleColorBlueRange"_FRange;
vf2d effectPos=vf2d{cos(randomAngle),sin(randomAngle)}*randomRange+meteorOffset;
game->AddEffect(std::make_unique<Effect>(effectPos,0,"circle.png",OnUpperLevel(),vf2d{util::random(2)+1,util::random(3)+1},util::random(3)+1,vf2d{util::random(10)-5,-util::random(20)-5},Pixel{uint8_t(randomColorTintR),uint8_t(randomColorTintG),uint8_t(randomColorTintB),uint8_t("Wizard.Ability 3.MeteorImpactParticleAlphaRange"_FRange)},0,0,true),effectPos.y<meteorOffset.y);
game->AddEffect(std::make_unique<Effect>(effectPos,0,"circle.png",OnUpperLevel(),vf2d{util::random(2)+1,util::random(3)+1},util::random(3)+1,vf2d{util::random(10)-5,-util::random(20)-5},Pixel{255,uint8_t(randomColorTintG),uint8_t(randomColorTintB),uint8_t("Wizard.Ability 3.MeteorImpactParticleAlphaRange"_FRange)},0,0,true),effectPos.y<meteorOffset.y);
}
game->Hurt(pos,meteorRadius,int(game->GetPlayer()->GetAttack()*damageMult),OnUpperLevel(),0,HurtType::MONSTER,HurtFlag::PLAYER_ABILITY);
game->AddEffect(std::make_unique<PulsatingFire>(pos,fireRingLifetime,meteorRadius,"fire_ring1.png",OnUpperLevel(),vf2d{meteorRadius/12,meteorRadius/12},"Wizard.Ability 3.FireRingFadeoutTime"_F),true);
SoundEffect::PlaySFX(meteorCrashSFX,pos);
game->Hurt(pos,"Wizard.Ability 3.MeteorRadius"_F/100*24,int(game->GetPlayer()->GetAttack()*"Wizard.Ability 3.MeteorDamageMult"_F),OnUpperLevel(),0,HurtType::MONSTER,HurtFlag::PLAYER_ABILITY);
game->AddEffect(std::make_unique<PulsatingFire>(pos,"Wizard.Ability 3.FireRingLifetime"_F,"fire_ring1.png",OnUpperLevel(),vf2d{"Wizard.Ability 3.MeteorRadius"_F/100*2,"Wizard.Ability 3.MeteorRadius"_F/100*2},"Wizard.Ability 3.FireRingFadeoutTime"_F),true);
SoundEffect::PlaySFX("Wizard Meteor",pos);
}
return Effect::Update(fElapsedTime);
}
void Meteor::Draw(const Pixel blendCol)const{
void Meteor::Draw()const{
if(lifetime>0){
vf2d scale=vf2d{192,64}/3.f*(startLifetime+1-lifetime)*0.25*size;
vf2d meteorOffset=vf2d{lifetime*"Wizard.Ability 3.MeteorXMovementMult"_I,0}*"Wizard.Ability 3.MeteorShadowStartingDist"_F/fallSpdMult;
vf2d meteorOffset=vf2d{lifetime*"Wizard.Ability 3.MeteorXMovementMult"_I,0}*"Wizard.Ability 3.MeteorShadowStartingDist"_F;
vf2d centerPoint=pos-vf2d{GFX["circle.png"].Sprite()->width*scale.x/2,GFX["circle.png"].Sprite()->height*scale.y/2};
game->view.DrawDecal(centerPoint+meteorOffset,GFX["circle.png"].Decal(),scale,{0,0,0,192});
}
vf2d meteorOffset=pos+vf2d{lifetime*"Wizard.Ability 3.MeteorXMovementMult"_I,lifetime*"Wizard.Ability 3.MeteorYMovementMult"_I}*"Wizard.Ability 3.MeteorStartingDist"_F/fallSpdMult-vf2d{0,GetFrame().GetSourceRect().size.y/4.f}*size;
vf2d meteorOffset=pos+vf2d{lifetime*"Wizard.Ability 3.MeteorXMovementMult"_I,lifetime*"Wizard.Ability 3.MeteorYMovementMult"_I}*"Wizard.Ability 3.MeteorStartingDist"_F-vf2d{0,GetFrame().GetSourceRect().size.y/4.f}*size;
if(lifetime<=0){
meteorOffset=pos-vf2d{0,GetFrame().GetSourceRect().size.y/4.f}*size;
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save