Merge pull request 'CatchUnitTesting' (#131) from CatchUnitTesting into master
All checks were successful
Emscripten Build / Build_and_Deploy_Web_Build (push) Successful in 8m36s
Emscripten Build / UnitTesting (push) Successful in 8m7s

Reviewed-on: #131
This commit is contained in:
AMay 2026-05-04 16:00:32 -05:00
commit f7517a67e4
85 changed files with 22918 additions and 586 deletions

View File

@ -1,7 +1,7 @@
name: Emscripten Build
run-name: Emscripten build initiated by ${{ gitea.actor }} for ${{ gitea.repository }}
on: [push]
jobs:
Build_and_Deploy_Web_Build:
runs-on: emscripten
@ -18,10 +18,6 @@ jobs:
run: |
apt-get update
apt-get install -y libfreetype6-dev
- name: Build and Run Unit Tests
run: |
./test.sh
./runTest.sh
- name: Build Emscripten Project
run: cmake --build ${{ gitea.event.repository.name }}/ -j 8
- name: Move Files and Finalize
@ -31,15 +27,35 @@ jobs:
echo "Move files to final directory (/web/${{ gitea.event.repository.name }})"
- name: Cleanup - Web Build Available
run: echo "Emscripten build now available at http://projectdivar.com/files/web/${{ gitea.event.repository.name }}"
- name: Send Webhook Update (Success)
if: success()
- name: Send Webhook Update
if: always()
run: |
curl -k -v -X POST -H "Content-Type: application/json" \
-d '{"action": "build_success", "repo": "${{ gitea.event.repository.name }}", "run_number":"${{ gitea.run_number }}"}' \
-d '{"action": "build_result", "status": "${{ job.status }}", "ref": "${{ gitea.ref }}", "repo": "${{ gitea.event.repository.name }}", "run_number":"${{ gitea.run_number }}"}' \
https://45.33.13.215:4505/postUpdate
- name: Send Webhook Update (Failed)
if: failure()
UnitTesting:
runs-on: gcc
container:
volumes:
- /home/sigonasr2/assets:/assets
steps:
- name: Install dependencies
run: |
apt-get update
apt-get install -y nodejs git libfreetype6-dev cmake
- name: Check out repository code
uses: actions/checkout@v4
- name: Build Unit Tests
run: |
./test.sh
- name: Run Unit Tests
run: |
pwd
ln -s /assets "/workspace/AMay/AdventuresInLestoria/bin/assets"
./runTest.sh
- name: Send Webhook Update
if: always()
run: |
curl -k -v -X POST -H "Content-Type: application/json" \
-d '{"action": "build_fail", "repo": "${{ gitea.event.repository.name }}", "run_number":"${{ gitea.run_number }}"}' \
-d '{"action": "test_result", "status": "${{ job.status }}", "ref": "${{ gitea.ref }}", "repo": "${{ gitea.event.repository.name }}", "run_number":"${{ gitea.run_number }}"}' \
https://45.33.13.215:4505/postUpdate

2
.gitignore vendored
View File

@ -425,8 +425,10 @@ desktop.ini
/Adventures in Lestoria Tests/x64/Unit Testing
/x64/Unit Testing
/Adventures in Lestoria/assets/2.10
/Adventures in Lestoria/assets/assets.zip
/Adventures in Lestoria/assets/items/Bird_s Treasure.png
tools
/dotnet-tools.json
/Adventures in Lestoria/assets.zip

View File

@ -1,141 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{69757256-c088-4b22-9557-af7d281cee86}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v145</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="Shared" />
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<IncludePath>$(IncludePath)</IncludePath>
<ExecutablePath>$(VC_ExecutablePath_x64);$(CommonExecutablePath);</ExecutablePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
</PropertyGroup>
<ItemDefinitionGroup />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<WarningLevel>Level3</WarningLevel>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<WarningLevel>Level3</WarningLevel>
<AdditionalIncludeDirectories>$(ProjectDir)../Adventures in Lestoria;;J:\AdventuresInLestoria\Adventures in Lestoria\steam;J:\AdventuresInLestoria\Adventures in Lestoria\discord-files</AdditionalIncludeDirectories>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>discord_game_sdk.dll.lib;freetype.lib;steam_api64.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<PreprocessorDefinitions>X64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(ProjectDir)../Adventures in Lestoria;$(ProjectDir)../Adventures in Lestoria;J:\AdventuresInLestoria\Adventures in Lestoria\steam;J:\AdventuresInLestoria\Adventures in Lestoria\discord-files</AdditionalIncludeDirectories>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<AdditionalDependencies>discord_game_sdk.dll.lib;freetype.lib;steam_api64.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="test.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Adventures in Lestoria\Adventures in Lestoria.vcxproj">
<Project>{8e3067af-cfe7-4b11-bc6b-b867c32753d7}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.8\build\native\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.targets" Condition="Exists('..\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.8\build\native\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.8\build\native\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.8\build\native\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.targets'))" />
</Target>
</Project>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn" version="1.8.1.8" targetFramework="native" />
</packages>

View File

@ -1,5 +0,0 @@
//
// pch.cpp
//
#include "pch.h"

View File

@ -1,7 +0,0 @@
//
// pch.h
//
#pragma once
#include "gtest/gtest.h"

View File

@ -1,8 +0,0 @@
#include "pch.h"
#include "olcUTIL_Geometry2D.h"
using namespace olc::utils;
TEST(GeometryTest, CircleOverlapTest) {
EXPECT_TRUE(geom2d::overlaps(geom2d::circle<float>{vf2d{},10},vf2d{5,5}));
}

View File

@ -1,14 +1,12 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.4.11519.219 insiders
VisualStudioVersion = 18.4.11519.219
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Adventures in Lestoria", "Adventures in Lestoria\Adventures in Lestoria.vcxproj", "{8E3067AF-CFE7-4B11-BC6B-B867C32753D7}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Adventures in Lestoria Tests", "Adventures in Lestoria Tests\Adventures in Lestoria Tests.vcxproj", "{11969B7B-3D50-4825-9584-AF01D15B88E0}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Adventures in Lestoria GTest", "Adventures in Lestoria GTest\Adventures in Lestoria GTest.vcxproj", "{69757256-C088-4B22-9557-AF7D281CEE86}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@ -60,33 +58,7 @@ Global
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Release|x64.ActiveCfg = Unit Testing|x64
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Release|x86.ActiveCfg = Unit Testing|Win32
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Unit Testing|x64.ActiveCfg = Unit Testing|x64
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Unit Testing|x64.Build.0 = Unit Testing|x64
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Unit Testing|x86.ActiveCfg = Unit Testing|Win32
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Unit Testing|x86.Build.0 = Unit Testing|Win32
{69757256-C088-4B22-9557-AF7D281CEE86}.Debug|x64.ActiveCfg = Debug|x64
{69757256-C088-4B22-9557-AF7D281CEE86}.Debug|x64.Build.0 = Debug|x64
{69757256-C088-4B22-9557-AF7D281CEE86}.Debug|x86.ActiveCfg = Debug|Win32
{69757256-C088-4B22-9557-AF7D281CEE86}.Debug|x86.Build.0 = Debug|Win32
{69757256-C088-4B22-9557-AF7D281CEE86}.Emscripten Debug|x64.ActiveCfg = Release|x64
{69757256-C088-4B22-9557-AF7D281CEE86}.Emscripten Debug|x64.Build.0 = Release|x64
{69757256-C088-4B22-9557-AF7D281CEE86}.Emscripten Debug|x86.ActiveCfg = Release|Win32
{69757256-C088-4B22-9557-AF7D281CEE86}.Emscripten Debug|x86.Build.0 = Release|Win32
{69757256-C088-4B22-9557-AF7D281CEE86}.Emscripten|x64.ActiveCfg = Release|x64
{69757256-C088-4B22-9557-AF7D281CEE86}.Emscripten|x64.Build.0 = Release|x64
{69757256-C088-4B22-9557-AF7D281CEE86}.Emscripten|x86.ActiveCfg = Release|Win32
{69757256-C088-4B22-9557-AF7D281CEE86}.Emscripten|x86.Build.0 = Release|Win32
{69757256-C088-4B22-9557-AF7D281CEE86}.Release Desktop|x64.ActiveCfg = Release|x64
{69757256-C088-4B22-9557-AF7D281CEE86}.Release Desktop|x64.Build.0 = Release|x64
{69757256-C088-4B22-9557-AF7D281CEE86}.Release Desktop|x86.ActiveCfg = Release|Win32
{69757256-C088-4B22-9557-AF7D281CEE86}.Release Desktop|x86.Build.0 = Release|Win32
{69757256-C088-4B22-9557-AF7D281CEE86}.Release|x64.ActiveCfg = Release|x64
{69757256-C088-4B22-9557-AF7D281CEE86}.Release|x64.Build.0 = Release|x64
{69757256-C088-4B22-9557-AF7D281CEE86}.Release|x86.ActiveCfg = Release|Win32
{69757256-C088-4B22-9557-AF7D281CEE86}.Release|x86.Build.0 = Release|Win32
{69757256-C088-4B22-9557-AF7D281CEE86}.Unit Testing|x64.ActiveCfg = Release|x64
{69757256-C088-4B22-9557-AF7D281CEE86}.Unit Testing|x64.Build.0 = Release|x64
{69757256-C088-4B22-9557-AF7D281CEE86}.Unit Testing|x86.ActiveCfg = Release|Win32
{69757256-C088-4B22-9557-AF7D281CEE86}.Unit Testing|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<Catch2Adapter>
<FilenameFilter>.*</FilenameFilter>
<ExecutionMode>Single</ExecutionMode>
<Logging>debug</Logging>
<TestCaseTimeout>30000</TestCaseTimeout>
</Catch2Adapter>
</RunSettings>

View File

@ -76,7 +76,9 @@
"beach",
"beach_boss",
"undead_swamp",
"undead_swamp_boss"
"undead_swamp_boss",
"foresty2",
"mountain2"
],
"valuesAsFlags": false
},

View File

@ -77,7 +77,7 @@
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
@ -145,7 +145,7 @@
<CodeAnalysisRuleSet>..\CodeAnalysisRuleset.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|x64'">
<IncludePath>$(ProjectDir)include;$(VCInstallDir)Auxiliary\VS\UnitTest\include;$(IncludePath)</IncludePath>
<IncludePath>$(ProjectDir)include;$(VCInstallDir)Auxiliary\VS\UnitTest\include;$(IncludePath);$(ProjectDir)</IncludePath>
<LibraryPath>$(VCInstallDir)Auxiliary\VS\UnitTest\lib;$(LibraryPath)</LibraryPath>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
<CodeAnalysisRuleSet>..\CodeAnalysisRuleset.ruleset</CodeAnalysisRuleSet>
@ -251,7 +251,7 @@
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>UNIT_TESTING;OLC_PGE_HEADLESS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalIncludeDirectories>$(ProjectDir)steam;$(ProjectDir)discord-files;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>
@ -269,8 +269,7 @@
<Command>powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive -File update_version.ps1 "./version.h"</Command>
</PreBuildEvent>
<PostBuildEvent>
<Command>
</Command>
<Command>powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive -File ../unit-testing-prebuild.ps1</Command>
</PostBuildEvent>
<Lib>
<AdditionalDependencies>discord_game_sdk.dll.lib;freetype.lib;steam_api64.lib;$(CoreLibraryDependencies);%(AdditionalDependencies);</AdditionalDependencies>
@ -411,6 +410,7 @@
</SubType>
</ClInclude>
<ClInclude Include="CastInfo.h" />
<ClInclude Include="catch.h" />
<ClInclude Include="DynamicMenuLabel.h">
<SubType>
</SubType>
@ -421,6 +421,7 @@
</SubType>
</ClInclude>
<ClInclude Include="FriendlyType.h" />
<ClInclude Include="GameHelper.h" />
<ClInclude Include="HurtDamageInfo.h">
<SubType>
</SubType>
@ -747,10 +748,6 @@
<ClInclude Include="steam\steam_api_flat.h" />
<ClInclude Include="steam\steam_api_internal.h" />
<ClInclude Include="steam\steam_gameserver.h" />
<ClInclude Include="TEST_DEFINES.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="TextEntryLabel.h">
<SubType>
</SubType>
@ -1095,6 +1092,33 @@
</SubType>
</ClCompile>
<ClCompile Include="Spider.cpp" />
<ClCompile Include="tests\BuffTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="tests\EffectTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="tests\EnchantTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="tests\EngineTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="tests\FileTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="tests\GeometryTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="tests\ItemTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="tests\MonsterTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="tests\PlayerTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="ThunderOrb.cpp" />
<ClCompile Include="TileGroup.cpp" />
<ClCompile Include="Menu.cpp" />
@ -1337,6 +1361,7 @@
<None Include="..\runGame.bat" />
<None Include="..\runGame.sh" />
<None Include="..\unit-testing-prebuild.ps1" />
<None Include=".runsettings" />
<None Include="ClassDiagram.cd" />
<None Include="ClassDiagram2.cd" />
<None Include="cpp.hint" />
@ -1344,6 +1369,7 @@
<None Include="steam\steam_api.json" />
</ItemGroup>
<ItemGroup>
<Text Include="..\CMakeLists.txt" />
<Text Include="..\x64\Unit Testing\assets\config\items\Accessories-test.txt" />
<Text Include="..\x64\Unit Testing\assets\config\items\Equipment-test.txt" />
<Text Include="..\x64\Unit Testing\assets\config\items\ItemDatabase-test.txt" />

View File

@ -109,6 +109,12 @@
<Filter Include="Configurations\Items\Test Configurations">
<UniqueIdentifier>{354b389b-2ec1-4cf6-ace0-6597b14edfab}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Unit Tests">
<UniqueIdentifier>{38efdf17-2483-4a53-8598-e8cebb722137}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Unit Testing">
<UniqueIdentifier>{64d4cc9f-7c64-4939-9483-ddd2e3ad4bba}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="olcPixelGameEngine.h">
@ -666,9 +672,6 @@
<ClInclude Include="Pixel.h">
<Filter>Header Files\Engine</Filter>
</ClInclude>
<ClInclude Include="TEST_DEFINES.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Bullet.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -732,6 +735,12 @@
<ClInclude Include="Effect.h">
<Filter>Source Files\Effects</Filter>
</ClInclude>
<ClInclude Include="catch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="GameHelper.h">
<Filter>Header Files\Unit Testing</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Player.cpp">
@ -1385,6 +1394,33 @@
<ClCompile Include="Warrior.cpp">
<Filter>Source Files\Player Classes</Filter>
</ClCompile>
<ClCompile Include="tests\BuffTests.cpp">
<Filter>Source Files\Unit Tests</Filter>
</ClCompile>
<ClCompile Include="tests\EffectTests.cpp">
<Filter>Source Files\Unit Tests</Filter>
</ClCompile>
<ClCompile Include="tests\EnchantTests.cpp">
<Filter>Source Files\Unit Tests</Filter>
</ClCompile>
<ClCompile Include="tests\EngineTests.cpp">
<Filter>Source Files\Unit Tests</Filter>
</ClCompile>
<ClCompile Include="tests\GeometryTests.cpp">
<Filter>Source Files\Unit Tests</Filter>
</ClCompile>
<ClCompile Include="tests\FileTests.cpp">
<Filter>Source Files\Unit Tests</Filter>
</ClCompile>
<ClCompile Include="tests\ItemTests.cpp">
<Filter>Source Files\Unit Tests</Filter>
</ClCompile>
<ClCompile Include="tests\MonsterTests.cpp">
<Filter>Source Files\Unit Tests</Filter>
</ClCompile>
<ClCompile Include="tests\PlayerTests.cpp">
<Filter>Source Files\Unit Tests</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="cpp.hint" />
@ -1451,6 +1487,7 @@
<None Include="..\.gitea\workflows\emscripten_autobuild.yaml">
<Filter>Configurations</Filter>
</None>
<None Include=".runsettings" />
</ItemGroup>
<ItemGroup>
<Text Include="InitialConcept.txt">
@ -1674,6 +1711,7 @@
<Filter>Documentation\Mechanics</Filter>
</Text>
<Text Include="Chapter_4_Boss.txt" />
<Text Include="..\CMakeLists.txt" />
</ItemGroup>
<ItemGroup>
<Image Include="assets\heart.ico">

View File

@ -73,9 +73,11 @@ All rights reserved.
#include "olcPGEX_Gamepad.h"
#include "InventoryScrollableWindowComponent.h"
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
#include "discord.h"
#include "steam/steam_api.h"
#endif
#endif
#include "GameSettings.h"
#include "LoadingScreen.h"
#include "Tutorial.h"
@ -162,8 +164,10 @@ InputGroup AiL::KEY_MOUSE_RIGHT;
InputGroup AiL::KEY_TOGGLE_MAP;
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
::discord::Core*Discord{};
#endif
#endif
float AiL::SIZE_CHANGE_SPEED=1;
@ -294,8 +298,9 @@ void InitializeGameConfigurations(){
bool AiL::OnUserCreate(){
if(PACK_KEY=="INSERT_PACK_KEY_HERE"||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);
#ifndef OLC_PGE_HEADLESS
GamePad::init();
#endif
Input::Initialize();
Font::init();
@ -376,6 +381,7 @@ bool AiL::OnUserCreate(){
)
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
if(steamKeyboardCallbackListener==nullptr){
steamKeyboardCallbackListener=new SteamKeyboardCallbackHandler();
}
@ -383,13 +389,16 @@ bool AiL::OnUserCreate(){
steamStatsReceivedHandlerListener=new SteamStatsReceivedHandler();
}
#endif
#endif
utils::datafile::INITIAL_SETUP_COMPLETE=true;
ValidateGameStatus(); //Checks to make sure everything has been initialized properly.
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
SetupDiscord();
#endif
#endif
player->InitializeMinimapImage();
minimap.Initialize();
@ -497,6 +506,7 @@ bool AiL::OnUserUpdate(float fElapsedTime){
LoadingScreen::Draw();
RenderVersionInfo();
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
if(Discord){
auto result=Discord->RunCallbacks();
if(result!=::discord::Result::Ok){
@ -506,6 +516,7 @@ bool AiL::OnUserUpdate(float fElapsedTime){
}
}
#endif
#endif
}
skipGameProcess:
@ -2688,6 +2699,9 @@ bool AiL::IsForegroundTile(TilesheetData sheet,int tileID){
const TilesheetData AiL::GetTileSheet(MapName map,int tileID)const{
const std::vector<TilesetTag>&tileData=MAP_DATA.at(map).TilesetData;
#ifdef UNIT_TESTING
return{tileData[0].baseSourceDir,1,BLACK};
#endif
if(tileData.size()==1){
return {tileData[0].baseSourceDir,1,MAP_TILESETS.at(tileData[0].baseSourceDir).tilecols[tileID]};
}else{
@ -2957,6 +2971,7 @@ datafiledoubledata AiL::GetDoubleList(std::string key){
bool Steam_Init(){
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
if(SteamAPI_Init()){
if(SteamUtils()!=nullptr){
SteamUtils()->SetWarningMessageHook([](int severity,const char*message){
@ -2969,133 +2984,144 @@ bool Steam_Init(){
return true;
}
#endif
#endif
return false;
}
int main(const int argn,char**args)
{
debugLogger.open("debug.log");
LOG(std::format("Found {} args",argn));
bool usingSteam=true;
#ifdef UNIT_TESTING
#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
#include "catch.h"
std::set_terminate([](){
try{
std::exception_ptr eptr{std::current_exception()};
if(eptr)std::rethrow_exception(eptr);
else std::cerr<<"Exiting without exception\n";
}catch(const std::exception& ex){std::cerr<<"Exception: "<<ex.what()<< '\n';}
catch(...){std::cerr << "Unknown exception caught\n";}
#ifndef __EMSCRIPTEN__
LOG(std::stacktrace::current());
#endif
std::exit(EXIT_FAILURE);
});
#else
int main(const int argn,char**args)
{
debugLogger.open("debug.log");
LOG(std::format("Found {} args",argn));
bool usingSteam=true;
for(int i=0;i<argn;i++){
if(std::string(args[i])=="nosteam"){
LOG("nosteam flag detected. Disabling steam API...");
usingSteam=false;
}
LOG(std::format("{}: {}",i,args[i]));
}
if(!std::filesystem::exists("assets/config/configuration.txt")){
ERR("WARNING! Could not find initial config file! Aborting.");
return false;
}
{
utils::datafile configFile;
utils::datafile::Read(configFile,"assets/config/configuration.txt");
usingSteam&=configFile["steam_api"].GetBool();
if(usingSteam){
std::set_terminate([](){
try{
std::exception_ptr eptr{std::current_exception()};
if(eptr)std::rethrow_exception(eptr);
else std::cerr<<"Exiting without exception\n";
}catch(const std::exception& ex){std::cerr<<"Exception: "<<ex.what()<< '\n';}
catch(...){std::cerr << "Unknown exception caught\n";}
#ifndef __EMSCRIPTEN__
if(!ADMIN_MODE&&SteamAPI_RestartAppIfNecessary(2895980U))return false; //Immediately quit if steam is detected and can be started through it.
if(Steam_Init()){
LOG("Steam API Initialized successfully!");
}else{
LOG("Steam API failed to initialize!");
}
#endif
LOG(std::stacktrace::current());
#endif
std::exit(EXIT_FAILURE);
});
for(int i=0;i<argn;i++){
if(std::string(args[i])=="nosteam"){
LOG("nosteam flag detected. Disabling steam API...");
usingSteam=false;
}
LOG(std::format("{}: {}",i,args[i]));
}
}
{
InitializeGameConfigurations();
AiL demo;
demo.UsingSteamAPI(usingSteam);
#pragma region Load Window Settings
utils::datafile loadSystemFile;
std::string loadSystemFilename="save_file_path"_S+"system.conf";
vi2d windowPosConf={30,30};
vi2d windowSizeConf=WINDOW_SIZE*4;
bool fullscreenConf=false;
bool vsyncConf=GameSettings::VSyncEnabled();
if(std::filesystem::exists(loadSystemFilename)){
utils::datafile::Read(loadSystemFile,loadSystemFilename);
if(loadSystemFile.HasProperty("Window Pos")){
GameSettings::SetWindowPos({loadSystemFile["Window Pos"].GetInt(0),loadSystemFile["Window Pos"].GetInt(1)});
windowPosConf={loadSystemFile["Window Pos"].GetInt(0),loadSystemFile["Window Pos"].GetInt(1)};
}
if(loadSystemFile.HasProperty("Window Size"))windowSizeConf={loadSystemFile["Window Size"].GetInt(0),loadSystemFile["Window Size"].GetInt(1)};
size_t requiredSize=0;
#ifdef WIN32
getenv_s(&requiredSize,NULL,0,"SteamTenfoot");
#else
const char*bigPicture=getenv("SteamTenfoot");
if(bigPicture){
requiredSize=strlen(bigPicture);
LOG("Big Picture reported a length of "<<requiredSize);
if(!std::filesystem::exists("assets/config/configuration.txt")){
ERR("WARNING! Could not find initial config file! Aborting.");
return false;
}
{
utils::datafile configFile;
utils::datafile::Read(configFile,"assets/config/configuration.txt");
usingSteam&=configFile["steam_api"].GetBool();
if(usingSteam){
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
if(!ADMIN_MODE&&SteamAPI_RestartAppIfNecessary(2895980U))return false; //Immediately quit if steam is detected and can be started through it.
if(Steam_Init()){
LOG("Steam API Initialized successfully!");
}else{
LOG("Steam API failed to initialize!");
}
#endif
#endif
if(loadSystemFile.HasProperty("Fullscreen"))fullscreenConf=loadSystemFile["Fullscreen"].GetBool();
if(loadSystemFile.HasProperty("VSync"))vsyncConf=loadSystemFile["VSync"].GetBool();
if(requiredSize>0)fullscreenConf=true;
}
#pragma endregion
}
{
InitializeGameConfigurations();
AiL demo;
demo.UsingSteamAPI(usingSteam);
if (demo.Construct(windowPosConf.x, windowPosConf.y, windowSizeConf.x, windowSizeConf.y, WINDOW_SIZE.x, WINDOW_SIZE.y, 4, 4, fullscreenConf, vsyncConf))
demo.Start();
}
#pragma region Load Window Settings
utils::datafile loadSystemFile;
#ifdef _DEBUG
#ifndef __EMSCRIPTEN__
#ifndef __linux__
HANDLE hLogFile;
hLogFile = CreateFile(L"assets/memoryleak.txt", GENERIC_WRITE,
FILE_SHARE_WRITE, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
_CrtSetReportMode(_CRT_WARN,_CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN,hLogFile);
_CrtDumpMemoryLeaks();
CloseHandle(hLogFile);
std::string loadSystemFilename="save_file_path"_S+"system.conf";
std::ifstream file("assets/memoryleak.txt");
bool leaked=false;
while(file.good()){
std::string line;
std::getline(file,line);
if(line.find("AiL\\")!=std::string::npos){
if(!leaked){
leaked=true;
LOG(std::endl<<std::endl<<std::endl<<"Memory leak detected!");
}
LOG(line);
std::getline(file,line);
LOG(line);
}
}
if(leaked)ERR("")
#endif
#endif
#endif
vi2d windowPosConf={30,30};
vi2d windowSizeConf=WINDOW_SIZE*4;
bool fullscreenConf=false;
bool vsyncConf=GameSettings::VSyncEnabled();
return 0;
}
if(std::filesystem::exists(loadSystemFilename)){
utils::datafile::Read(loadSystemFile,loadSystemFilename);
if(loadSystemFile.HasProperty("Window Pos")){
GameSettings::SetWindowPos({loadSystemFile["Window Pos"].GetInt(0),loadSystemFile["Window Pos"].GetInt(1)});
windowPosConf={loadSystemFile["Window Pos"].GetInt(0),loadSystemFile["Window Pos"].GetInt(1)};
}
if(loadSystemFile.HasProperty("Window Size"))windowSizeConf={loadSystemFile["Window Size"].GetInt(0),loadSystemFile["Window Size"].GetInt(1)};
size_t requiredSize=0;
#ifdef WIN32
getenv_s(&requiredSize,NULL,0,"SteamTenfoot");
#else
const char*bigPicture=getenv("SteamTenfoot");
if(bigPicture){
requiredSize=strlen(bigPicture);
LOG("Big Picture reported a length of "<<requiredSize);
}
#endif
if(loadSystemFile.HasProperty("Fullscreen"))fullscreenConf=loadSystemFile["Fullscreen"].GetBool();
if(loadSystemFile.HasProperty("VSync"))vsyncConf=loadSystemFile["VSync"].GetBool();
if(requiredSize>0)fullscreenConf=true;
}
#pragma endregion
if (demo.Construct(windowPosConf.x, windowPosConf.y, windowSizeConf.x, windowSizeConf.y, WINDOW_SIZE.x, WINDOW_SIZE.y, 4, 4, fullscreenConf, vsyncConf))
demo.Start();
}
#ifdef _DEBUG
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
#ifndef __linux__
HANDLE hLogFile;
hLogFile = CreateFile(L"assets/memoryleak.txt", GENERIC_WRITE,
FILE_SHARE_WRITE, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
_CrtSetReportMode(_CRT_WARN,_CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN,hLogFile);
_CrtDumpMemoryLeaks();
CloseHandle(hLogFile);
std::ifstream file("assets/memoryleak.txt");
bool leaked=false;
while(file.good()){
std::string line;
std::getline(file,line);
if(line.find("AiL\\")!=std::string::npos){
if(!leaked){
leaked=true;
LOG(std::endl<<std::endl<<std::endl<<"Memory leak detected!");
}
LOG(line);
std::getline(file,line);
LOG(line);
}
}
if(leaked)ERR("")
#endif
#endif
#endif
#endif
return 0;
}
#endif
#ifndef _DEBUG
#ifdef _WIN32
@ -3228,8 +3254,10 @@ bool AiL::OnUserDestroy(){
gameEnd=true;
if(!savingFile){
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
SteamAPI_Shutdown();
#endif
#endif
GFX.Reset();
for(auto&map:MAP_DATA){
if(map.optimizedTile!=nullptr){
@ -3822,7 +3850,7 @@ const std::weak_ptr<Item>AiL::GetLoadoutItem(int slot){
void AiL::RestockLoadoutItems(){
for(int slot=0;slot<GetLoadoutSize();slot++){
if(!ISBLANK(GetLoadoutItem(slot))){
if(!GetLoadoutItem(slot).expired()&&GetLoadoutItem(slot).lock()->it!=nullptr){
SetLoadoutItem(slot,GetLoadoutItem(slot).lock()->ActualName());
//Set the loadout slot selection for this loadout item.
@ -3907,7 +3935,7 @@ void AiL::SetLoadoutItem(int slot,std::string itemName){
bool AiL::UseLoadoutItem(int slot,const std::optional<vf2d>targetingPos){
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){
if(!ISBLANK(GetLoadoutItem(slot))&&GetLoadoutItem(slot).lock()->Amt()>0){
Tutorial::GetTask(TutorialTaskName::USE_RECOVERY_ITEMS).I(A::ITEM_USE_COUNT)++;
Inventory::UseItem(GetLoadoutItem(slot).lock()->ActualName(),1U,targetingPos);
if(GameState::GetCurrentState()!=States::GAME_HUB){
@ -4028,6 +4056,7 @@ void AiL::EndGame(){
}
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
::discord::Result AiL::SetupDiscord(){
auto result=::discord::Core::Create(1186719371750555780,DiscordCreateFlags_NoRequireDiscord,&Discord);
if(result==::discord::Result::Ok){
@ -4043,11 +4072,13 @@ void AiL::EndGame(){
return result;
}
#endif
#endif
void AiL::UpdateDiscordStatus(std::string levelName,std::string className){
std::string originalClassName=className;
bool retry=false;
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
if(Discord){
::discord::Activity newActivity{};
newActivity.SetDetails(levelName.c_str());
@ -4094,6 +4125,7 @@ void AiL::UpdateDiscordStatus(std::string levelName,std::string className){
UpdateDiscordStatus(levelName,className);
}
#endif
#endif
}
void AiL::InitializePlayerLevelCap(){
@ -4276,14 +4308,14 @@ const bool AiL::GameInitialized()const {
rcode AiL::LoadResource(Renderable&renderable,std::string_view imgPath,bool filter,bool clamp){
rcode returnCode;
if(gamepack.Loaded()){
returnCode=renderable.Load(std::string(imgPath),&gamepack,filter,clamp);
}else{
returnCode=renderable.Load(std::string(imgPath),nullptr,filter,clamp);
if("GENERATE_GAMEPACK"_B){
gamepack.AddFile(std::string(imgPath));
if(gamepack.Loaded()){
returnCode=renderable.Load(std::string(imgPath),&gamepack,filter,clamp);
}else{
returnCode=renderable.Load(std::string(imgPath),nullptr,filter,clamp);
if("GENERATE_GAMEPACK"_B){
gamepack.AddFile(std::string(imgPath));
}
}
}
return returnCode;
}

View File

@ -49,8 +49,10 @@ All rights reserved.
#include "olcUTIL_DataFile.h"
#include "GameState.h"
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
#include "discord.h"
#endif
#endif
#include "Audio.h"
#include "olcPGEX_SplashScreen.h"
#include "olcPixelGameEngine.h"
@ -60,6 +62,7 @@ All rights reserved.
#include "Overlay.h"
#include <variant>
#include"safemap.h"
#include"TileGroup.h"
#undef KEY_MENU
#undef KEY_SELECT
@ -448,8 +451,10 @@ private:
//This function assigns the mode tile colors of each loaded tileset.
void ComputeModeColors(TilesetData&tileset);
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
::discord::Result SetupDiscord();
#endif
#endif
Audio audioEngine;
SteamKeyboardCallbackHandler*steamKeyboardCallbackListener=nullptr;
SteamStatsReceivedHandler*steamStatsReceivedHandlerListener=nullptr;
@ -467,3 +472,8 @@ private:
Pixel vignetteOverlayCol{"Interface.Vignette Color"_Pixel};
std::vector<Notification>notifications;
};
#undef min
#undef max
#undef GetClassName

View File

@ -60,7 +60,8 @@ public:
static void Update();
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);
//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);

View File

@ -52,8 +52,10 @@ All rights reserved.
#include "ProgressBar.h"
#include "MenuItemLabel.h"
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
#include "steam/isteamuserstats.h"
#endif
#endif
#include <bit>
INCLUDE_game

View File

@ -46,8 +46,10 @@ All rights reserved.
#include "TextEntryLabel.h"
#include "Checkbox.h"
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
#include "steam/isteamutils.h"
#endif
#endif
INCLUDE_game
INCLUDE_DEMO_BUILD

View File

@ -67,14 +67,8 @@ private:
using EffectData=EffectManager::EffectData;
namespace EffectTests{
class EffectTest;
};
template<typename T>
struct EffectRef{
friend class EffectTests::EffectTest;
public:
inline EffectRef(int slotId,int effectId,bool foregroundEffect):slotId(slotId),effectId(effectId),foregroundEffect(foregroundEffect){}
@ -92,7 +86,11 @@ public:
if(expired())ERR("WARNING! This effect was not properly checked for expiration! THIS IS NOT ALLOWED!");
return dynamic_cast<T&>(EFF(*effectList.at(slotId).effect));
};
#ifndef UNIT_TESTING
private:
#else
public:
#endif
int slotId;
int effectId;
bool foregroundEffect{false};

View File

@ -58,14 +58,9 @@ enum class EffectType{
BLIZZARD_SNOW,
};
namespace EffectTests{
class EffectTest;
};
struct Effect{
friend class AiL;
friend struct FallingBullet;
friend class EffectTests::EffectTest;
vf2d pos={0,0};
float lifetime=0;
float fadeout=0;
@ -100,7 +95,11 @@ protected:
EffectType type{EffectType::NONE};
Animate2D::Animation<std::string>animation;
Animate2D::AnimationState internal_animState;
#ifndef UNIT_TESTING
private:
#else
public:
#endif
bool upperLevel=false;
double aliveTime{};
};

View File

@ -10,9 +10,6 @@ INCLUDE_game
debugLogger<<finalOutput.str()<<std::endl;
std::cout<<finalOutput.str()<<std::endl;
if(game&&game->ConsoleOut()&&game->ConsoleOut().good())game->ConsoleOut()<<finalOutput.str()<<std::endl;
#ifdef UNITTESTING
Logger::WriteMessage((finalOutput.str()+"\n").c_str());
#endif
}
void Error::log(std::stringstream&str,std::source_location loc){
std::stringstream finalOutput;
@ -22,9 +19,6 @@ INCLUDE_game
if(game&&game->ConsoleOut()&&game->ConsoleOut().good()){
game->ConsoleOut()<<finalOutput.str()<<std::endl;
}
#ifdef UNITTESTING
Logger::WriteMessage((finalOutput.str()+"\n").c_str());
#endif
throw std::runtime_error{finalOutput.str()};
}
#else

View File

@ -38,7 +38,6 @@ All rights reserved.
#pragma once
#include <iostream>
#include <sstream>
#include <strstream>
#include <format>
#include <any>
#include <memory>
@ -48,21 +47,19 @@ All rights reserved.
#include <stacktrace>
#endif
#include<utility>
#ifdef UNITTESTING
#include "CppUnitTest.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
#endif
inline std::ofstream debugLogger;
#ifdef _DEBUG
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
#ifndef __linux__
#define NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
// Replace _NORMAL_BLOCK with _CLIENT_BLOCK if you want the
// allocations to be of _CLIENT_BLOCK type
#endif
#endif
#endif
#endif
#ifndef NEW //For everything else.

View File

@ -0,0 +1,116 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2026 Amy 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"catch.h"
INCLUDE_game
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->ResetPlayerAndChangeClass(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;
}
//Adds the buff directly to the player instead of the buffs added list. (By calling an update tick.)
inline void AddBuffToPlayer(BuffType type,float duration,float intensity){
game->GetPlayer()->AddBuff(type,duration,intensity);
Update(0.f);
}
//NOTE: If adding a % increase stat, please use the percentage version! 100% = 1!!
inline void AddBuffToPlayer(BuffType type,float duration,float intensity,std::set<ItemAttribute>attr){
game->GetPlayer()->AddBuff(type,duration,intensity,attr);
Update(0.f);
}
//NOTE: If adding a % increase stat, please use the percentage version! 100% = 1!!
inline void AddBuffToPlayer(BuffType type,float duration,float intensity,std::set<std::string>attr){
game->GetPlayer()->AddBuff(type,duration,intensity,attr);
Update(0.f);
}
inline void ResetPlayerAndChangeClass(Class cl,Player*&pl,AiL*const game){
game->ResetPlayerAndChangeClass(cl);
pl=game->GetPlayer(); //The player pointer has been reassigned...
}
inline void LoadLevelWithTMX(const std::string&mapKeyname,AiL*const game){
game->InitializeLevel("map_path"_S+DATA["Levels"][mapKeyname]["Map File"].GetString(),mapKeyname);
game->SetupLevel(game->MAP_DATA[mapKeyname]);
_SetCurrentLevel(mapKeyname);
}
inline void LoadFakeLevel(const std::string&mapKeyname,AiL*const game){
game->MAP_DATA.at(mapKeyname).name=mapKeyname;
game->SetupLevel(game->MAP_DATA[mapKeyname]);
_SetCurrentLevel(mapKeyname);
}
//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.
inline void _SetCurrentLevel(const MapName map){
game->ResetLevelStates();
game->currentLevel=map;
}
}
namespace Test
{
template<class T>
inline static void InRange(T initialVal,std::pair<T,T>range){
REQUIRE((((initialVal)>=range.first)&&((initialVal)<=range.second)));
}
}

View File

@ -48,8 +48,10 @@ All rights reserved.
#include "ClassInfo.h"
#include "RowInventoryScrollableWindowComponent.h"
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
#include "steam/isteamuserstats.h"
#endif
#endif
#include <ranges>
#include "ItemEnchant.h"
#include<algorithm>
@ -675,11 +677,9 @@ uint32_t Item::Amt()const{
return amt;
};
const std::string&Item::ActualName()const{
if(_IsBlank())return BLANK_ITEM_NAME;
return it->Name();
};
const std::string Item::DisplayName()const{
if(_IsBlank())return BLANK_ITEM_NAME;
std::string name=ActualName();
if(IsEquippable()&&EnhancementLevel()>0){
name+=" [#00FF00+"+std::to_string(EnhancementLevel())+"#FFFFFF]";

View File

@ -167,14 +167,6 @@ public:
const std::vector<std::pair<int,Stats>>&GetSetBonusBreakpoints()const;
};
namespace PlayerTests{
class PlayerTest;
};
namespace EnchantTests{
class EnchantTest;
};
using IncreaseAmount=int;
using RefineResult=std::pair<ItemAttribute,IncreaseAmount>;
@ -185,8 +177,6 @@ class Item{
friend class SaveFile;
friend void Merchant::PurchaseItem(IT item,uint32_t amt);
friend void Merchant::SellItem(std::weak_ptr<Item>,uint32_t amt);
friend class PlayerTests::PlayerTest;
friend class EnchantTests::EnchantTest;
public:
static const std::string BLANK_ITEM_NAME;
Item();
@ -264,7 +254,11 @@ public:
static const bool SelectedEquipIsDifferent(const std::weak_ptr<Item>equipAttemptingToEquip,const EquipSlot slotToEquipTo);
static std::vector<std::string>GetItemCategories();
const std::optional<std::string>&FragmentIcon()const;
#ifndef UNIT_TESTING
private:
#else
public:
#endif
//The amount in the current item stack.
uint32_t amt;
uint8_t enhancementLevel;
@ -284,7 +278,6 @@ class Inventory{
friend class Item;
friend class SaveFile;
friend class InventoryCreator;
friend class ItemTests::ItemTest;
public:
enum class DisassembleResult{
@ -335,7 +328,11 @@ private:
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;
#ifdef UNIT_TESTING
public:
#endif
static std::array<std::pair<IT,int>,3U>loadoutItemsUsed;
private:
//Only contains "1" of every item, as this is a map to index items and not the actual storage of items!
static std::map<ITCategory,std::vector<std::shared_ptr<Item>>>sortedInv;
};

View File

@ -39,10 +39,6 @@ All rights reserved.
#include "util.h"
#include "AdventuresInLestoria.h"
namespace MonsterTests{
class MonsterTest;
}
class ItemDrop{
friend class AiL;
vf2d pos;

View File

@ -44,8 +44,6 @@ All rights reserved.
class ItemEnchantInfo{
friend class ItemEnchant;
friend class ItemTests::ItemTest;
friend class PlayerTests::PlayerTest;
public:
enum class TextStyle{
NORMAL,
@ -108,7 +106,11 @@ private:
ItemAttributable minStatModifiers;
ItemAttributable maxStatModifiers;
std::unordered_map<std::string,float>config;
#ifdef UNIT_TESTING
public:
#endif
static std::unordered_map<std::string,ItemEnchantInfo>ENCHANT_LIST;
private:
static std::unordered_map<ItemEnchantCategory,ItemEnchantCategoryData>ENCHANT_CATEGORIES;
AbilityDescriptionModifiers modifiers;
};

View File

@ -63,6 +63,7 @@ std::vector<Steam::SteamInput>Input::rightStickActionSets{Steam::SCROLL};
std::array<std::unordered_map<Steam::SteamInput,std::pair<std::string,HWButton>>,STEAM_INPUT_MAX_COUNT>Input::enumToActionName;
std::unordered_map<EInputActionOrigin,std::string>Input::steamIconToGameIcon{
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
{k_EInputActionOrigin_SteamController_A,"themes/button_d_xb.png"},
{k_EInputActionOrigin_XBoxOne_A,"themes/button_d_xb.png"},
{k_EInputActionOrigin_XBox360_A,"themes/button_d_xb.png"},
@ -141,6 +142,7 @@ std::unordered_map<EInputActionOrigin,std::string>Input::steamIconToGameIcon{
{k_EInputActionOrigin_SteamDeck_LeftStick_Move,"themes/button_analogstick.png"},
{k_EInputActionOrigin_SteamDeck_RightStick_Move,"themes/button_analogstick.png"},
#endif
#endif
};
Input::Input(InputType type,int key)
@ -178,6 +180,7 @@ void Input::Initialize(){
void Input::LoadSteamButtonIcons(){
GFX.Unlock();
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
for(int i=1;i<k_EInputActionOrigin_Count;i++){
const char*imageName{SteamInput()->GetGlyphPNGForActionOrigin(EInputActionOrigin(i),k_ESteamInputGlyphSize_Small,0U)};
if(imageName!=nullptr){
@ -192,6 +195,7 @@ void Input::LoadSteamButtonIcons(){
}
}
#endif
#endif
GFX.SetInitialized();
}
@ -913,6 +917,7 @@ const bool Input::HasExtendedIcons()const{
const Renderable&Input::GetIcon()const{
if(type==STEAM){
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
EInputActionOrigin action=Input::steamGameInputToOrigin.at(Steam::SteamInput(key)).second[0];
EInputActionOrigin analogAction=Input::steamGameInputToAnalogOrigin.at(Steam::SteamInput(key)).second[0];
if(Input::steamGameInputToOrigin.count(Steam::SteamInput(key))&&
@ -932,6 +937,7 @@ const Renderable&Input::GetIcon()const{
}
}
#endif
#endif
}
return GFX.at(GenericKey::keyLiteral.at({type,key}).iconName);
}
@ -1118,31 +1124,35 @@ const bool Input::AxesActive(){
}
void Input::StartVibration(const bool override){
if(!GameSettings::RumbleEnabled(override))return;
if(UsingGamepad()){
STEAMINPUT(
SteamInput()->TriggerVibration(steamControllers[activeSteamControllerIndex],std::numeric_limits<unsigned short>::max(),std::numeric_limits<unsigned short>::max());
)else{
for(GamePad*gamepad:GamePad::getGamepads()){
if(gamepad->stillConnected){
gamepad->startVibration();
#ifndef OLC_PGE_HEADLESS
if(!GameSettings::RumbleEnabled(override))return;
if(UsingGamepad()){
STEAMINPUT(
SteamInput()->TriggerVibration(steamControllers[activeSteamControllerIndex],std::numeric_limits<unsigned short>::max(),std::numeric_limits<unsigned short>::max());
)else{
for(GamePad*gamepad:GamePad::getGamepads()){
if(gamepad->stillConnected){
gamepad->startVibration();
}
}
}
}
}
#endif
}
void Input::StopVibration(){
STEAMINPUT(
for(int i=0;i<controllerCount;i++){
SteamInput()->TriggerVibration(steamControllers[i],0U,0U);
}
)else{
for(GamePad*gamepad:GamePad::getGamepads()){
if(gamepad->stillConnected){
gamepad->stopVibration();
#ifndef OLC_PGE_HEADLESS
STEAMINPUT(
for(int i=0;i<controllerCount;i++){
SteamInput()->TriggerVibration(steamControllers[i],0U,0U);
}
)else{
for(GamePad*gamepad:GamePad::getGamepads()){
if(gamepad->stillConnected){
gamepad->stopVibration();
}
}
}
}
#endif
}
const bool operator<(const InputGroup&group1,const InputGroup&group2){

View File

@ -47,8 +47,10 @@ All rights reserved.
#include "olcPGEX_ViewPort.h"
#include "UndefKeys.h"
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
#include "steam/isteaminput.h"
#endif
#endif
#include "emscripten_compat.h"
class AiL;
@ -149,10 +151,6 @@ public:
static uint64_t ingameControlsHandle;
};
namespace PlayerTests{
class PlayerTest;
}
class InputGroup{
friend class AiL;
friend class Menu;
@ -160,7 +158,6 @@ class InputGroup{
friend class InputListener;
friend class SaveFile;
friend class State_MainMenu;
friend class PlayerTests::PlayerTest;
std::set<Input>keys;
std::vector<Input>keyOrder; //A list of keys inserted in order, for primary key mapping.
static safemap<std::string,InputGroup*>menuNamesToInputGroups;

View File

@ -91,9 +91,11 @@ void Menu::InitializeLoadGameWindow(){
},ButtonAttr::FIT_TO_LABEL)END;
onlineCharacterTab->SetSelectionType(HIGHLIGHT);
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
offlineCharacterTab->Disable();
onlineCharacterTab->Disable();
#endif
#endif
#pragma region Keyboard Navigation Rules
loadGameWindow->SetupKeyboardNavigation(

View File

@ -123,7 +123,7 @@ class Menu:public IAttributable{
friend class AiL;
friend class ItemInfo;
friend class EntityStats;
friend class InputListener;
friend class ::InputListener;
float buttonHoldTime=0;
static vi2d lastActiveMousePos;

View File

@ -49,8 +49,10 @@ All rights reserved.
#include "SoundEffect.h"
#include "Unlock.h"
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
#include "steam/isteamuserstats.h"
#endif
#endif
#include "GameSettings.h"
#include "ItemEnchant.h"
#include <ranges>

View File

@ -63,10 +63,6 @@ enum class Attribute;
class GameEvent;
namespace MonsterTests{
class MonsterTest;
};
class Entity;
class DeathSpawnInfo{
@ -82,7 +78,6 @@ class Monster:public IAttributable{
friend class AiL;
friend class InventoryCreator;
friend class DeathSpawnInfo;
friend class MonsterTests::MonsterTest;
friend struct MonsterData;
friend const bool Entity::IsBoss()const;
friend class Entity;
@ -383,8 +378,14 @@ private:
float targetAcquireTimer=0;
vf2d spawnPos;
int hp{0};
#ifdef UNIT_TESTING
public:
#endif
int mp{0};
float mpRemainder{0.f};
#ifdef UNIT_TESTING
private:
#endif
ItemAttributable stats;
float size;
float attackCooldownTimer=0;
@ -392,7 +393,13 @@ private:
float z=0;
float iframe_timer=0;
Direction facingDirection;
#ifdef UNIT_TESTING
public:
#endif
std::string strategy;
#ifdef UNIT_TESTING
private:
#endif
State::State state=State::NORMAL;
std::string overlaySprite="";
uint8_t overlaySpriteTransparency=0U;
@ -419,7 +426,13 @@ private:
std::shared_ptr<DamageNumber>damageNumberPtr;
std::shared_ptr<DamageNumber>dotNumberPtr;
std::unordered_map<StrategyName,int>phase{}; //NOTE: THIS SHOULD NOT BE MODIFIED DIRECTLY!!! Use SetPhase(), GetPhase() / PHASE() SETPHASE() macros!
#ifdef UNIT_TESTING
public:
#endif
bool diesNormally=true; //If set to false, the monster death is handled in a special way. Set it to true when it's time to die.
#ifdef UNIT_TESTING
private:
#endif
float targetSize=0;
bool isBoss=false;
void OnDeath();
@ -472,7 +485,13 @@ private:
vf2d addedVel{};
std::weak_ptr<Monster>attachedTarget; //A monster attached to another monster can then use this to alter behaviors based on the state of that other monster.
float unconsciousTimer{};
#ifdef UNIT_TESTING
public:
#endif
const bool IsUnconscious()const;
#ifdef UNIT_TESTING
private:
#endif
const float UnconsciousTime()const;
bool manualIgnoreTerrain{false}; //A manual flag that can be toggled on to dynamically make this monster ignore terrain collision.
float collisionRadius{}; //The collision radius can be modified, it's just set initially to the monster database entry.

View File

@ -70,12 +70,7 @@ struct MonsterAbilityData{
std::optional<float>radius;
};
namespace MonsterTests{
class MonsterTest;
};
struct MonsterData{
friend class MonsterTests::MonsterTest;
public:
MonsterData();
MonsterData(std::string name,std::string displayName,int hp,int atk,const uint32_t xp,std::vector<MonsterDropData>drops,float moveSpd=100.f,float size=1.0f,std::string strategy="Run Towards",int collisionDmg=0);
@ -133,12 +128,24 @@ private:
int hp;
int atk;
int startingMP{0};
#ifdef UNIT_TESTING
public:
#endif
float mpRecovery{0.f}; //In mana/sec
#ifdef UNIT_TESTING
private:
#endif
uint32_t xp;
float moveSpd;//1.0=100%
float size;
std::unordered_set<std::string>animations;
#ifdef UNIT_TESTING
public:
#endif
std::vector<MonsterAbilityData>abilities{};
#ifdef UNIT_TESTING
private:
#endif
std::string displayName;
std::string idleAnimation="WARRIOR_IDLE_S"; //Represents the basic animation name, not the full animation name that ANIMATION_DATA indexes into!! (Ex. Not GREEN_SLIME_IDLE, but IDLE)
std::string jumpAnimation="WARRIOR_IDLE_S"; //Represents the basic animation name, not the full animation name that ANIMATION_DATA indexes into!! (Ex. Not GREEN_SLIME_JUMP, but JUMP)
@ -146,7 +153,13 @@ private:
std::string deathAnimation="WARRIOR_IDLE_S"; //Represents the basic animation name, not the full animation name that ANIMATION_DATA indexes into!! (Ex. Not GREEN_SLIME_DEATH, but DEATH)
std::string strategy;
int collisionDmg;
#ifdef UNIT_TESTING
public:
#endif
EventName hurtSound="";
#ifdef UNIT_TESTING
private:
#endif
EventName deathSound="";
EventName walkSound="";
std::vector<MonsterDropData> dropData;

View File

@ -60,8 +60,10 @@ All rights reserved.
#include <ranges>
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
#include "steam/isteamuserstats.h"
#endif
#endif
INCLUDE_MONSTER_DATA
INCLUDE_MONSTER_LIST

View File

@ -64,10 +64,6 @@ namespace MoveFlag{
};
};
namespace PlayerTest{
class PlayerTests;
}
class EntityStats{
friend class Inventory;
ItemAttributable equipStats; //The stats after gear calculations are applied.

View File

@ -51,13 +51,7 @@ private:
static std::unordered_set<size_t>playingSoundEffects;
};
namespace MonsterTests{
class MonsterTest;
};
class SoundEffect{
friend class PlayerTests::PlayerTest;
friend class MonsterTests::MonsterTest;
public:
SoundEffect(const std::string_view filename,const float&vol,const float&minPitch=0.9f,const float&maxPitch=1.1f,const bool combatSound=false,const bool treatAsBGM=false);
static void PlaySFX(const std::string&eventName,const vf2d&pos);
@ -77,5 +71,11 @@ private:
bool combatSound=false;
float minPitch=0.9f;
float maxPitch=1.1f;
#ifdef UNIT_TESTING
public:
#endif
inline static std::unordered_map<std::string,int>soundsPlayedLog{};
#ifdef UNIT_TESTING
private:
#endif
};

View File

@ -43,8 +43,10 @@ All rights reserved.
#include "ItemDrop.h"
#include "util.h"
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
#include "steam/isteamuserstats.h"
#endif
#endif
INCLUDE_game

View File

@ -40,6 +40,7 @@ All rights reserved.
#include "emscripten_compat.h"
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
void SteamKeyboardCallbackHandler::TextEntryComplete( GamepadTextInputDismissed_t* pCallback ){
STEAMUTILS(
if(pCallback->m_bSubmitted){
@ -55,4 +56,5 @@ All rights reserved.
}else Component<MenuComponent>(SAVE_FILE_NAME,"Back Button")->Click();
)
};
#endif
#endif

View File

@ -37,13 +37,17 @@ All rights reserved.
#pragma endregion
#pragma once
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
#include "steam/steam_api.h"
#endif
#endif
#include <string>
#include "TextEntryLabel.h"
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
class SteamKeyboardCallbackHandler{
STEAM_CALLBACK(SteamKeyboardCallbackHandler,TextEntryComplete,GamepadTextInputDismissed_t);
};
#endif
#endif

View File

@ -45,10 +45,12 @@ All rights reserved.
INCLUDE_DATA
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
void SteamStatsReceivedHandler::SteamStatsReceived( UserStatsReceived_t* pCallback ){
if(pCallback->m_eResult==k_EResultOK){
SteamUserStats()->GetStat("Achievement.Kill Unlocks.Total Kill API Name"_S.c_str(),&Unlock::monsterKillCount);
LOG(std::format("Retrieved monster kill count: {}",Unlock::monsterKillCount));
}
}
#endif
#endif

View File

@ -37,11 +37,15 @@ All rights reserved.
#pragma endregion
#pragma once
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
#include "steam/steam_api.h"
#endif
#endif
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
class SteamStatsReceivedHandler{
STEAM_CALLBACK(SteamStatsReceivedHandler,SteamStatsReceived,UserStatsReceived_t);
};
#endif
#endif

View File

@ -1,46 +0,0 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2026 Amy 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
namespace PlayerTests{
class PlayerTest;
}
namespace ItemTests{
class ItemTest;
}

View File

@ -123,14 +123,6 @@ struct NPCData{
NPCData(XMLTag npcTag);
};
namespace MonsterTests{
class MonsterTest;
}
namespace ItemTests{
class ItemTest;
}
namespace Game{
void LoadFakeLevel(const std::string&mapKeyname,AiL*const game);
};
@ -138,7 +130,6 @@ namespace Game{
struct Map{
friend class AiL;
friend class TMXParser;
friend class ItemTests::ItemTest;
friend void Game::LoadFakeLevel(const std::string&mapKeyname,AiL*const game);
enum class MapType{
DUNGEON,
@ -156,7 +147,11 @@ private:
std::vector<EnvironmentalAudio>environmentalAudioData;
std::vector<ItemMapData>stageLoot;
std::vector<NPCData>npcs;
#ifdef UNIT_TESTING
public:
#endif
MapType mapType{};
private:
std::string bgmSongName="";
std::string audioEvent{"Default Volume"};
std::unordered_map<Class,float>devCompletionTrialTime;

View File

@ -34,6 +34,10 @@ Qty Up/Down is finicky
Add item icons in crafting menu descriptions
HP Recovery can be too much
Make molotov bigger
Item quantity inside the item box
In-universe explanation of item sets
Battle Cry movement speed buff?
Add potential upgrade amount on description before upgrading
@ -43,6 +47,16 @@ Collect loadout items in field = add to count immediately?
Crafting equipment should take you back to the item you were at.
No damage / No loadout items used / Defeat All
- On repeats
- Incentives for bonus XP
Crab contact damage is high
Warrior Shield bash
Bug in objects in stages? (1-1 bridge)
DEMO
====

View File

@ -38,7 +38,6 @@ All rights reserved.
#include "Map.h"
#include "AdventuresInLestoria.h"
#include "safemap.h"
#include"TileGroupDataFile.h"
INCLUDE_game

View File

@ -1 +1,22 @@
#pragma once
#include"olcUTIL_Geometry2D.h"
struct TileGroup{
friend class TileGroupDataFile;
private:
geom2d::rect<int>range;
geom2d::rect<float>collisionRange={{},{}};
std::vector<TileRenderData>tiles;
int minX=0,minY=0,maxX=0,maxY=0;
public:
static float FADE_TIME;
//0-255. 255 indicates fully invisible.
static uint8_t FADE_AMT;
geom2d::rect<float>GetCollisionRange();
geom2d::rect<int>GetRange();
//The fade range is the bounds in which this tile group will be considered "in range" of a player, one tile in each direction further than its actual range.
geom2d::rect<int>GetFadeRange();
std::vector<TileRenderData>&GetTiles();
void InsertTile(TileRenderData tile);
float fadeFactor=0.f;
};

View File

@ -46,26 +46,6 @@ All rights reserved.
INCLUDE_PACK_KEY
INCLUDE_game
struct TileGroup{
friend class TileGroupDataFile;
private:
geom2d::rect<int>range;
geom2d::rect<float>collisionRange={{},{}};
std::vector<TileRenderData>tiles;
int minX=0,minY=0,maxX=0,maxY=0;
public:
static float FADE_TIME;
//0-255. 255 indicates fully invisible.
static uint8_t FADE_AMT;
geom2d::rect<float>GetCollisionRange();
geom2d::rect<int>GetRange();
//The fade range is the bounds in which this tile group will be considered "in range" of a player, one tile in each direction further than its actual range.
geom2d::rect<int>GetFadeRange();
std::vector<TileRenderData>&GetTiles();
void InsertTile(TileRenderData tile);
float fadeFactor=0.f;
};
enum TileReadTag:uint32_t{
TILESET_NAME,
FIRSTGID,

View File

@ -42,8 +42,10 @@ All rights reserved.
#include "DEFINES.h"
#include "emscripten_compat.h"
#ifndef __EMSCRIPTEN__
#ifndef UNIT_TESTING
#include "steam/isteamuserstats.h"
#endif
#endif
INCLUDE_DATA

View File

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

Binary file not shown.

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.11.0" class="Map" orientation="orthogonal" renderorder="right-down" width="192" height="156" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="109">
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="192" height="156" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="109">
<properties>
<property name="Backdrop" propertytype="Backdrop" value="forest"/>
<property name="Background Music" propertytype="BGM" value="foresty1_1"/>
<property name="Background Music" propertytype="BGM" value="foresty2"/>
<property name="Level Type" type="int" propertytype="LevelType" value="0"/>
</properties>
<tileset firstgid="1" source="../maps/Tilesheet_No_Shadow24x24.tsx"/>

View File

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

View File

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

View File

@ -6,12 +6,12 @@ WORLD_MAP = 2026-02-27 21:53:33.6568368
CAMPAIGN_1_1 = 2026-01-21 20:30:58.3723266
BOSS_1_B = 2024-09-13 21:28:58.8886132
BOSS_2 = 2026-01-21 20:30:58.4312572
BOSS_3 = 2026-04-13 20:07:59.9982330
BOSS_3_B = 2026-04-21 02:50:32.8922615
BOSS_3 = 2026-04-23 20:04:39.8919861
BOSS_3_B = 2026-04-23 20:04:39.8919861
CAMPAIGN_7_1 = 2024-09-13 21:28:58.8570782
CAMPAIGN_1_4 = 2024-09-13 21:41:17.0083532
CAMPAIGN_1_4 = 2026-04-28 20:00:03.4806604
BOSS_2_B = 2026-01-21 20:30:58.4322614
CAMPAIGN_1_2 = 2026-01-21 20:30:58.3736269
CAMPAIGN_1_2 = 2026-04-28 19:44:26.2017819
CAMPAIGN_1_3 = 2024-09-13 21:28:58.8532684
CAMPAIGN_1_5 = 2024-09-13 21:28:58.8557772
CAMPAIGN_4_8 = 2026-02-26 21:44:13.5419545
@ -21,10 +21,10 @@ CAMPAIGN_1_B1 = 2024-09-13 21:28:58.8605592
CAMPAIGN_1_8 = 2024-09-13 21:28:58.8595553
CAMPAIGN_4_5 = 2026-02-26 21:44:13.5315825
CAMPAIGN_2_1 = 2026-01-21 20:30:58.3756334
CAMPAIGN_2_2 = 2026-01-21 20:30:58.3769261
CAMPAIGN_2_3 = 2026-04-13 20:04:48.1801993
CAMPAIGN_2_2 = 2026-04-28 19:44:26.2126272
CAMPAIGN_2_3 = 2026-04-23 20:04:39.8896736
CAMPAIGN_2_4 = 2026-01-21 20:30:58.3804324
CAMPAIGN_2_5 = 2026-04-22 02:07:26.4326535
CAMPAIGN_2_5 = 2026-04-23 20:04:39.8906724
CAMPAIGN_2_6 = 2026-01-21 20:30:58.3836104
CAMPAIGN_2_7 = 2026-01-21 20:30:58.3846135
CAMPAIGN_2_8 = 2026-01-21 20:30:58.3866133
@ -38,7 +38,7 @@ CAMPAIGN_3_5 = 2026-01-21 20:30:58.3977329
CAMPAIGN_3_6 = 2026-01-21 20:30:58.4008008
CAMPAIGN_3_7 = 2026-01-21 20:30:58.4023071
CAMPAIGN_3_8 = 2026-01-21 20:30:58.4049027
CAMPAIGN_3_B1 = 2026-04-21 02:50:32.8922615
CAMPAIGN_3_B1 = 2026-04-23 20:04:39.8919861
CAMPAIGN_4_1 = 2026-04-13 18:08:48.5353335
CAMPAIGN_4_2 = 2026-02-26 21:44:13.5210572
CAMPAIGN_4_4 = 2026-02-26 21:44:13.5270745

View File

@ -20,6 +20,24 @@ BGM
}
}
#Song title followed by filenames for individual parts
foresty2
{
Track Name = Foresty 2
channel[0]=commercial_assets/foresty2.ogg
# Transition time between one phase to the next.
Fade Time = 2.0
Loop Repeat Start Point = 0.0s
Events
{
Default Volume = 70%
}
}
#Song title followed by filenames for individual parts
overworld
{

17999
Adventures in Lestoria/catch.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -16,3 +16,4 @@ _N"; std::string class::idle_e="WARRIOR_IDLE_E"; std::string class::idle_s="WARR
#define INCLUDE_ITEM_DATA
#define INCLUDE_LEVEL_NAMES
#define INCLUDE_GFX
#define TEST(cl, testCaseName) TEST_CASE_METHOD(cl,"["#cl"] "##testCaseName)

View File

@ -13,6 +13,22 @@ class GamepadTextInputDismissed_t{};
inline void SteamAPI_RunCallbacks(){};
#define STEAMINPUT(statement) if(false){}
#define STEAMUTILS(statement) if(false){}
#define STEAMUSERSTATS(statement) if(false){}
#else
#ifdef UNIT_TESTING
#define STEAM_INPUT_MAX_COUNT 0
#define STEAM_INPUT_MAX_ORIGINS 0
#define STEAM_CALLBACK(arg1,arg2,arg3)
enum EInputActionOrigin{};
using InputHandle_t=uint64_t;
using InputActionSetHandle_t=uint64_t;
class GamepadTextInputDismissed_t{};
inline void SteamAPI_RunCallbacks(){};
#define STEAMINPUT(statement) if(false){}
#define STEAMUTILS(statement) if(false){}
#define STEAMUSERSTATS(statement) if(false){}
@ -21,3 +37,4 @@ inline void SteamAPI_RunCallbacks(){};
#define STEAMUTILS(statement) if(SteamUtils()){statement}
#define STEAMUSERSTATS(statement) if(SteamUserStats()){statement}
#endif
#endif

View File

@ -280,7 +280,9 @@ namespace olc {
static void enumerateGamepads();
static GamePad *openGamepad(const std::string &path);
#ifndef OLC_PGE_HEADLESS
static X11::Display *display;
#endif
static std::optional<int> inotifyFd;
constexpr static const int32_t buttonCodes[GP_BUTTON_COUNT]{BTN_X,
@ -318,6 +320,7 @@ namespace olc {
#pragma endregion
#ifndef UNIT_TESTING
#ifdef OLC_PGE_GAMEPAD
#undef OLC_PGE_GAMEPAD
@ -1403,4 +1406,165 @@ std::vector<olc::GamePad *> olc::GamePad::gamepads;
#endif // OLC_PGE_GAMEPAD
#pragma endregion
#pragma endregion
#else
#ifdef OLC_PGE_GAMEPAD
#undef OLC_PGE_GAMEPAD
#pragma region Platform Dependent
#pragma region Windows
#ifdef WIN32
BOOL IsXInputDevice(const GUID *pGuidProductFromDirectInput) {
return false;
}
void olc::GamePad::init() {}
void olc::GamePad::updateGamepads() {}
inline olc::GamePad::GamePad(LPCDIDEVICEINSTANCEA lpddi) {}
inline olc::GamePad::GamePad(DWORD xId) : xId(xId), xInput(true) {}
void olc::GamePad::poll() {}
void olc::GamePad::startVibration(float strength) const {}
void olc::GamePad::stopVibration() const {}
olc::GamePad::~GamePad() {}
std::string olc::GamePad::getId() {return {};}
#endif
#pragma endregion
#pragma region Linux
#ifdef __linux__
olc::GamePad *olc::GamePad::openGamepad(const std::string &path) {
return nullptr;
}
// Create a gamepad from a path to the event file
olc::GamePad::GamePad(std::string path, int fd)
: path{std::move(path)}, availableAxes{false},
availableButtons{false}, effect{}, fd{fd} {
}
bool olc::GamePad::readEvent(input_event &event) const {
return {};
}
void olc::GamePad::poll() {
}
void olc::GamePad::updateGamepads() {
}
void olc::GamePad::enumerateGamepads() {
}
std::string olc::GamePad::getId() { return {}; }
void olc::GamePad::startVibration(float strength) const {}
void olc::GamePad::stopVibration() const {
}
olc::GamePad::~GamePad() {
}
void olc::GamePad::reconnect() {
}
void olc::GamePad::init() {
}
#endif
#ifdef __EMSCRIPTEN__
olc::GamePad::GamePad(long id, std::string name) : name{name}, id{id} {}
void olc::GamePad::poll() {
}
void olc::GamePad::updateGamepads() { }
std::string olc::GamePad::getId() { return {}; }
void olc::GamePad::startVibration(float strength) const {}
void olc::GamePad::stopVibration() const {}
olc::GamePad::~GamePad() {}
void olc::GamePad::init() {
}
#endif
#pragma endregion
#pragma endregion
#pragma region Common
olc::GamePad *olc::GamePad::selectWithButton(olc::GPButtons b) {
return nullptr;
}
olc::GamePad *olc::GamePad::selectWithAnyButton() {
return nullptr;
}
float olc::GamePad::getAxis(olc::GPAxes a) {
return {};
}
const float olc::GamePad::getDeadZone()const{
return {};
}
const float olc::GamePad::getDeadZoneOuter()const{
return {};
}
void olc::GamePad::setDeadZone(const float deadZone){
}
void olc::GamePad::setDeadZoneOuter(const float deadZoneOuter){
}
olc::HWButton olc::GamePad::getButton(olc::GPButtons b) {
return {};
}
std::string olc::GamePad::getName() { return {}; }
int olc::GamePad::getAxisCount() const { return {}; }
int olc::GamePad::getButtonCount() const { return {}; }
void olc::GamePad::handleButton(int id, bool value) {
}
bool olc::GamePad::hasAxis(GPAxes a) { return {}; }
bool olc::GamePad::hasButton(GPButtons b) {
return {};
}
bool olc::GamePad::OnBeforeUserUpdate(float &fElapsedTime) {
return false;
}
std::vector<olc::GamePad *> &olc::GamePad::getGamepads() { return gamepads; }
std::vector<olc::GamePad *> olc::GamePad::gamepads;
#pragma endregion
#endif // OLC_PGE_GAMEPAD
#pragma endregion
#endif

View File

@ -41,6 +41,7 @@ All rights reserved.
#include "olcUTIL_Geometry2D.h"
#include "olcPixelGameEngine.h"
#ifndef UNIT_TESTING
#ifdef WIN32
#include <ft2build.h>
#pragma comment(lib, "freetype.lib")
@ -57,6 +58,7 @@ All rights reserved.
#include <string>
#include <vector>
#include "Error.h"
#include FT_FREETYPE_H
#include FT_GLYPH_H
@ -484,4 +486,79 @@ FT_Library olc::Font::library;
#endif
#else
namespace olc {
struct FontRect {
olc::vi2d offset;
olc::vi2d size;
};
class Font : public olc::PGEX {
public:
Font() = default;
~Font() {
}
Font(std::string path, int fontSize){
}
Font(const Font &other) = delete;
Font(Font &&other)noexcept{ }
Font &operator=(const Font &other) = delete;
Font &operator=(Font &&other)noexcept{
return *this;
}
private:
FontRect _GetStringBounds(std::u32string string, float angle = 0.0f) {
return olc::FontRect{};
}
public:
FontRect GetStringBounds(std::u32string string, float angle = 0.0f) {
return {};
}
olc::Sprite *RenderStringToSprite(std::u32string string,
olc::Pixel color) {
return nullptr;
}
olc::Decal *RenderStringToDecal(std::u32string string,
olc::Pixel color) {
return nullptr;
}
olc::Renderable RenderStringToRenderable(std::u32string string,
olc::Pixel color) {
return {};
}
void AddFallbackFont(std::string path) {
}
std::u32string GetFontName(){
return {};
}
static bool init() {
return true;
}
private:
void DrawBitmap(int x, int y, int bmp, int color) {
}
void DrawBitmapTo(int x, int y, int bmp, int color,
int *sprite) {
}
uint8_t GetCharIndex(char32_t charCode) {
return {};
}
};
} // namespace olc
#endif
#endif

View File

@ -377,7 +377,9 @@ return 0;
*/
#pragma endregion
#ifndef OLC_PGE_HEADLESS
#define OLC_GFX_OPENGL33
#endif
#ifndef OLC_PGE_DEF
#define OLC_PGE_DEF
@ -409,7 +411,6 @@ return 0;
#include "olcUTIL_Geometry2D.h"
#include "Pixel.h"
#include "util.h"
#include "TEST_DEFINES.h"
#pragma endregion
#define PGE_VER 225
@ -1699,6 +1700,10 @@ namespace olc
olc::rcode Sprite::LoadFromFile(const std::string& sImageFile, olc::ResourcePack* pack)
{
UNUSED(pack);
if(!loader){
util::GetCaseInsensitiveFilename(sImageFile);
return olc::rcode::OK;
}
return loader->LoadImageResource(this, util::GetCaseInsensitiveFilename(sImageFile), pack);
}
@ -5359,6 +5364,9 @@ namespace olc
virtual olc::rcode SetWindowTitle(const std::string& s) { return olc::rcode::OK; }
virtual olc::rcode StartSystemEventLoop() { return olc::rcode::OK; }
virtual olc::rcode HandleSystemEvent() { return olc::rcode::OK; }
virtual olc::rcode SetFullscreen(const bool bFullscreen, const vi2d windowPos = {}){return olc::rcode::OK;}
virtual void SetWindowPos(vi2d pos){}
virtual void SetWindowSize(vi2d size){}
};
#endif
}

View File

@ -54,6 +54,9 @@ David Barr, aka javidx9, <20>OneLoneCoder 2019, 2020, 2021, 2022
#pragma once
#include "olcUTIL_Geometry2D.h"
#undef min
#undef max
namespace olc::utils
{
class Camera2D
@ -216,6 +219,7 @@ namespace olc::utils
case Mode::LazyFollow:
{
#undef min
m_vPosition += (GetTarget() - m_vPosition) * std::min(1.f,m_fLazyFollowRate * fElapsedTime);
}
break;

View File

@ -61,10 +61,10 @@ David Barr, aka javidx9, <20>OneLoneCoder 2019, 2020, 2021, 2022
#include <fstream>
#include <stack>
#include <sstream>
#include <strstream>
#include <numeric>
#include "Error.h"
#include<ranges>
#include"util.h"
using namespace std::literals;

View File

@ -2498,4 +2498,6 @@ namespace olc::utils::geom2d
}
using namespace olc;
using namespace olc::utils;
using namespace olc::utils;
#undef min
#undef max

View File

@ -0,0 +1,187 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2026 Amy 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 "config.h"
#include "ItemDrop.h"
#include "Tutorial.h"
#include "DamageNumber.h"
#include "GameHelper.h"
#include"catch.h"
using namespace olc::utils;
INCLUDE_INITIALIZEGAMECONFIGURATIONS
INCLUDE_game
INCLUDE_GFX
INCLUDE_DAMAGENUMBER_LIST
class BuffTests{
public:
inline BuffTests(){
SetupTestMonster();
SetupMockMap();
}
inline ~BuffTests(){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
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();
Monster::InitializeStrategies();
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
testGame->ResetLevelStates();
#pragma region Setup a fake test map
game->MAP_DATA.Unlock();
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();
game->MAP_DATA.SetInitialized();
}
void SetupMockMap(){
game->MAP_DATA.at("CAMPAIGN_1_1")=Map{};
ItemDrop::ClearDrops();
}
#pragma endregion
};
TEST(BuffTests,"AddBuffMonsterCallbackExpireFunctionTest"){
Monster&m{game->SpawnMonster({},MONSTER_DATA.at("TestName"))};
Game::Update(0.f);
REQUIRE(size_t(0)==m.GetBuffs(BuffType::AFFECTED_BY_LIGHTNING_BOLT).size());
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());});
REQUIRE(size_t(1)==m.GetBuffs(BuffType::AFFECTED_BY_LIGHTNING_BOLT).size());
Game::Update(0.f);
Game::Update(3.f);
REQUIRE(25==m.GetHealth());
}
TEST(BuffTests,"AddBuffPlayerCallbackExpireFunctionTest"){
REQUIRE(size_t(0)==game->GetPlayer()->GetBuffs(BuffType::AFFECTED_BY_LIGHTNING_BOLT).size());
game->GetPlayer()->AddBuff(BuffType::AFFECTED_BY_LIGHTNING_BOLT,3.f,1,[](Player*attachedTarget,Buff&b){attachedTarget->Hurt(5,attachedTarget->OnUpperLevel(),attachedTarget->GetZ());});
Game::Update(0.f);
REQUIRE(size_t(1)==game->GetPlayer()->GetBuffs(BuffType::AFFECTED_BY_LIGHTNING_BOLT).size());
Game::Update(0.f);
Game::Update(3.f);
REQUIRE(95==game->GetPlayer()->GetHealth());
}
TEST(BuffTests,"MonsterHasBuffFunctionTest"){
Monster&m{game->SpawnMonster({},MONSTER_DATA.at("TestName"))};
Game::Update(0.f);
REQUIRE(!m.HasBuff(BuffType::SPEEDBOOST));
m.AddBuff(BuffType::ADRENALINE_RUSH,1.f,1.f);
REQUIRE(!m.HasBuff(BuffType::SPEEDBOOST));
m.AddBuff(BuffType::SPEEDBOOST,1.f,1.f);
REQUIRE(m.HasBuff(BuffType::SPEEDBOOST));
m.AddBuff(BuffType::SPEEDBOOST,2.f,1.f);
REQUIRE(m.HasBuff(BuffType::SPEEDBOOST));
Game::Update(0.f);
Game::Update(1.f);
REQUIRE(m.HasBuff(BuffType::SPEEDBOOST));
Game::Update(1.f);
REQUIRE(!m.HasBuff(BuffType::SPEEDBOOST));
m.AddBuff(BuffType::SPEEDBOOST,1.f,1.f);
m.RemoveBuff(BuffType::SPEEDBOOST);
REQUIRE(!m.HasBuff(BuffType::SPEEDBOOST));
}
TEST(BuffTests,"PlayerHasBuffFunctionTest"){
REQUIRE(!game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST));
Game::AddBuffToPlayer(BuffType::ADRENALINE_RUSH,1.f,1.f);
REQUIRE(!game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST));
Game::AddBuffToPlayer(BuffType::SPEEDBOOST,1.f,1.f);
REQUIRE(game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST));
Game::AddBuffToPlayer(BuffType::SPEEDBOOST,2.f,1.f);
REQUIRE(game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST));
Game::Update(0.f);
Game::Update(1.f);
REQUIRE(game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST));
Game::Update(1.f);
REQUIRE(!game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST));
Game::AddBuffToPlayer(BuffType::SPEEDBOOST,1.f,1.f);
game->GetPlayer()->RemoveBuff(BuffType::SPEEDBOOST);
REQUIRE(!game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST));
}
TEST(BuffTests,"PlayerDoesNotImmediatelyReceiveBuffTest"){
REQUIRE(!game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST));
game->GetPlayer()->AddBuff(BuffType::SPEEDBOOST,1.f,1.f);
REQUIRE(!game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST));
Game::Update(0.f);
REQUIRE(game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST));
}
TEST(BuffTests,"MonsterGetBuffFunctionTest"){
Monster&m{game->SpawnMonster({},MONSTER_DATA.at("TestName"))};
Game::Update(0.f);
REQUIRE(!bool(m.GetBuff(BuffType::ADRENALINE_RUSH)));
m.AddBuff(BuffType::ADRENALINE_RUSH,1.f,1.f);
m.AddBuff(BuffType::SPEEDBOOST,1.f,1.f);
m.AddBuff(BuffType::SPEEDBOOST,2.f,1.f);
Game::Update(0.f);
REQUIRE(bool(m.GetBuff(BuffType::ADRENALINE_RUSH)));
REQUIRE(bool(m.GetBuff(BuffType::SPEEDBOOST)));
REQUIRE(1.f==(*m.GetBuff(BuffType::SPEEDBOOST)).lifetime);
Game::Update(1.f);
REQUIRE(1.f==(*m.GetBuff(BuffType::SPEEDBOOST)).lifetime);
Game::Update(1.f);
REQUIRE(!bool(m.GetBuff(BuffType::SPEEDBOOST)));
}

View File

@ -0,0 +1,208 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2026 Amy 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 "Tutorial.h"
#include <random>
#include "ItemDrop.h"
#include"SoundEffect.h"
#include"GameHelper.h"
#include"catch.h"
using namespace olc::utils;
INCLUDE_GFX
INCLUDE_ITEM_DATA
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_INITIALIZEGAMECONFIGURATIONS
INCLUDE_MONSTER_LIST
extern std::mt19937 rng;
class EffectTests{
public:
std::unique_ptr<AiL>testGame;
InputGroup testKeyboardInput;
Player*player;
HWButton*testKey;
inline EffectTests(){
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();
Monster::InitializeStrategies();
SoundEffect::Initialize();
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
ItemDrop::Initialize();
testGame->ResetLevelStates();
#pragma region Setup a fake test map and test monster
game->MAP_DATA.Unlock();
game->MAP_DATA["CAMPAIGN_1_1"];
game->MAP_DATA.at("CAMPAIGN_1_1")._SetMapData(MapTag{24*24,24*24,24,24});
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->InitializeCamera();
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.
testKey->bHeld=true; //Assume key is held for every test unless otherwise needs to be changed.
Menu::themes.SetInitialized();
GFX.SetInitialized();
DAMAGENUMBER_LIST.clear();
game->MAP_DATA.SetInitialized();
}
inline ~EffectTests(){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
};
TEST(EffectTests,"EffectListsStartEmpty"){
REQUIRE(size_t(0)==EffectManager::GetBackgroundEffectCount());
REQUIRE(size_t(0)==EffectManager::GetForegroundEffectCount());
}
TEST(EffectTests,"EffectListCountsAdjustWhenAddingAndRemovingEffects"){
REQUIRE(size_t(0)==EffectManager::GetBackgroundEffectCount());
REQUIRE(size_t(0)==EffectManager::GetForegroundEffectCount());
const auto&foregroundEffRef{game->AddEffect(Effect{vf2d{},0.5f,"pixel.png",false,0.f})};
REQUIRE(size_t(1)==EffectManager::GetForegroundEffectCount());
REQUIRE(size_t(0)==EffectManager::GetBackgroundEffectCount());
REQUIRE(0==foregroundEffRef.slotId);
REQUIRE(0==foregroundEffRef.effectId);
REQUIRE(true==foregroundEffRef.foregroundEffect);
REQUIRE(false==foregroundEffRef.expired());
const auto&foregroundEffRef2{game->AddEffect(Effect{vf2d{},1.f,"pixel.png",false,0.f,0.5f})};
const auto&backgroundEffRef{game->AddEffect(Effect{vf2d{},1.f,"pixel.png",false,0.f},true)};
REQUIRE(size_t(2)==EffectManager::GetForegroundEffectCount());
REQUIRE(size_t(1)==EffectManager::GetBackgroundEffectCount());
REQUIRE(0==backgroundEffRef.slotId);
REQUIRE(2==backgroundEffRef.effectId);
REQUIRE(false==backgroundEffRef.foregroundEffect);
REQUIRE(false==backgroundEffRef.expired());
REQUIRE(false==foregroundEffRef2.expired());
Game::Update(0.5f);
REQUIRE(size_t(1)==EffectManager::GetForegroundEffectCount());
REQUIRE(size_t(1)==EffectManager::GetBackgroundEffectCount());
REQUIRE(true==foregroundEffRef.expired());
REQUIRE(false==foregroundEffRef2.expired());
Game::Update(0.5f);
REQUIRE(size_t(1)==EffectManager::GetForegroundEffectCount());
REQUIRE(size_t(0)==EffectManager::GetBackgroundEffectCount());
const auto&foregroundEffRef3{game->AddEffect(Effect{vf2d{},0.5f,"pixel.png",false,0.f})};
REQUIRE(size_t(2)==EffectManager::GetForegroundEffectCount());
REQUIRE(size_t(0)==EffectManager::GetBackgroundEffectCount());
REQUIRE(0==foregroundEffRef3.slotId);
REQUIRE(3==foregroundEffRef3.effectId);
REQUIRE(true==foregroundEffRef.expired());
REQUIRE(true==foregroundEffRef3.foregroundEffect);
REQUIRE(false==foregroundEffRef3.expired());
Game::Update(0.5f);
REQUIRE(size_t(0)==EffectManager::GetForegroundEffectCount());
REQUIRE(size_t(0)==EffectManager::GetBackgroundEffectCount());
REQUIRE(true==foregroundEffRef3.expired());
REQUIRE(true==foregroundEffRef.expired());
REQUIRE(true==backgroundEffRef.expired());
REQUIRE(true==foregroundEffRef2.expired());
}
TEST(EffectTests,"GettingExpiredEffectRefObjectFails"){
Effect newEff{vf2d{},0.5f,"pixel.png",false,0.f};
auto backgroundEffRef{game->AddEffect(newEff)};
Game::Update(0.5f);
REQUIRE_THROWS_AS(backgroundEffRef.get(),std::runtime_error);
}
TEST(EffectTests,"AddAndRemovalOfEffectsInMiddleOfArray"){
auto eff1{game->AddEffect(Effect{vf2d{},0.5f,"pixel.png",false,0.f})};
auto eff2{game->AddEffect(Effect{vf2d{},0.5f,"pixel.png",false,0.f})};
auto eff3{game->AddEffect(Effect{vf2d{},0.5f,"pixel.png",false,0.f})};
auto eff4{game->AddEffect(Effect{vf2d{},0.25f,"pixel.png",false,0.f})};
auto eff5{game->AddEffect(Effect{vf2d{},0.5f,"pixel.png",false,0.f})};
Game::Update(0.25f);
auto eff6{game->AddEffect(Effect{vf2d{},0.25f,"pixel.png",false,0.f})};
auto eff7{game->AddEffect(Effect{vf2d{},0.25f,"pixel.png",false,0.f})};
REQUIRE(eff6.slotId==eff4.slotId);
REQUIRE(eff6.effectId!=eff4.effectId);
REQUIRE(5==eff7.slotId);
}
TEST(EffectTests,"AddNewEffectWhenArrayIsFull"){
for(int i:std::ranges::iota_view(0,EFFECT_LIMIT)){
game->AddEffect(Effect{vf2d{},0.5f,"pixel.png",false,0.f});
game->AddEffect(Effect{vf2d{},0.5f,"pixel.png",false,0.f},true);
}
EFF(*EffectManager::GetForegroundEffects()[50].effect).aliveTime=200.f;
int oldEffectId{EffectManager::GetForegroundEffects()[50].effectId};
EffectRef replacedEff{game->AddEffect(Effect{vf2d{},0.5f,"pixel.png",false,0.f})};
REQUIRE(oldEffectId<replacedEff.effectId);
}
TEST(EffectTests,"CheckForGapInExpiryTimes"){
for(int i:std::ranges::iota_view(0,EFFECT_LIMIT)){
if(i==40){
game->AddEffect(Effect{vf2d{},0.1f,"pixel.png",false,0.f});
game->AddEffect(Effect{vf2d{},0.1f,"pixel.png",false,0.f},true);
}else{
game->AddEffect(Effect{vf2d{},0.5f,"pixel.png",false,0.f});
game->AddEffect(Effect{vf2d{},0.5f,"pixel.png",false,0.f},true);
}
}
Game::Update(0.1f);
REQUIRE(false==bool(EffectManager::GetForegroundEffects()[40].effect));
REQUIRE(false==bool(EffectManager::GetBackgroundEffects()[40].effect));
EffectRef foregroundEff{game->AddEffect(Effect{vf2d{},0.5f,"pixel.png",false,0.f})};
EffectRef backgroundEff{game->AddEffect(Effect{vf2d{},0.5f,"pixel.png",false,0.f},true)};
REQUIRE(40==foregroundEff.slotId);
REQUIRE(40==backgroundEff.slotId);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,190 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2026 Amy 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 "Tutorial.h"
#include <random>
#include "ItemDrop.h"
#include"SoundEffect.h"
#include"catch.h"
INCLUDE_GFX
INCLUDE_ITEM_DATA
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_INITIALIZEGAMECONFIGURATIONS
extern std::mt19937 rng;
class EngineTests{
public:
std::unique_ptr<AiL>testGame;
InputGroup testKeyboardInput;
Player*player;
HWButton*testKey;
EngineTests(){
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();
SoundEffect::Initialize();
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
#pragma region Setup a fake test map and test monster
game->MAP_DATA.Unlock();
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();
game->MAP_DATA.SetInitialized();
}
~EngineTests(){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
};
TEST(EngineTests,"StripColorTest"){
std::string noColCode{"Hello World!"};
REQUIRE("Hello World!"s==testGame->stripCol(noColCode));
std::string leadingColCode{"#FFFFFFHello World!"};
REQUIRE("Hello World!"s==testGame->stripCol(leadingColCode));
std::string extraColCodes{"#FFFFFFHello #00FF00World!"};
REQUIRE("Hello World!"s==testGame->stripCol(extraColCodes));
std::u32string u32noColCode{noColCode.begin(),noColCode.end()};
std::u32string u32noColCodeResult{testGame->stripCol(u32noColCode)};
REQUIRE("Hello World!"s==std::string{u32noColCodeResult.begin(),u32noColCodeResult.end()});
std::u32string u32leadingColCode{noColCode.begin(),noColCode.end()};
std::u32string u32leadingColCodeResult{testGame->stripCol(u32leadingColCode)};
REQUIRE("Hello World!"s==std::string{u32leadingColCodeResult.begin(),u32leadingColCodeResult.end()});
std::u32string u32extraColCodes{extraColCodes.begin(),extraColCodes.end()};
std::u32string u32extraColCodesResult{testGame->stripCol(u32extraColCodes)};
REQUIRE("Hello World!"s==std::string{u32extraColCodesResult.begin(),u32extraColCodesResult.end()});
}
TEST(EngineTests,"StripLeadingColorTest"){
std::string noColCode{"Hello World!"};
REQUIRE("Hello World!"s==testGame->stripLeadingCol(noColCode));
std::string leadingColCode{"#FFFFFFHello World!"};
REQUIRE("Hello World!"s==testGame->stripLeadingCol(leadingColCode));
std::string extraColCodes{"#FFFFFFHello #00FF00World!"};
REQUIRE("Hello #00FF00World!"s==testGame->stripLeadingCol(extraColCodes));
std::u32string u32noColCode{noColCode.begin(),noColCode.end()};
std::u32string u32noColCodeResult{testGame->stripLeadingCol(u32noColCode)};
REQUIRE("Hello World!"s==std::string{u32noColCodeResult.begin(),u32noColCodeResult.end()});
std::u32string u32leadingColCode{noColCode.begin(),noColCode.end()};
std::u32string u32leadingColCodeResult{testGame->stripLeadingCol(u32leadingColCode)};
REQUIRE("Hello World!"s==std::string{u32leadingColCodeResult.begin(),u32leadingColCodeResult.end()});
std::u32string u32extraColCodes{extraColCodes.begin(),extraColCodes.end()};
std::u32string u32extraColCodesResult{testGame->stripLeadingCol(u32extraColCodes)};
REQUIRE("Hello #00FF00World!"s==std::string{u32extraColCodesResult.begin(),u32extraColCodesResult.end()});
}
TEST(EngineTests,"GetFinalRenderColorTest"){
REQUIRE(WHITE.n==testGame->GetFinalRenderColor(WHITE,"Hello World!").n);
REQUIRE(BLUE.n==testGame->GetFinalRenderColor(WHITE,"#0000FFHello World!").n);
REQUIRE(BLUE.n==testGame->GetFinalRenderColor(WHITE,"#0000FFHello #00FF00World!").n);
REQUIRE(WHITE.n==testGame->GetFinalRenderColor(WHITE,"Hello #00FF00World!").n);
std::string testStr{"Hello World!"};
std::u32string u32testStr{testStr.begin(),testStr.end()};
REQUIRE(WHITE.n==testGame->GetFinalRenderColor(WHITE,testStr).n);
std::string colorCodeStr{"#0000FFHello World!"};
std::u32string u32colorCodeStr{colorCodeStr.begin(),colorCodeStr.end()};
REQUIRE(BLUE.n==testGame->GetFinalRenderColor(WHITE,colorCodeStr).n);
std::string extraColorCodeStr{"#0000FFHello #00FF00World!"};
std::u32string u32extraColorCodeStr{extraColorCodeStr.begin(),extraColorCodeStr.end()};
REQUIRE(BLUE.n==testGame->GetFinalRenderColor(WHITE,extraColorCodeStr).n);
std::string middleColorCodeStr{"Hello #00FF00World!"};
std::u32string u32middleColorCodeStr{middleColorCodeStr.begin(),middleColorCodeStr.end()};
REQUIRE(WHITE.n==testGame->GetFinalRenderColor(WHITE,middleColorCodeStr).n);
}
TEST(EngineTests,"UtilMapRangeTest"){
REQUIRE(0.f==util::map_range<float>(0.f,0,100,0,100));
REQUIRE(100.f==util::map_range<float>(100.f,0,100,0,100));
REQUIRE(50.f==util::map_range<float>(50.f,0,100,0,100));
REQUIRE(0.f==util::map_range<float>(0.f,0,50,0,100));
REQUIRE(200.f==util::map_range<float>(100.f,0,50,0,100));
REQUIRE(100.f==util::map_range<float>(50.f,0,50,0,100));
REQUIRE(100.f==util::map_range<float>(0.f,0,100,100,200));
REQUIRE(200.f==util::map_range<float>(100.f,0,100,100,200));
REQUIRE(150.f==util::map_range<float>(50.f,0,100,100,200));
REQUIRE(0.f==util::map_range<float>(0.f,50,100,100,200));
REQUIRE(200.f==util::map_range<float>(100.f,50,100,100,200));
REQUIRE(100.f==util::map_range<float>(50.f,50,100,100,200));
}
TEST(EngineTests,"ClassUtilTest"){
REQUIRE(int(Class::WARRIOR)==int(classutils::StringToClass("Warrior")));
REQUIRE(int(Class::WIZARD)==int(classutils::StringToClass("Wizard")));
REQUIRE(int(Class::RANGER)==int(classutils::StringToClass("Ranger")));
REQUIRE(int(Class::THIEF)==int(classutils::StringToClass("Thief")));
REQUIRE(int(Class::WITCH)==int(classutils::StringToClass("Witch")));
REQUIRE(int(Class::TRAPPER)==int(classutils::StringToClass("Trapper")));
REQUIRE("Warrior"==classutils::ClassToString(Class::WARRIOR));
REQUIRE("Wizard"==classutils::ClassToString(Class::WIZARD));
REQUIRE("Ranger"==classutils::ClassToString(Class::RANGER));
REQUIRE("Thief"==classutils::ClassToString(Class::THIEF));
REQUIRE("Witch"==classutils::ClassToString(Class::WITCH));
REQUIRE("Trapper"==classutils::ClassToString(Class::TRAPPER));
}

View File

@ -0,0 +1,127 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2026 Amy 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 "config.h"
#include "ItemDrop.h"
#include "Tutorial.h"
#include "DamageNumber.h"
#include "GameHelper.h"
#include "SaveFile.h"
#include"SoundEffect.h"
#include <ranges>
using namespace olc::utils;
INCLUDE_MONSTER_DATA
INCLUDE_game
INCLUDE_GFX
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_MONSTER_LIST
INCLUDE_INITIALIZEGAMECONFIGURATIONS
class FileTests{
public:
std::unique_ptr<AiL>testGame;
FileTests(){
SetupTest();
SetupMockMap();
}
~FileTests(){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
#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();
SoundEffect::Initialize();
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
testGame->ResetLevelStates();
#pragma region Setup a fake test map
game->MAP_DATA.Unlock();
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();
game->MAP_DATA.SetInitialized();
}
void SetupMockMap(){
game->MAP_DATA.at("CAMPAIGN_1_1")=Map{};
ItemDrop::ClearDrops();
}
#pragma endregion
};
TEST(FileTests,"ImageLoadsProperRegardlessOfCaseSensitivity"){
Sprite testSpr;
REQUIRE(int(olc::rcode::OK)==int(testSpr.LoadFromFile("assets/title_transparent.png")));
REQUIRE_THROWS_AS(testSpr.LoadFromFile("assets/title_transparentfdjakl.png"),std::runtime_error);
REQUIRE(int(olc::rcode::OK)==int(testSpr.LoadFromFile("assets/title_transparent.png")));
REQUIRE(int(olc::rcode::OK)==int(testSpr.LoadFromFile("assets/TITle_transparent.PNg")));
}
TEST(FileTests,"DataFileLoadsProperRegardlessOfCaseSensitivity"){
datafile data;
REQUIRE(datafile::Read(data,"assets/config/credits.txt"));
data.Reset();
REQUIRE_THROWS_AS(datafile::Read(data,"assets/config/creditsfdsfdsafd.txt"),std::runtime_error);
data.Reset();
REQUIRE(datafile::Read(data,"assets/config/credITS.tXt"));
}

View File

@ -0,0 +1,10 @@
#include "catch.h"
#include"olcUTIL_Geometry2D.h"
class GeometryTests{
public:
};
TEST(GeometryTests,"Circle overlap test") {
REQUIRE( geom2d::overlaps(geom2d::circle<float>{vf2d{},10},vf2d{5,5}) );
}

View File

@ -0,0 +1,465 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2026 Amy 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 "Tutorial.h"
#include <random>
#include <format>
#include "ItemDrop.h"
#include <ranges>
#include "GameHelper.h"
#include"RowInventoryScrollableWindowComponent.h"
#include"SaveFile.h"
#include"SoundEffect.h"
using namespace olc::utils;
INCLUDE_GFX
INCLUDE_ITEM_DATA
INCLUDE_INITIALIZEGAMECONFIGURATIONS
extern std::mt19937 rng;
class ItemTests{
public:
std::unique_ptr<AiL>testGame;
InputGroup testKeyboardInput;
Player*player;
HWButton*testKey;
ItemTests(){
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();
Monster::InitializeStrategies();
MonsterData::InitializeMonsterData();
sig::Animation::InitializeAnimations();
testGame->InitializeDefaultKeybinds();
testGame->InitializePlayer();
sig::Animation::SetupPlayerAnimations();
Menu::InitializeMenus();
Tutorial::Initialize();
Stats::InitializeDamageReductionTable();
SoundEffect::Initialize();
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.Unlock();
game->MAP_DATA["CAMPAIGN_1_1"];
game->MAP_DATA["HUB_TYPE_TEST_MAP"];
game->MAP_DATA["WORLD_TYPE_TEST_MAP"];
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
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();
game->MAP_DATA.SetInitialized();
MapTag dungeonMapData{};
game->MAP_DATA["HUB_TYPE_TEST_MAP"].mapType=Map::MapType::HUB;
game->MAP_DATA["WORLD_TYPE_TEST_MAP"].mapType=Map::MapType::WORLD_MAP;
}
~ItemTests(){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
};
TEST(ItemTests,"ItemGiveTest"){
Inventory::AddItem("Health Potion"s,3);
REQUIRE(3U==Inventory::GetItemCount("Health Potion"s));
}
TEST(ItemTests,"ItemAddAndRemoveAffectsStageLootTest"){
REQUIRE(size_t(0)==Inventory::get("Stage Loot").size());
REQUIRE(size_t(0)==Inventory::get("Monster Loot").size());
Game::LoadLevelWithTMX("CAMPAIGN_1_1",testGame.get());
REQUIRE(size_t(0)==Inventory::get("Stage Loot").size());
REQUIRE(size_t(0)==Inventory::get("Monster Loot").size());
Inventory::AddItem("Health Potion"s,3);
REQUIRE(size_t(1)==Inventory::get("Stage Loot").size());
REQUIRE(size_t(0)==Inventory::get("Monster Loot").size());
Inventory::AddItem("Health Potion"s,3,true);
REQUIRE(size_t(1)==Inventory::get("Stage Loot").size());
REQUIRE(size_t(1)==Inventory::get("Monster Loot").size());
Game::LoadLevelWithTMX("BOSS_1",testGame.get());
REQUIRE(size_t(0)==Inventory::get("Stage Loot").size());
REQUIRE(size_t(0)==Inventory::get("Monster Loot").size());
Inventory::AddItem("Health Potion"s,3);
REQUIRE(size_t(1)==Inventory::get("Stage Loot").size());
REQUIRE(size_t(0)==Inventory::get("Monster Loot").size());
Inventory::AddItem("Health Potion"s,3,true);
REQUIRE(size_t(1)==Inventory::get("Stage Loot").size());
REQUIRE(size_t(1)==Inventory::get("Monster Loot").size());
Game::LoadFakeLevel("HUB_TYPE_TEST_MAP",testGame.get());
REQUIRE(size_t(0)==Inventory::get("Stage Loot").size());
REQUIRE(size_t(0)==Inventory::get("Monster Loot").size());
Inventory::AddItem("Health Potion"s,3);
REQUIRE(size_t(0)==Inventory::get("Stage Loot").size());
REQUIRE(size_t(0)==Inventory::get("Monster Loot").size());
Inventory::AddItem("Health Potion"s,3,true);
REQUIRE(size_t(0)==Inventory::get("Stage Loot").size());
REQUIRE(size_t(0)==Inventory::get("Monster Loot").size());
Game::LoadFakeLevel("WORLD_TYPE_TEST_MAP",testGame.get());
REQUIRE(size_t(0)==Inventory::get("Stage Loot").size());
REQUIRE(size_t(0)==Inventory::get("Monster Loot").size());
Inventory::AddItem("Health Potion"s,3);
REQUIRE(size_t(0)==Inventory::get("Stage Loot").size());
REQUIRE(size_t(0)==Inventory::get("Monster Loot").size());
Inventory::AddItem("Health Potion"s,3,true);
REQUIRE(size_t(0)==Inventory::get("Stage Loot").size());
REQUIRE(size_t(0)==Inventory::get("Monster Loot").size());
}
TEST(ItemTests,"ItemRemoveTest"){
Inventory::AddItem("Health Potion"s,3);
for(std::weak_ptr<Item>&item:Inventory::GetItem("Health Potion"s))Inventory::RemoveItem(item,3);
REQUIRE(0U==Inventory::GetItemCount("Health Potion"s));
}
TEST(ItemTests,"ItemQuantityStackTest"){
Inventory::AddItem("Health Potion"s,3);
Inventory::AddItem("Health Potion"s,3);
REQUIRE(6U==Inventory::GetItemCount("Health Potion"s));
}
TEST(ItemTests,"EquipmentNoStackTest"){
Inventory::AddItem("Ring of the Slime King"s);
Inventory::AddItem("Ring of the Slime King"s);
Inventory::AddItem("Ring of the Slime King"s);
int itemCounter{};
for(std::weak_ptr<Item>&item:Inventory::GetItem("Ring of the Slime King"s))itemCounter++;
REQUIRE(3==itemCounter);
}
TEST(ItemTests,"UsingBlankLoadoutItemDoesNothing"){
REQUIRE(!game->UseLoadoutItem(0));
REQUIRE(!game->UseLoadoutItem(1));
REQUIRE(!game->UseLoadoutItem(2));
}
TEST(ItemTests,"UsingLoadoutItemOfQuantityZeroDoesNothing"){
REQUIRE_THROWS_AS(game->SetLoadoutItem(0,"Minor Health Potion"),std::exception);
}
TEST(ItemTests,"UsingLoadoutItemConsumesIt"){
player->Hurt(1,player->OnUpperLevel(),player->GetZ());
Inventory::AddItem("Minor Health Potion"s,5U);
game->SetLoadoutItem(0,"Minor Health Potion");
testKey->bHeld=true; //Simulate key being pressed.
player->CheckAndPerformAbility(player->GetItem1(),testKeyboardInput);
REQUIRE(1==Inventory::loadoutItemsUsed[0].second);
REQUIRE(4U==Inventory::GetItemCount("Minor Health Potion"s));
REQUIRE(player->GetItem1().GetCooldownTime()==player->GetItem1().cooldown);
}
TEST(ItemTests,"ItemScriptBuffTest"){
player->Hurt(1,player->OnUpperLevel(),player->GetZ());
Inventory::AddItem("Stat Up Everything Potion"s,5U);
game->SetLoadoutItem(0,"Stat Up Everything Potion");
testKey->bHeld=true; //Simulate key being pressed.
REQUIRE_THROWS_AS(player->CheckAndPerformAbility(player->GetItem1(),testKeyboardInput),std::exception);
}
TEST(ItemTests,"FlatRestoreScriptTest"){
player->Hurt(75,player->OnUpperLevel(),player->GetZ());
player->ConsumeMana(76);
Inventory::AddItem("Flat Recovery Potion"s,5U);
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.
REQUIRE(75==player->GetHealth());
REQUIRE(75==player->GetMana());
}
TEST(ItemTests,"PctRestoreScriptTest"){
player->Hurt(75,player->OnUpperLevel(),player->GetZ());
player->ConsumeMana(76);
Inventory::AddItem("Pct Recovery Potion"s,5U);
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.
REQUIRE(75==player->GetHealth());
REQUIRE(75==player->GetMana());
}
TEST(ItemTests,"HealOverTimeTest"){
player->Hurt(75,player->OnUpperLevel(),player->GetZ());
Inventory::AddItem("Bandages"s,5U);
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.
REQUIRE(30==player->GetHealth());
game->SetElapsedTime(1.f);
game->OnUserUpdate(1.f);
for(int seconds{1};seconds<=6;seconds++){
REQUIRE(30+seconds*5==player->GetHealth());
game->OnUserUpdate(1.f);
}
REQUIRE(60==player->GetHealth());
}
TEST(ItemTests,"DisassembleAccessoryTest"){
Inventory::AddItem("Ring of the Slime King"s);
std::weak_ptr<Item>disassembleRingTest{Inventory::AddItem("Ring of the Slime King"s)};
Inventory::AddItem("Ring of the Slime King"s);
Inventory::Disassemble(disassembleRingTest);
REQUIRE(2U==Inventory::GetItemCount("Ring of the Slime King"s));
REQUIRE(disassembleRingTest.expired());
REQUIRE(1U==Inventory::GetItemCount(ITEM_DATA["Ring of the Slime King"].FragmentName()));
}
TEST(ItemTests,"DisassembleNonAccessoryTest"){
Inventory::AddItem("Ring of the Slime King"s);
try{
Inventory::Disassemble(Inventory::AddItem("Test Armor"s));
FAIL("Disassembling Test Armor succeeded! This should NOT be allowed!");
}catch(std::runtime_error&e){}
try{
Inventory::Disassemble(Inventory::AddItem("Green Slime Remains"s));
FAIL("Disassembling Green Slime Remains succeeded! This should NOT be allowed!");
}catch(std::runtime_error&e){}
try{
Inventory::Disassemble(Inventory::AddItem("Health Potion"s));
FAIL("Disassembling a Health Potion succeeded! This should NOT be allowed!");
}catch(std::runtime_error&e){}
}
TEST(ItemTests,"RefiningTest"){
std::weak_ptr<Item>slimeKingRing{Inventory::AddItem("Ring of the Slime King"s)};
REQUIRE(!slimeKingRing.lock()->CanBeRefined());
std::weak_ptr<Item>testArmor{Inventory::AddItem("Test Armor"s)};
REQUIRE(!testArmor.lock()->CanBeRefined());
Inventory::AddItem(slimeKingRing.lock()->FragmentName(),50U);
Inventory::EquipItem(slimeKingRing,EquipSlot::RING1);
REQUIRE(slimeKingRing.lock()->CanBeRefined());
player->SetMoney(0);
REQUIRE(!slimeKingRing.lock()->CanBeRefined());
player->SetMoney(10000);
while(slimeKingRing.lock()->CanBeRefined()){
RefineResult result{slimeKingRing.lock()->Refine()};
LOG(std::format("Enhanced {} by {}",result.first.Name(),result.second));
}
for(const auto&[attr,val]:slimeKingRing.lock()->RandomStats()){
REQUIRE(ITEM_DATA[slimeKingRing.lock()->ActualName()].GetMaxStats().A_Read(attr)==val);
}
/*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!
REQUIRE(120==player->GetMaxHealth());
REQUIRE(104==player->GetMaxMana());
REQUIRE(1.03f==player->GetMoveSpdMult());
}
TEST(ItemTests,"EnchantTestCheck"){
std::weak_ptr<Item>slimeKingRing{Inventory::AddItem("Ring of the Slime King"s)};
REQUIRE(!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));
REQUIRE(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;
REQUIRE(improvementExists);
obtainedDuplicate=true;
}
if(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().has_value())REQUIRE(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.
}
Game::ResetPlayerAndChangeClass(WIZARD,player,&*testGame);
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));
REQUIRE(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;
REQUIRE(improvementExists);
obtainedDuplicate=true;
}
if(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().has_value())REQUIRE(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.
}
REQUIRE(obtainedDuplicate);
}
TEST(ItemTests,"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);
REQUIRE(Item::SelectedEquipIsDifferent(extraRing2,EquipSlot::RING1));
REQUIRE(!Item::SelectedEquipIsDifferent(extraRing,EquipSlot::RING1));
Inventory::UnequipItem(EquipSlot::RING2);
REQUIRE(Item::SelectedEquipIsDifferent(extraRing,EquipSlot::RING1));
}
TEST(ItemTests,"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())REQUIRE(int(resultEnchant.GetClass().value())==int(player->GetClass()));
enchantCounts[resultEnchant.Category()]++;
REQUIRE(extraRing.lock()->GetEnchant().has_value());
REQUIRE(resultEnchant.Name()==extraRing.lock()->GetEnchant().value().Name());
REQUIRE(!player->HasEnchant(resultEnchant.Name()));
Inventory::EquipItem(extraRing,EquipSlot::RING1);
REQUIRE(player->HasEnchant(resultEnchant.Name()));
Inventory::UnequipItem(EquipSlot::RING1);
}
Test::InRange(enchantCounts[ItemEnchantInfo::ItemEnchantCategory::GENERAL],{450U,550U});
Test::InRange(enchantCounts[ItemEnchantInfo::ItemEnchantCategory::CLASS],{350U,450U});
Test::InRange(enchantCounts[ItemEnchantInfo::ItemEnchantCategory::UNIQUE],{50U,150U});
}
TEST(ItemTests,"EnchantColorTest"){
REQUIRE("Item Enchants.General Enchants.Enchant Display Color"_Pixel.n==ItemEnchantInfo::GetEnchant("Health Boost").DisplayCol().n);
REQUIRE("Item Enchants.Class Enchants.Enchant Display Color"_Pixel.n==ItemEnchantInfo::GetEnchant("Quickdraw").DisplayCol().n);
REQUIRE("Item Enchants.Unique Enchants.Enchant Display Color"_Pixel.n==ItemEnchantInfo::GetEnchant("Magical Protection").DisplayCol().n);
}
TEST(ItemTests,"CanBeEnchantedTest"){
std::weak_ptr<Item>extraRing{Inventory::AddItem("Ring of the Slime King"s)};
std::weak_ptr<Item>testArmor{Inventory::AddItem("Test Armor"s)};
REQUIRE(!extraRing.lock()->CanBeEnchanted());
REQUIRE(!testArmor.lock()->CanBeEnchanted());
player->SetMoney(2000U);
REQUIRE(!extraRing.lock()->CanBeEnchanted());
player->SetMoney(0U);
Inventory::AddItem(extraRing.lock()->FragmentName(),"Fragment Enchant Cost"_i[0]);
REQUIRE(!extraRing.lock()->CanBeEnchanted());
player->SetMoney(2000U);
REQUIRE(extraRing.lock()->CanBeEnchanted());
extraRing.lock()->ApplyRandomEnchant();
REQUIRE(!extraRing.lock()->CanBeEnchanted());
Inventory::AddItem(extraRing.lock()->FragmentName(),"Fragment Enchant Cost"_i[0]);
REQUIRE(extraRing.lock()->CanBeEnchanted());
REQUIRE(uint32_t(2000-"Fragment Enchant Cost"_i[1])==player->GetMoney());
}
TEST(ItemTests,"UIUpdatesWhenItemsAreReceivedTest"){
Inventory::AddItem("Health Potion"s,3);
Game::Update(0.f);
for(const auto&category:Item::GetItemCategories()|std::views::filter([](const std::string&category){return DATA["ItemCategory"][category].GetString()!="!HIDE";})){
const size_t itemCount{Component<RowInventoryScrollableWindowComponent>(MenuType::INVENTORY,std::format("Inventory Display - {}",category))->GetMainComponentCount()};
REQUIRE(Inventory::get(category).size()==itemCount);
}
}
TEST(ItemTests,"UIUpdatesWhenGameFileWithItemsIsLoadedTest"){
SaveFile::SetSaveFileID(-1);
SaveFile::LoadGame();
Game::Update(0.f);
for(const auto&category:Item::GetItemCategories()|std::views::filter([](const std::string&category){return DATA["ItemCategory"][category].GetString()!="!HIDE";})){
const size_t itemCount{Component<RowInventoryScrollableWindowComponent>(MenuType::INVENTORY,std::format("Inventory Display - {}",category))->GetMainComponentCount()};
REQUIRE(Inventory::get(category).size()==itemCount);
}
}
TEST(ItemTests,"UIDoesNotUpdateWhenSavingFileFlagIsEnabledTest"){
game->savingFile=true;
Inventory::AddItem("Health Potion"s,3);
for(const auto&category:Item::GetItemCategories()|std::views::filter([](const std::string&category){return DATA["ItemCategory"][category].GetString()!="!HIDE";})){
const size_t itemCount{Component<RowInventoryScrollableWindowComponent>(MenuType::INVENTORY,std::format("Inventory Display - {}",category))->GetMainComponentCount()};
REQUIRE(size_t(0)==itemCount);
}
}
TEST(ItemTests,"ConsumedLoadoutSlotTest"){
Inventory::AddItem("Health Potion"s,4);
Inventory::AddItem("Mana Potion"s,2);
Inventory::AddItem("Elixir of the Wind"s,1);
Inventory::AddItem("Boar Meat"s,3);
game->SetLoadoutItem(0,"Health Potion");
game->SetLoadoutItem(1,"Mana Potion");
game->SetLoadoutItem(2,"Elixir of the Wind");
const bool usedItemResult{game->UseLoadoutItem(2)};
const bool usedItemResult2{game->UseLoadoutItem(2)};
REQUIRE(usedItemResult);
REQUIRE(!usedItemResult2);
game->SetLoadoutItem(2,"Boar Meat");
}
TEST(ItemTests,"LoadoutSlotRefillsAfterLevelTest"){
Inventory::AddItem("Health Potion"s,100);
Inventory::AddItem("Mana Potion"s,100);
Inventory::AddItem("Elixir of the Wind"s,100);
Inventory::AddItem("Boar Meat"s,3);
game->SetLoadoutItem(0,"Health Potion");
game->SetLoadoutItem(1,"Mana Potion");
game->SetLoadoutItem(2,"Elixir of the Wind");
REQUIRE(game->GetLoadoutItem(0).lock()->Amt()==(uint32_t)"Player.Item Loadout Limit"_I);
REQUIRE(game->GetLoadoutItem(1).lock()->Amt()==(uint32_t)"Player.Item Loadout Limit"_I);
REQUIRE(game->GetLoadoutItem(2).lock()->Amt()==(uint32_t)"Player.Item Loadout Limit"_I);
for(int i:std::ranges::iota_view(0U,(uint32_t)"Player.Item Loadout Limit"_I)){
game->UseLoadoutItem(0);
game->UseLoadoutItem(1);
Game::Update(std::max(game->GetLoadoutItem(0).lock()->CooldownTime(),game->GetLoadoutItem(1).lock()->CooldownTime()));
}
REQUIRE(game->GetLoadoutItem(0).lock()->Amt()==0);
REQUIRE(game->GetLoadoutItem(1).lock()->Amt()==0);
game->RestockLoadoutItems();
REQUIRE(game->GetLoadoutItem(0).lock()->Amt()==(uint32_t)"Player.Item Loadout Limit"_I);
REQUIRE(game->GetLoadoutItem(1).lock()->Amt()==(uint32_t)"Player.Item Loadout Limit"_I);
REQUIRE(game->GetLoadoutItem(2).lock()->Amt()==(uint32_t)"Player.Item Loadout Limit"_I);
}

View File

@ -0,0 +1,638 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2026 Amy 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"config.h"
#include"ItemDrop.h"
#include"Tutorial.h"
#include"DamageNumber.h"
#include"GameHelper.h"
#include"SoundEffect.h"
#include"Monster.h"
using namespace olc::utils;
INCLUDE_MONSTER_DATA
INCLUDE_game
INCLUDE_GFX
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_MONSTER_LIST
INCLUDE_INITIALIZEGAMECONFIGURATIONS
class MonsterTests{
public:
std::unique_ptr<AiL>testGame;
MonsterTests(){
SetupTestMonster();
SetupMockMap();
}
~MonsterTests(){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
#pragma region Setup Functions
//Makes MONSTER_DATA["TestName"] available.
void SetupTestMonster(){
InitializeGameConfigurations();
testGame.reset(new AiL(true));
ItemAttribute::Initialize();
ItemInfo::InitializeItems();
testGame->InitializeGraphics();
testGame->InitializeClasses();
sig::Animation::InitializeAnimations();
Monster::InitializeStrategies();
MonsterData::InitializeMonsterData();
testGame->InitializeDefaultKeybinds();
testGame->InitializePlayer();
sig::Animation::SetupPlayerAnimations();
Menu::InitializeMenus();
Tutorial::Initialize();
Stats::InitializeDamageReductionTable();
SoundEffect::Initialize();
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
testGame->ResetLevelStates();
#pragma region Setup a fake test map
game->MAP_DATA.Unlock();
game->MAP_DATA["CAMPAIGN_1_1"];
Game::_SetCurrentLevel("CAMPAIGN_1_1");
ItemDrop::ClearDrops();
game->MAP_DATA.SetInitialized();
#pragma endregion
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
testMonsterData.hurtSound="Monster Hurt";
MONSTER_DATA["TestName"]=testMonsterData;
Menu::themes.SetInitialized();
GFX.SetInitialized();
DAMAGENUMBER_LIST.clear();
game->MAP_DATA.SetInitialized();
}
void SetupMockMap(){
game->MAP_DATA.at("CAMPAIGN_1_1")=Map{};
ItemDrop::ClearDrops();
}
#pragma endregion
};
TEST(MonsterTests,"DisplayNameCheck"){
REQUIRE("Test Monster"==MONSTER_DATA["TestName"].GetDisplayName());
}
TEST(MonsterTests,"InternalNameCheck"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
REQUIRE("TestName"==testMonster.GetName());
}
TEST(MonsterTests,"HealthCheck"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
REQUIRE(testMonster.GetHealth()==testMonster.GetMaxHealth());
REQUIRE(testMonster.GetHealth()==30);
}
TEST(MonsterTests,"Deal5DamageAndNoDamageFlagWork"){
Monster&testMonster{game->SpawnMonster({},MONSTER_DATA["TestName"])};
Game::Update(0.f);
testMonster.Hurt(5,testMonster.OnUpperLevel(),testMonster.GetZ());
REQUIRE(testMonster.GetHealth()==testMonster.GetMaxHealth()-5);
REQUIRE(false==testMonster.HasIframes());
Game::Update(0.1f); //Prevent stacking up damage all on one damage number.
testMonster.Hurt(0,testMonster.OnUpperLevel(),testMonster.GetZ());
REQUIRE(size_t(2)==DAMAGENUMBER_LIST.size());
Game::Update(0.1f); //Prevent stacking up damage all on one damage number.
testMonster.Hurt(0,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::NO_DAMAGE_NUMBER);
REQUIRE(size_t(2)==DAMAGENUMBER_LIST.size());
}
TEST(MonsterTests,"Deal5DamageAndDamageSoundEffectsPlay"){
Monster&testMonster{game->SpawnMonster({},MONSTER_DATA["TestName"])};
testMonster.Hurt(5,testMonster.OnUpperLevel(),testMonster.GetZ());
REQUIRE(1==SoundEffect::soundsPlayedLog["Monster Hurt"]);
Game::Update(0.1f); //Prevent stacking up damage all on one damage number.
testMonster.Hurt(5,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::NO_DAMAGE_NUMBER);
REQUIRE(1==SoundEffect::soundsPlayedLog["Monster Hurt"]);
Game::Update(0.1f); //Prevent stacking up damage all on one damage number.
testMonster.Hurt(5,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::DOT);
REQUIRE(1==SoundEffect::soundsPlayedLog["Monster Hurt"]);
}
TEST(MonsterTests,"ZeroDamageResultsInTrue"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
const bool result{testMonster.Hurt(0,testMonster.OnUpperLevel(),testMonster.GetZ())};
REQUIRE(true==result);
REQUIRE(false==testMonster.HasIframes());
}
TEST(MonsterTests,"IFrameShouldResultInNoDamage"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.ApplyIframes(0.5f);
testMonster.Hurt(5,testMonster.OnUpperLevel(),testMonster.GetZ());
REQUIRE(testMonster.GetHealth()==testMonster.GetMaxHealth());
}
TEST(MonsterTests,"BeingInTheAirShouldAvoidAttacksFromTheGround"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.SetZ(2.f);
testMonster.Hurt(5,testMonster.OnUpperLevel(),0.f);
REQUIRE(testMonster.GetHealth()==testMonster.GetMaxHealth());
}
TEST(MonsterTests,"MonstersDeal10Damage_NoDamageReduction"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
Monster testMonster2{{},MONSTER_DATA["TestName"]};
game->GetPlayer()->Hurt(testMonster.GetAttack(),testMonster.OnUpperLevel(),testMonster.GetZ());
testMonster2.Hurt(testMonster.GetAttack(),testMonster.OnUpperLevel(),testMonster.GetZ());
REQUIRE(game->GetPlayer()->GetMaxHealth()-10==game->GetPlayer()->GetHealth());
REQUIRE(testMonster2.GetMaxHealth()-10==testMonster2.GetHealth());
}
TEST(MonsterTests,"DoubleAttackPctModifierWorks"){
Monster buffMonster{{},MONSTER_DATA["TestName"]};
buffMonster.AddBuff(BuffType::STAT_UP,5,100._Pct,{ItemAttribute::Get("Attack %")});
Monster testMonster2{{},MONSTER_DATA["TestName"]};
game->GetPlayer()->Hurt(buffMonster.GetAttack(),buffMonster.OnUpperLevel(),buffMonster.GetZ());
testMonster2.Hurt(buffMonster.GetAttack(),buffMonster.OnUpperLevel(),buffMonster.GetZ());
REQUIRE(game->GetPlayer()->GetMaxHealth()-20==game->GetPlayer()->GetHealth());
REQUIRE(testMonster2.GetMaxHealth()-20==testMonster2.GetHealth());
}
TEST(MonsterTests,"AttackUpModifierWorks"){
Monster buffMonster{{},MONSTER_DATA["TestName"]};
buffMonster.AddBuff(BuffType::STAT_UP,5,5.f,{"Attack"});
Monster testMonster2{{},MONSTER_DATA["TestName"]};
game->GetPlayer()->Hurt(buffMonster.GetAttack(),buffMonster.OnUpperLevel(),buffMonster.GetZ());
testMonster2.Hurt(buffMonster.GetAttack(),buffMonster.OnUpperLevel(),buffMonster.GetZ());
REQUIRE(game->GetPlayer()->GetMaxHealth()-15==game->GetPlayer()->GetHealth());
REQUIRE(testMonster2.GetMaxHealth()-15==testMonster2.GetHealth());
}
TEST(MonsterTests,"HealthUpModifierWorks"){
Monster buffMonster{{},MONSTER_DATA["TestName"]};
buffMonster.AddBuff(BuffType::STAT_UP,5,5.f,{"Health"});
REQUIRE(35==buffMonster.GetMaxHealth());
REQUIRE(30==buffMonster.GetHealth());
}
TEST(MonsterTests,"AttackUpPctModifierWorks"){
Monster buffMonster{{},MONSTER_DATA["TestName"]};
buffMonster.AddBuff(BuffType::STAT_UP,5,100.0_Pct,{"Attack %"});
Monster testMonster2{{},MONSTER_DATA["TestName"]};
game->GetPlayer()->Hurt(buffMonster.GetAttack(),buffMonster.OnUpperLevel(),buffMonster.GetZ());
testMonster2.Hurt(buffMonster.GetAttack(),buffMonster.OnUpperLevel(),buffMonster.GetZ());
REQUIRE(game->GetPlayer()->GetMaxHealth()-20==game->GetPlayer()->GetHealth());
REQUIRE(testMonster2.GetMaxHealth()-20==testMonster2.GetHealth());
}
TEST(MonsterTests,"MonsterIsConsideredDeadAt0Health"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.Hurt(testMonster.GetMaxHealth(),testMonster.OnUpperLevel(),testMonster.GetZ());
REQUIRE(true==testMonster.IsDead());
}
TEST(MonsterTests,"ItemDropSpawnsWhenMonsterIsKilled"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.Hurt(testMonster.GetMaxHealth(),testMonster.OnUpperLevel(),testMonster.GetZ());
REQUIRE(size_t(1)==ItemDrop::GetDrops().size());
}
TEST(MonsterTests,"MoveSpdSetCorrectly"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
REQUIRE(2.f==testMonster.GetMoveSpdMult());
}
TEST(MonsterTests,"SlowdownBuffTest"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::SLOWDOWN,5.f,0.5f);
REQUIRE(1.f==testMonster.GetMoveSpdMult());
}
TEST(MonsterTests,"SelfInflictedSlowdownTest"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::SELF_INFLICTED_SLOWDOWN,5.f,0.5f);
REQUIRE(1.f==testMonster.GetMoveSpdMult());
}
TEST(MonsterTests,"SpeedBoostTest"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::SPEEDBOOST,5.f,0.5f);
REQUIRE(3.f==testMonster.GetMoveSpdMult());
}
TEST(MonsterTests,"LockOnSpeedBoostTest"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::LOCKON_SPEEDBOOST,5.f,0.5f);
REQUIRE(3.f==testMonster.GetMoveSpdMult());
}
TEST(MonsterTests,"AdditiveMoveSpdBuffsTest"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::SLOWDOWN,5.f,0.5f);
testMonster.AddBuff(BuffType::SPEEDBOOST,5.f,0.75f);
REQUIRE(2.5f==testMonster.GetMoveSpdMult());
}
TEST(MonsterTests,"DamageReductionBuffTest"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::DAMAGE_REDUCTION,5.f,0.25f);
testMonster.Hurt(testMonster.GetAttack(),testMonster.OnUpperLevel(),testMonster.GetZ());
//25% damage reduction should result in 7.5 health taken, When ceiling'd results in 8 health taken.
REQUIRE(22==testMonster.GetHealth());
}
TEST(MonsterTests,"BarrierBuffTest"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::BARRIER_DAMAGE_REDUCTION,5.f,0.5f);
testMonster.Hurt(testMonster.GetAttack(),testMonster.OnUpperLevel(),testMonster.GetZ());
//50% damage reduction should result in 5 health taken
REQUIRE(25==testMonster.GetHealth());
}
TEST(MonsterTests,"AdditiveDamageReductionTest"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::DAMAGE_REDUCTION,5.f,0.25f);
testMonster.AddBuff(BuffType::BARRIER_DAMAGE_REDUCTION,5.f,0.5f);
testMonster.Hurt(testMonster.GetAttack(),testMonster.OnUpperLevel(),testMonster.GetZ());
//25+50% damage reduction should result in 2.5 health taken. When ceiling'd, results in 3 health taken.
REQUIRE(27==testMonster.GetHealth());
}
TEST(MonsterTests,"MaximumDamageReductionTest"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::DAMAGE_REDUCTION,5.f,INFINITY);
testMonster.Hurt(testMonster.GetAttack(),testMonster.OnUpperLevel(),testMonster.GetZ());
//At 100% or more damage reduction, the monster should take 0 damage.
REQUIRE(30==testMonster.GetHealth());
}
TEST(MonsterTests,"TrueDamageTest"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster._DealTrueDamage(testMonster.GetAttack()*3);
REQUIRE(0==testMonster.GetHealth());
}
TEST(MonsterTests,"TrueDamageTestWith100PctDamageReduction"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::DAMAGE_REDUCTION,5.f,INFINITY);
testMonster._DealTrueDamage(testMonster.GetAttack()*3);
//Damage reduction should not affect true damage at all.
REQUIRE(0==testMonster.GetHealth());
}
TEST(MonsterTests,"CriticalRateTest"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
game->GetPlayer()->SetBaseStat(ItemAttribute::Get("Crit Rate"),100.f);
testMonster.Hurt(5,testMonster.OnUpperLevel(),testMonster.GetZ());
//If crit rate works 100% of the time, getting hurt should deal crit dmg % more damage (which defaults to 50%). Ceiling 7.5 damage to 8.
REQUIRE(22==testMonster.GetHealth());
}
TEST(MonsterTests,"CriticalDamageTest"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
game->GetPlayer()->SetBaseStat(ItemAttribute::Get("Crit Rate"),100.f);
game->GetPlayer()->SetBaseStat(ItemAttribute::Get("Crit Dmg"),150.f);
testMonster.Hurt(5,testMonster.OnUpperLevel(),testMonster.GetZ());
//If crit rate works 100% of the time, getting hurt will deal 150% more damage. Ceiling 12.5 damage to 13.
REQUIRE(17==testMonster.GetHealth());
}
TEST(MonsterTests,"ShouldNotDieNormallyTest"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.diesNormally=false;
testMonster.Hurt(1000,testMonster.OnUpperLevel(),testMonster.GetZ());
testMonster.Hurt(1000,testMonster.OnUpperLevel(),testMonster.GetZ());
testMonster.Hurt(1000,testMonster.OnUpperLevel(),testMonster.GetZ());
//Health should continue to remain at 1 and the monster should remain alive if the dies normally flag is false.
REQUIRE(1==testMonster.GetHealth());
REQUIRE(testMonster.IsAlive());
}
TEST(MonsterTests,"IllegalDefenseStatUpBuffCheck"){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"Defense"});
FAIL("Adding a Defense buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(MonsterTests,"IllegalMoveSpdStatUpBuffCheck"){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"Move Spd %"});
FAIL("Adding a Move Spd % buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(MonsterTests,"IllegalDefensePctStatUpBuffCheck"){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"Defense %"});
FAIL("Adding a Defense % buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(MonsterTests,"IllegalCDRStatUpBuffCheck"){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"CDR"});
FAIL("Adding a CDR buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(MonsterTests,"IllegalCritRateStatUpBuffCheck"){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"Crit Rate"});
FAIL("Adding a Crit Rate buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(MonsterTests,"IllegalCritDmgStatUpBuffCheck"){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"Crit Damage"});
FAIL("Adding a Crit Damage buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(MonsterTests,"IllegalHPRecoveryPctStatUpBuffCheck"){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"HP Recovery %"});
FAIL("Adding a HP Recovery % buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(MonsterTests,"IllegalHP6RecoveryPctStatUpBuffCheck"){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"HP6 Recovery %"});
FAIL("Adding a HP6 Recovery % buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(MonsterTests,"IllegalHP4RecoveryPctStatUpBuffCheck"){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"HP4 Recovery %"});
FAIL("Adding a HP4 Recovery % buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(MonsterTests,"IllegalDamageReductionStatUpBuffCheck"){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"Damage Reduction"});
FAIL("Adding a Damage Reduction buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(MonsterTests,"IllegalAttackSpdStatUpBuffCheck"){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"Attack Spd"});
FAIL("Adding a Attack Spd buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){};
}
TEST(MonsterTests,"IllegalManaStatUpBuffCheck"){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"Mana"});
FAIL("Adding a Mana buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(MonsterTests,"DOTTest"){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::DAMAGE_REDUCTION,10.f,100._Pct);
testMonster.Hurt(10,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::DOT);
REQUIRE(20==testMonster.GetHealth());
testMonster.Update(1.f); //Let time pass so two DOT numbers don't stack up.
testMonster.ApplyIframes(0.5f);
testMonster.Hurt(10,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::DOT);
REQUIRE(10==testMonster.GetHealth());
REQUIRE(2==std::accumulate(DAMAGENUMBER_LIST.begin(),DAMAGENUMBER_LIST.end(),0,[](int count,const std::shared_ptr<DamageNumber>&damageNumber){
if(damageNumber->GetType()==DamageNumberType::DOT)return count+1;
else return count;
})
);
}
TEST(MonsterTests,"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.
std::weak_ptr<Monster>testMonster{MONSTER_LIST.front()};
REQUIRE(uint8_t(0)==testMonster.lock()->GetMarkStacks());
testMonster.lock()->ApplyMark(7.f,5U);
game->SetElapsedTime(0.3f);
game->OnUserUpdate(0.3f); //A monster that had a mark applied needs to be added as a lock on target in the next tick.
REQUIRE(uint8_t(5)==testMonster.lock()->GetMarkStacks());
testMonster.lock()->Hurt(1,testMonster.lock()->OnUpperLevel(),testMonster.lock()->GetZ());
REQUIRE(uint8_t(5)==testMonster.lock()->GetMarkStacks());
testMonster.lock()->Hurt(1,testMonster.lock()->OnUpperLevel(),testMonster.lock()->GetZ(),HurtFlag::PLAYER_ABILITY);
REQUIRE(uint8_t(4)==testMonster.lock()->GetMarkStacks());
REQUIRE(22==testMonster.lock()->GetHealth());
testMonster.lock()->Hurt(1,testMonster.lock()->OnUpperLevel(),testMonster.lock()->GetZ(),HurtFlag::DOT);
REQUIRE(uint8_t(4)==testMonster.lock()->GetMarkStacks());
testMonster.lock()->_DealTrueDamage(1);
REQUIRE(uint8_t(4)==testMonster.lock()->GetMarkStacks());
testMonster.lock()->Heal(testMonster.lock()->GetMaxHealth()); //Heal the monster so it doesn't die.
testMonster.lock()->_DealTrueDamage(1,HurtFlag::PLAYER_ABILITY);
REQUIRE(uint8_t(3)==testMonster.lock()->GetMarkStacks());
testMonster.lock()->_DealTrueDamage(10,HurtFlag::DOT|HurtFlag::PLAYER_ABILITY);
REQUIRE(uint8_t(2)==testMonster.lock()->GetMarkStacks());
testMonster.lock()->Heal(testMonster.lock()->GetMaxHealth());
testMonster.lock()->TriggerMark();
REQUIRE(uint8_t(1)==testMonster.lock()->GetMarkStacks());
REQUIRE(24==testMonster.lock()->GetHealth());
game->SetElapsedTime(10.f);
testMonster.lock()->Update(10.f);
REQUIRE(uint8_t(0)==testMonster.lock()->GetMarkStacks());
}
TEST(MonsterTests,"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());
REQUIRE(m.IsDead());
REQUIRE(!m.IsAlive());
REQUIRE(!m.Hurt(50,m.OnUpperLevel(),m.GetZ()));
REQUIRE(!m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::DOT));
REQUIRE(!m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::PLAYER_ABILITY));
REQUIRE(!m._DealTrueDamage(50));
REQUIRE(!m._DealTrueDamage(50==HurtFlag::DOT));
REQUIRE(!m._DealTrueDamage(50==HurtFlag::PLAYER_ABILITY));
}
TEST(MonsterTests,"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());
REQUIRE(!m.IsDead());
REQUIRE(m.IsAlive());
REQUIRE(m.Hurt(50,m.OnUpperLevel(),m.GetZ()));
REQUIRE(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::DOT));
REQUIRE(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::PLAYER_ABILITY));
REQUIRE(m._DealTrueDamage(50));
REQUIRE(m._DealTrueDamage(50==HurtFlag::DOT));
REQUIRE(m._DealTrueDamage(50==HurtFlag::PLAYER_ABILITY));
}
TEST(MonsterTests,"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());
REQUIRE(m.IsDead());
REQUIRE(!m.IsAlive());
m.Heal(50);
REQUIRE(m.Hurt(50,m.OnUpperLevel(),m.GetZ()));
m.Heal(50);
REQUIRE(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::DOT));
m.Heal(50);
REQUIRE(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::PLAYER_ABILITY));
m.Heal(50);
REQUIRE(m._DealTrueDamage(50));
m.Heal(50);
REQUIRE(m._DealTrueDamage(50==HurtFlag::DOT));
m.Heal(50);
REQUIRE(m._DealTrueDamage(50==HurtFlag::PLAYER_ABILITY));
}
TEST(MonsterTests,"UnconsciousMonsterTest"){
Monster&parrot{game->SpawnMonster({},MONSTER_DATA.at("Parrot"))};
Game::Update(1.f);
REQUIRE(!parrot.IsUnconscious());
parrot.Hurt(1,parrot.OnUpperLevel(),parrot.GetZ());
REQUIRE(!parrot.IsUnconscious());
parrot.Hurt(parrot.GetMaxHealth(),parrot.OnUpperLevel(),parrot.GetZ());
REQUIRE(parrot.IsUnconscious());
REQUIRE(parrot.IsDead());
REQUIRE(parrot.InUndamageableState(parrot.OnUpperLevel(),parrot.GetZ()));
Game::Update(MONSTER_DATA.at("Parrot").UnconsciousTime()/2);
REQUIRE(parrot.IsUnconscious());
REQUIRE(parrot.IsDead());
REQUIRE(parrot.InUndamageableState(parrot.OnUpperLevel(),parrot.GetZ()));
Game::Update(MONSTER_DATA.at("Parrot").UnconsciousTime()/2);
REQUIRE(!parrot.IsUnconscious());
REQUIRE(parrot.IsAlive());
REQUIRE(!parrot.InUndamageableState(parrot.OnUpperLevel(),parrot.GetZ()));
}
TEST(MonsterTests,"UnconsciousMonsterHurtTest"){
Monster&parrot{game->SpawnMonster({},MONSTER_DATA.at("Parrot"))};
Game::Update(1.f);
parrot.Hurt(parrot.GetMaxHealth(),parrot.OnUpperLevel(),parrot.GetZ());
REQUIRE(parrot.IsUnconscious());
}
TEST(MonsterTests,"MonsterCollisionRadiusTest"){
Monster&parrot{game->SpawnMonster({},MONSTER_DATA.at("Parrot"))};
parrot.strategy="[TEST]Run Right"; //Disable default AI for testing collisions
Game::Update(1.f);
Game::Update(1.f);
REQUIRE(parrot.GetOriginalCollisionRadius()==parrot.GetCollisionRadius());
REQUIRE(int(game->GetPlayer()->GetMaxHealth()-parrot.GetCollisionDamage())==game->GetPlayer()->GetHealth());
parrot.SetCollisionRadius(0.f);
REQUIRE(0.f==parrot.GetCollisionRadius());
game->GetPlayer()->Heal(game->GetPlayer()->GetMaxHealth());
game->GetPlayer()->_SetIframes(0.f);
parrot.SetPos({});
game->GetPlayer()->ForceSetPos({});
Game::Update(1.f);
REQUIRE(game->GetPlayer()->GetMaxHealth()==game->GetPlayer()->GetHealth());
parrot.SetCollisionRadius(parrot.GetOriginalCollisionRadius());
Game::Update(1.f);
REQUIRE(int(game->GetPlayer()->GetMaxHealth()-parrot.GetCollisionDamage())==game->GetPlayer()->GetHealth());
}
TEST(MonsterTests,"MonsterCollisionRadiusSizeTest"){
Monster&parrot{game->SpawnMonster({},MONSTER_DATA.at("Parrot"))};
}
TEST(MonsterTests,"MonsterRunRightTest"){
Game::LoadLevelWithTMX("TEST_MAP",testGame.get());
testGame->GetPlayer()->ForceSetPos({100,100});
Monster&boar{game->SpawnMonster({24,24},MONSTER_DATA.at("Boar"))};
Game::Update(0.f); // Make sure monster spawns first.
boar.strategy="[TEST]Run Right";
const vf2d originalBoarPos{boar.GetPos()};
Game::Update(0.5f);
REQUIRE(originalBoarPos+vf2d{50.f*boar.GetMoveSpdMult(),0.f}==boar.GetPos());
}
TEST(MonsterTests,"MonsterHasteMoveTest"){
Game::LoadLevelWithTMX("TEST_MAP",testGame.get());
Monster&parrot{game->SpawnMonster({24,24},MONSTER_DATA.at("Parrot"))};
Game::Update(0.f); // Make sure monster spawns first.
parrot.strategy="[TEST]Run Right";
{
const vf2d originalParrotPos{parrot.GetPos()};
Game::Update(0.5f);
REQUIRE(originalParrotPos+vf2d{50.f*parrot.GetMoveSpdMult(),0.f}==parrot.GetPos());
}
parrot.AddBuff(BuffType::HASTEN,INFINITE,0.3f);
{
const vf2d originalParrotPos{parrot.GetPos()};
Game::Update(0.5f);
REQUIRE(originalParrotPos+vf2d{65.f*parrot.GetMoveSpdMult(),0.f}==parrot.GetPos());
}
}
TEST(MonsterTests,"MonsterManaRecoveryTest"){
Monster&fireMage{game->SpawnMonster({24,24},MONSTER_DATA.at("Skeleton Fire Mage"))};
std::for_each(MONSTER_DATA["Skeleton Fire Mage"].abilities.begin(),MONSTER_DATA["Skeleton Fire Mage"].abilities.end(),[](MonsterAbilityData&data){
data.pctCastChance=0.f; //While testing and running game updates we do not want this ability to go off, so we set the cast percent chance to zero percent.
});
Game::Update(0.f); // Make sure monster spawns first.
const int initialMonsterMP{DATA.GetProperty("Monsters.Skeleton Fire Mage.MP").GetInt()};
REQUIRE(fireMage.mp==initialMonsterMP);
const float mpRecovery{float(DATA.GetProperty("Monsters.Skeleton Fire Mage.MP Recovery").GetReal())};
REQUIRE(fireMage.GetMPRecovery()==mpRecovery);
Game::Update(0.f);
int newMonsterMP{initialMonsterMP+int(mpRecovery)};
REQUIRE(fireMage.mp==newMonsterMP);
Game::Update(0.f);
REQUIRE(fireMage.mp==newMonsterMP);
Game::Update(1.f);
newMonsterMP+=int(mpRecovery);
REQUIRE(fireMage.mp==newMonsterMP);
MONSTER_DATA["TestName"].mpRecovery=0.5f;
MONSTER_DATA["TestName"].abilities.emplace_back("Meteor",50,0.0f);
Monster&testMonster{game->SpawnMonster({24,24},MONSTER_DATA.at("TestName"))};
Game::Update(0.f); // Make sure monster spawns first.
REQUIRE(testMonster.mp==0);
Game::Update(1.f);
REQUIRE(testMonster.mp==0);
REQUIRE(testMonster.mpRemainder==0.5f);
Game::Update(1.f);
REQUIRE(testMonster.mp==1);
REQUIRE(testMonster.mpRemainder==0.f);
}

View File

@ -0,0 +1,866 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2026 Amy 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 "Tutorial.h"
#include <random>
#include <format>
#include "ItemDrop.h"
#include "DamageNumber.h"
#include <ranges>
#include "GameHelper.h"
#include"SoundEffect.h"
using namespace olc::utils;
INCLUDE_GFX
INCLUDE_ITEM_DATA
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_INITIALIZEGAMECONFIGURATIONS
extern std::mt19937 rng;
class PlayerTests{
public:
std::unique_ptr<AiL>testGame;
InputGroup testKeyboardInput;
Player*player;
HWButton*testKey;
PlayerTests(){
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();
SoundEffect::Initialize();
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
#pragma region Setup a fake test map and test monster
game->MAP_DATA.Unlock();
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();
game->MAP_DATA.SetInitialized();
}
~PlayerTests(){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
};
TEST(PlayerTests,"PlayerAlive"){
REQUIRE(player->IsAlive());
}
TEST(PlayerTests,"PlayerTakesDamageAndNoDamageFlagWorks"){
player->Hurt(30,player->OnUpperLevel(),player->GetZ());
REQUIRE(70==player->GetHealth());
REQUIRE(size_t(1)==DAMAGENUMBER_LIST.size());
Game::Update(0.1f); //Prevent stacking up damage all on one damage number.
player->Hurt(0,player->OnUpperLevel(),player->GetZ());
REQUIRE(size_t(2)==DAMAGENUMBER_LIST.size());
Game::Update(0.1f); //Prevent stacking up damage all on one damage number.
player->Hurt(0,player->OnUpperLevel(),player->GetZ(),HurtFlag::NO_DAMAGE_NUMBER);
REQUIRE(size_t(2)==DAMAGENUMBER_LIST.size());
}
TEST(PlayerTests,"PlayerTakesDamageSoundEffectsWork"){
player->Hurt(30,player->OnUpperLevel(),player->GetZ());
REQUIRE(1==SoundEffect::soundsPlayedLog["Player Hit"]);
Game::Update(0.1f); //Prevent stacking up damage all on one damage number.
player->Hurt(20,player->OnUpperLevel(),player->GetZ(),HurtFlag::NO_DAMAGE_NUMBER);
REQUIRE(1==SoundEffect::soundsPlayedLog["Player Hit"]);
Game::Update(0.1f); //Prevent stacking up damage all on one damage number.
player->Hurt(5,player->OnUpperLevel(),player->GetZ(),HurtFlag::DOT);
REQUIRE(1==SoundEffect::soundsPlayedLog["Player Hit"]);
}
TEST(PlayerTests,"PlayerBlockingTakesNoDamage"){
player->SetState(State::BLOCK);
player->Hurt(30,player->OnUpperLevel(),player->GetZ());
REQUIRE(100==player->GetHealth());
}
TEST(PlayerTests,"PlayerIframesPreventsDamage"){
player->ApplyIframes(0.5f);
player->Hurt(30,player->OnUpperLevel(),player->GetZ());
REQUIRE(100==player->GetHealth());
}
TEST(PlayerTests,"PlayerConsumesManaOnRightClickAbilityUse"){
int abilityManaCost{player->GetRightClickAbility().manaCost};
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetRightClickAbility(),testKeyboardInput);
REQUIRE(player->GetMaxMana()-abilityManaCost==player->GetMana());
}
TEST(PlayerTests,"PlayerConsumesManaOnAbility1Use"){
int abilityManaCost{player->GetAbility1().manaCost};
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility1(),testKeyboardInput);
REQUIRE(player->GetMaxMana()-abilityManaCost==player->GetMana());
}
TEST(PlayerTests,"PlayerConsumesManaOnAbility2Use"){
int abilityManaCost{player->GetAbility2().manaCost};
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility2(),testKeyboardInput);
REQUIRE(player->GetMaxMana()-abilityManaCost==player->GetMana());
}
TEST(PlayerTests,"PlayerConsumesManaOnAbility3Use"){
int abilityManaCost{player->GetAbility3().manaCost};
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
REQUIRE(player->GetMaxMana()-abilityManaCost==player->GetMana());
}
TEST(PlayerTests,"PlayerConsumesManaOnAbility4Use"){
int abilityManaCost{player->GetAbility4().manaCost};
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility4(),testKeyboardInput);
REQUIRE(player->GetMaxMana()-abilityManaCost==player->GetMana());
}
TEST(PlayerTests,"WarriorBlockActivatesBlock"){
player->GetRightClickAbility().action(player,player->GetPos());
REQUIRE(int(State::BLOCK)==int(player->GetState()));
}
TEST(PlayerTests,"WizardCastDoesNotConsumeManaImmediately"){
testGame->ResetPlayerAndChangeClass(WIZARD);
player=testGame->GetPlayer(); //The player pointer has been reassigned...
//Ability 3 is Meteor, which is a cast. Mana should not be consumed for a spell that begins as a cast.
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
REQUIRE(player->GetMaxMana()==player->GetMana());
}
TEST(PlayerTests,"RangerCastDoesNotConsumeManaImmediately"){
testGame->ResetPlayerAndChangeClass(RANGER);
player=testGame->GetPlayer(); //The player pointer has been reassigned...
//Ability 2 is Charged Shot, which is a cast. Mana should not be consumed for a spell that begins as a cast.
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility2(),testKeyboardInput);
REQUIRE(player->GetMaxMana()==player->GetMana());
}
TEST(PlayerTests,"PlayerTakesDamageWithDamageReductionApplied"){
std::weak_ptr<Item>testArmor{Inventory::AddItem("Bone Armor"s)};
testArmor.lock()->enhancementLevel="Item.Item Max Enhancement Level"_I; //Force enhance the item to the max enhancement level.
Inventory::EquipItem(testArmor,EquipSlot::ARMOR);
//If any of these values change, the armor piece may return a different value and these need to be updated for
//BONE ARMOR +10!
REQUIRE(72.f==testArmor.lock()->GetStats().A_Read("Defense"));
REQUIRE(14.f==testArmor.lock()->GetStats().A_Read("Health"));
REQUIRE(7.f==testArmor.lock()->GetStats().A_Read("Attack"));
player->Hurt(30,player->OnUpperLevel(),player->GetZ());
REQUIRE(74==player->GetHealth()); //Even though we are supposed to take 30 damage, damage reduction reduces it to 26 instead. (Random variance is controlled during testing. If this number changes at some point, either the damage formula or the item's stats have changed!)
}
TEST(PlayerTests,"PlayerSetEffectsAcknowledged"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor"s)};
setArmor.lock()->enhancementLevel="Item.Item Max Enhancement Level"_I; //Force enhance the item to the max enhancement level.
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
//This gear is supposed to be provide an additional 100 defense, 1000 health, and 100 attack.
REQUIRE(100.f+player->GetBaseStat("Defense")==player->GetEquipStat("Defense"));
REQUIRE(100.f+player->GetBaseStat("Attack")==player->GetEquipStat("Attack"));
REQUIRE(1000.f+player->GetBaseStat("Health")==player->GetEquipStat("Health"));
}
TEST(PlayerTests,"PlayerSetEffectsAcknowledged2"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
setArmor.lock()->enhancementLevel="Item.Item Max Enhancement Level"_I; //Force enhance the item to the max enhancement level.
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
//This gear is supposed to be provide an additional 100% defense, 100% Attack, 100% Move Spd, 100% CDR, 100% Crit Rate, 100% Crit Dmg, 100% Health %, 100% HP/6 Recovery
REQUIRE(100.f+player->GetBaseStat("Defense %")==player->GetEquipStat("Defense %"));
REQUIRE(100.f+player->GetBaseStat("Attack %")==player->GetEquipStat("Attack %"));
REQUIRE(100.f+player->GetBaseStat("Move Spd %")==player->GetEquipStat("Move Spd %"));
REQUIRE(100.f+player->GetBaseStat("CDR")==player->GetEquipStat("CDR"));
REQUIRE(100.f+player->GetBaseStat("Crit Rate")==player->GetEquipStat("Crit Rate"));
REQUIRE(100.f+player->GetBaseStat("Crit Dmg")==player->GetEquipStat("Crit Dmg"));
REQUIRE(100.f+player->GetBaseStat("Health %")==player->GetEquipStat("Health %"));
REQUIRE(100.f+player->GetBaseStat("HP6 Recovery %")==player->GetEquipStat("HP6 Recovery %"));
}
TEST(PlayerTests,"PlayerSetEffectsAcknowledged3"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor3"s)};
setArmor.lock()->enhancementLevel="Item.Item Max Enhancement Level"_I; //Force enhance the item to the max enhancement level.
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
//This gear is supposed to be provide an additional 100% HP/4 Recovery, 100% Damage Reduction
REQUIRE(100.f+player->GetBaseStat("HP4 Recovery %")==player->GetEquipStat("HP4 Recovery %"));
REQUIRE(100.f+player->GetBaseStat("Damage Reduction")==player->GetEquipStat("Damage Reduction"));
}
TEST(PlayerTests,"PlayerSetEffectsAcknowledged4"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor4"s)};
setArmor.lock()->enhancementLevel="Item.Item Max Enhancement Level"_I; //Force enhance the item to the max enhancement level.
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
//This gear is supposed to be provide an additional 100% HP/s Recovery, 50% Attack Spd, and 100 More Base Mana.
REQUIRE(100.f+player->GetBaseStat("HP Recovery %")==player->GetEquipStat("HP Recovery %"));
REQUIRE(50.f+player->GetBaseStat("Attack Spd")==player->GetEquipStat("Attack Spd"));
REQUIRE(100.f+player->GetBaseStat("Mana")==player->GetEquipStat("Mana"));
}
TEST(PlayerTests,"MultiPieceSetEffectsWork"){
std::weak_ptr<Item>testArmor{Inventory::AddItem("Bone Armor"s)};
std::weak_ptr<Item>testHeadpiece{Inventory::AddItem("Bone Helmet"s)};
std::weak_ptr<Item>testLeggings{Inventory::AddItem("Bone Pants"s)};
std::weak_ptr<Item>testGloves{Inventory::AddItem("Bone Gloves"s)};
REQUIRE(player->GetBaseStat("Crit Rate")==player->GetEquipStat("Crit Rate"));
REQUIRE(player->GetBaseStat("Crit Dmg")==player->GetEquipStat("Crit Dmg"));
Inventory::EquipItem(testArmor,EquipSlot::ARMOR);
Inventory::EquipItem(testHeadpiece,EquipSlot::HELMET);
REQUIRE(5+player->GetBaseStat("Crit Rate")==player->GetEquipStat("Crit Rate"));
REQUIRE(player->GetBaseStat("Crit Dmg")==player->GetEquipStat("Crit Dmg"));
Inventory::UnequipItem(EquipSlot::ARMOR);
REQUIRE(player->GetBaseStat("Crit Rate")==player->GetEquipStat("Crit Rate"));
REQUIRE(player->GetBaseStat("Crit Dmg")==player->GetEquipStat("Crit Dmg"));
Inventory::EquipItem(testArmor,EquipSlot::ARMOR);
Inventory::EquipItem(testLeggings,EquipSlot::PANTS);
Inventory::EquipItem(testGloves,EquipSlot::GLOVES);
REQUIRE(5+player->GetBaseStat("Crit Rate")==player->GetEquipStat("Crit Rate"));
REQUIRE(7+player->GetBaseStat("Crit Dmg")==player->GetEquipStat("Crit Dmg"));
}
TEST(PlayerTests,"AccessoriesWork"){
std::weak_ptr<Item>testRing{Inventory::AddItem("Bird's Treasure"s)};
std::weak_ptr<Item>testRing2{Inventory::AddItem("Bird's Treasure"s)};
REQUIRE(3.f==testRing.lock()->RandomStats().A_Read("Move Spd %"));
REQUIRE(2.f==testRing.lock()->RandomStats().A_Read("Crit Rate"));
REQUIRE(5.f==testRing.lock()->RandomStats().A_Read("Mana"));
REQUIRE(2.f==testRing2.lock()->RandomStats().A_Read("Move Spd %"));
REQUIRE(2.f==testRing2.lock()->RandomStats().A_Read("Crit Rate"));
REQUIRE(2.f==testRing2.lock()->RandomStats().A_Read("Mana"));
REQUIRE((testRing.lock()->RandomStats().A_Read("Move Spd %")>=ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Move Spd %")&&
testRing.lock()->RandomStats().A_Read("Move Spd %")<=ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Move Spd %")));
REQUIRE((testRing.lock()->RandomStats().A_Read("Crit Rate")>=ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Crit Rate")&&
testRing.lock()->RandomStats().A_Read("Crit Rate")<=ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Crit Rate")));
REQUIRE((testRing.lock()->RandomStats().A_Read("Mana")>=ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Mana")&&
testRing.lock()->RandomStats().A_Read("Mana")<=ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Mana")));
REQUIRE((testRing2.lock()->RandomStats().A_Read("Move Spd %")>=ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Move Spd %")&&
testRing2.lock()->RandomStats().A_Read("Move Spd %")<=ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Move Spd %")));
REQUIRE((testRing2.lock()->RandomStats().A_Read("Crit Rate")>=ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Crit Rate")&&
testRing2.lock()->RandomStats().A_Read("Crit Rate")<=ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Crit Rate")));
REQUIRE((testRing2.lock()->RandomStats().A_Read("Mana")>=ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Mana")&&
testRing2.lock()->RandomStats().A_Read("Mana")<=ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Mana")));
//Ensure stats are default.
REQUIRE(player->GetBaseStat("Move Spd %")==player->GetEquipStat("Move Spd %"));
REQUIRE(player->GetBaseStat("Crit Rate")==player->GetEquipStat("Crit Rate"));
REQUIRE(player->GetBaseStat("Mana")==player->GetEquipStat("Mana"));
Inventory::EquipItem(testRing,EquipSlot::RING1);
REQUIRE(3+player->GetBaseStat("Move Spd %")==player->GetEquipStat("Move Spd %"));
REQUIRE(2+player->GetBaseStat("Crit Rate")==player->GetEquipStat("Crit Rate"));
REQUIRE(5+player->GetBaseStat("Mana")==player->GetEquipStat("Mana"));
//Equipping to same slot should undo the previous stats and apply the new ring's stats.
Inventory::EquipItem(testRing2,EquipSlot::RING1);
REQUIRE(2+player->GetBaseStat("Move Spd %")==player->GetEquipStat("Move Spd %"));
REQUIRE(2+player->GetBaseStat("Crit Rate")==player->GetEquipStat("Crit Rate"));
REQUIRE(2+player->GetBaseStat("Mana")==player->GetEquipStat("Mana"));
Inventory::UnequipItem(EquipSlot::RING1);
//Ensure stats are default again.
REQUIRE(player->GetBaseStat("Move Spd %")==player->GetEquipStat("Move Spd %"));
REQUIRE(player->GetBaseStat("Crit Rate")==player->GetEquipStat("Crit Rate"));
REQUIRE(player->GetBaseStat("Mana")==player->GetEquipStat("Mana"));
Inventory::EquipItem(testRing,EquipSlot::RING1);
Inventory::EquipItem(testRing2,EquipSlot::RING2);
REQUIRE(5+player->GetBaseStat("Move Spd %")==player->GetEquipStat("Move Spd %"));
REQUIRE(4+player->GetBaseStat("Crit Rate")==player->GetEquipStat("Crit Rate"));
REQUIRE(7+player->GetBaseStat("Mana")==player->GetEquipStat("Mana"));
}
TEST(PlayerTests,"FlatDefenseStatEquipCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor"s)};
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
player->Hurt(100,player->OnUpperLevel(),player->GetZ());
REQUIRE(17==player->GetHealth());
}
TEST(PlayerTests,"HealthStatEquipCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor"s)};
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(1000+player->GetBaseStat("Health")==float(player->GetMaxHealth()));
}
TEST(PlayerTests,"HealthStatUpBuffCheck"){
Game::AddBuffToPlayer(BuffType::STAT_UP,5.f,1000.f,{"Health"});
REQUIRE(1000+player->GetBaseStat("Health")==float(player->GetMaxHealth()));
}
TEST(PlayerTests,"HealthPctStatUpBuffCheck"){
Game::AddBuffToPlayer(BuffType::STAT_UP,5.f,1000.0_Pct,{"Health %"});
REQUIRE(1000+player->GetBaseStat("Health")==float(player->GetMaxHealth()));
}
TEST(PlayerTests,"IllegalCritRateStatUpBuffCheck"){
try{
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"Crit Rate"});
FAIL("Adding a Crit Rate buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(PlayerTests,"IllegalCritDmgStatUpBuffCheck"){
try{
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"Crit Damage"});
FAIL("Adding a Crit Damage buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(PlayerTests,"IllegalHPRecoveryPctStatUpBuffCheck"){
try{
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"HP Recovery %"});
FAIL("Adding a HP Recovery % buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(PlayerTests,"IllegalHP6RecoveryPctStatUpBuffCheck"){
try{
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"HP6 Recovery %"});
FAIL("Adding a HP6 Recovery % buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(PlayerTests,"IllegalHP4RecoveryPctStatUpBuffCheck"){
try{
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"HP4 Recovery %"});
FAIL("Adding a HP4 Recovery % buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(PlayerTests,"IllegalDamageReductionStatUpBuffCheck"){
try{
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"Damage Reduction"});
FAIL("Adding a Damage Reduction buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(PlayerTests,"IllegalAttackSpdStatUpBuffCheck"){
try{
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"Attack Spd"});
FAIL("Adding a Attack Spd buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){};
}
TEST(PlayerTests,"IllegalManaStatUpBuffCheck"){
try{
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"Mana"});
FAIL("Adding a Mana buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST(PlayerTests,"AttackStatEquipCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor"s)};
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(100+player->GetBaseStat("Attack")==float(player->GetAttack()));
}
TEST(PlayerTests,"DefensePctStatEquipCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(player->GetBaseStat("Defense")==float(player->GetDefense()));
std::weak_ptr<Item>bonePants{Inventory::AddItem("Bone Pants"s)};
bonePants.lock()->enhancementLevel="Item.Item Max Enhancement Level"_I; //Force enhance the item to the max enhancement level.
Inventory::EquipItem(bonePants,EquipSlot::PANTS);
REQUIRE(122.f+player->GetBaseStat("Defense")==float(player->GetDefense()));
Inventory::UnequipItem(EquipSlot::ARMOR);
player->Hurt(100,player->OnUpperLevel(),player->GetZ());
REQUIRE(13==player->GetHealth());
player->Heal(87); //Back to 100 Health.
LOG(std::format("Player Health is now {}",player->GetHealth()));
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
player->Hurt(100,player->OnUpperLevel(),player->GetZ());
REQUIRE(36==player->GetHealth());
}
TEST(PlayerTests,"AttackPctStatEquipCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.Hurt(player->GetAttack(),player->OnUpperLevel(),player->GetZ());
const int damageDealt{testMonster.GetMaxHealth()-testMonster.GetHealth()};
testMonster.Heal(testMonster.GetMaxHealth());
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(player->GetBaseStat("Attack")*2==float(player->GetAttack()));
testMonster.Hurt(player->GetAttack(),player->OnUpperLevel(),player->GetZ());
const int enhancedDamageDealt{testMonster.GetMaxHealth()-testMonster.GetHealth()};
REQUIRE(enhancedDamageDealt==damageDealt*5);
}
TEST(PlayerTests,"MoveSpdPctStatEquipCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
REQUIRE(1.f==player->GetMoveSpdMult());
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(2.f==player->GetMoveSpdMult());
}
TEST(PlayerTests,"CDRStatEquipCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
REQUIRE(0.f==player->GetCooldownReductionPct());
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(1.f==player->GetCooldownReductionPct());
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetRightClickAbility(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetAbility1(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetAbility2(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetAbility4(),testKeyboardInput);
Inventory::AddItem("Health Potion"s);
Inventory::AddItem("Berries"s);
Inventory::AddItem("Mana Potion"s);
game->SetLoadoutItem(0,"Health Potion");
game->SetLoadoutItem(1,"Berries");
game->SetLoadoutItem(2,"Mana Potion");
player->CheckAndPerformAbility(player->GetItem1(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetItem2(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetItem3(),testKeyboardInput);
game->SetElapsedTime(0.01f); //Force elapsed time value.
game->OnUserUpdate(0.01f); //Let 0.01 seconds go by. All abilities should be off cooldown.
REQUIRE(0.f==player->GetRightClickAbility().cooldown);
REQUIRE(0.f==player->GetAbility1().cooldown);
REQUIRE(0.f==player->GetAbility2().cooldown);
REQUIRE(0.f==player->GetAbility3().cooldown);
REQUIRE(0.f==player->GetAbility4().cooldown);
REQUIRE(0.f!=player->GetItem1().cooldown);
REQUIRE(0.f!=player->GetItem2().cooldown);
REQUIRE(0.f!=player->GetItem3().cooldown);
}
TEST(PlayerTests,"CritRateStatEquipCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
REQUIRE(0.f==player->GetCritRatePct());
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(1.f==player->GetCritRatePct());
}
TEST(PlayerTests,"CritDmgStatEquipCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
REQUIRE(0.5f==player->GetCritDmgPct());
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(1.5f==player->GetCritDmgPct());
}
TEST(PlayerTests,"HealthPctStatEquipCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
REQUIRE(100==player->GetHealth());
REQUIRE(100==player->GetMaxHealth());
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(100==player->GetHealth());
REQUIRE(200==player->GetMaxHealth());
}
TEST(PlayerTests,"HP6RecoveryStatEquipCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
REQUIRE(0.0_Pct==player->GetHP6RecoveryPct());
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(100.0_Pct==player->GetHP6RecoveryPct());
}
TEST(PlayerTests,"HP4RecoveryStatEquipCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor3"s)};
REQUIRE(0.0_Pct==player->GetHP4RecoveryPct());
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(100.0_Pct==player->GetHP4RecoveryPct());
}
TEST(PlayerTests,"DamageReductionStatEquipCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor3"s)};
REQUIRE(0.0_Pct==player->GetDamageReductionFromBuffs());
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(75.0_Pct==player->GetDamageReductionFromBuffs());
}
TEST(PlayerTests,"HPRecoveryStatEquipCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor4"s)};
REQUIRE(0.0_Pct==player->GetHPRecoveryPct());
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(100.0_Pct==player->GetHPRecoveryPct());
}
TEST(PlayerTests,"AttackSpeedStatEquipCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor4"s)};
REQUIRE(0.f==player->GetAttackRecoveryRateReduction());
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(50.f==player->GetAttackRecoveryRateReduction());
}
TEST(PlayerTests,"ManaStatEquipCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor4"s)};
REQUIRE(100==player->GetMana());
REQUIRE(100==player->GetMaxMana());
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(100==player->GetMana());
REQUIRE(200==player->GetMaxMana());
}
TEST(PlayerTests,"HP6RecoveryTest"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
game->SetElapsedTime(0.1f);
game->OnUserUpdate(0.1f); //Advance the game by 0.1s so that all healing ticks occur at least once.
//Hurt the player for most of their health.
player->Hurt(player->GetMaxHealth()-1,player->OnUpperLevel(),player->GetZ());
REQUIRE(1==player->GetHealth());
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(100.0_Pct==player->GetHP6RecoveryPct());
game->SetElapsedTime(3.f);
game->OnUserUpdate(3.f); //Wait 3 seconds. The player shouldn't be healed yet.
REQUIRE(1==player->GetHealth());
game->SetElapsedTime(3.f);
game->OnUserUpdate(3.f); //Wait 3 more seconds. The player should be healed now.
REQUIRE(player->GetMaxHealth()==player->GetHealth());
}
TEST(PlayerTests,"HP4RecoveryTest"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor3"s)};
game->SetElapsedTime(0.1f);
game->OnUserUpdate(0.1f); //Advance the game by 0.1s so that all healing ticks occur at least once.
//Hurt the player for most of their health.
player->Hurt(player->GetMaxHealth()-1,player->OnUpperLevel(),player->GetZ());
REQUIRE(1==player->GetHealth());
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(100.0_Pct==player->GetHP4RecoveryPct());
game->SetElapsedTime(2.f);
game->OnUserUpdate(2.f); //Wait 2 seconds. The player shouldn't be healed yet.
REQUIRE(1==player->GetHealth());
game->SetElapsedTime(2.f);
game->OnUserUpdate(2.f); //Wait 2 more seconds. The player should be healed now.
REQUIRE(player->GetMaxHealth()==player->GetHealth());
}
TEST(PlayerTests,"HPRecoveryTest"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor4"s)};
game->SetElapsedTime(0.1f);
game->OnUserUpdate(0.1f); //Advance the game by 0.1s so that all healing ticks occur at least once.
//Hurt the player for most of their health.
player->Hurt(player->GetMaxHealth()-1,player->OnUpperLevel(),player->GetZ());
REQUIRE(1==player->GetHealth());
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
REQUIRE(100.0_Pct==player->GetHPRecoveryPct());
game->SetElapsedTime(0.5f);
game->OnUserUpdate(0.5f); //Wait 0.5 seconds. The player shouldn't be healed yet.
REQUIRE(1==player->GetHealth());
game->SetElapsedTime(0.5f);
game->OnUserUpdate(0.5f); //Wait 0.5 more seconds. The player should be healed now.
REQUIRE(player->GetMaxHealth()==player->GetHealth());
}
TEST(PlayerTests,"AdrenalineRushSkillBuffTest"){
testGame->ResetPlayerAndChangeClass(THIEF);
player=testGame->GetPlayer(); //The player pointer has been reassigned...
REQUIRE(1.f==player->GetMoveSpdMult());
REQUIRE(0.f==player->GetAttackRecoveryRateReduction());
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
Game::Update(0.f);
REQUIRE(player->GetBuffs(BuffType::ADRENALINE_RUSH).size()>0);
REQUIRE(1.1f==player->GetMoveSpdMult());
REQUIRE(0.105f==player->GetAttackRecoveryRateReduction());
}
TEST(PlayerTests,"TrueDamageCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor3"s)};
Inventory::EquipItem(setArmor,EquipSlot::ARMOR); //Equip an item with 100% damage reduction.
player->Hurt(20,player->OnUpperLevel(),player->GetZ(),TrueDamageFlag::IGNORE_DAMAGE_RULES);
REQUIRE(80==player->GetHealth());
player->ApplyIframes(0.5f);
player->Hurt(20,player->OnUpperLevel(),player->GetZ(),TrueDamageFlag::IGNORE_DAMAGE_RULES);
REQUIRE(60==player->GetHealth());
}
TEST(PlayerTests,"DOTDamageCheck"){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor3"s)};
Inventory::EquipItem(setArmor,EquipSlot::ARMOR); //Equip an item with 100% damage reduction.
player->Hurt(20,player->OnUpperLevel(),player->GetZ(),HurtFlag::DOT);
REQUIRE(80==player->GetHealth());
REQUIRE(1==std::accumulate(DAMAGENUMBER_LIST.begin(),DAMAGENUMBER_LIST.end(),0,[](int count,const std::shared_ptr<DamageNumber>&damageNumber){
if(damageNumber->GetType()==DamageNumberType::DOT)return count+1;
else return count;
})
);
player->Update(1.0f);//Let some time pass so two DOT numbers don't stack up.
player->ApplyIframes(0.5f);
player->Hurt(20,player->OnUpperLevel(),player->GetZ(),HurtFlag::DOT);
REQUIRE(80==player->GetHealth());
REQUIRE(2==std::accumulate(DAMAGENUMBER_LIST.begin(),DAMAGENUMBER_LIST.end(),0,[](int count,const std::shared_ptr<DamageNumber>&damageNumber){
if(damageNumber->GetType()==DamageNumberType::DOT)return count+1;
else return count;
})
);
}
TEST(PlayerTests,"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);
REQUIRE(player->HasEnchant("Emergency Recovery"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING2);
REQUIRE(player->HasEnchant("Emergency Recovery"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING1);
slimeKingRing.lock()->_EnchantItem("Reaper of Souls"sv);
REQUIRE(player->HasEnchant("Reaper of Souls"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING2);
REQUIRE(player->HasEnchant("Reaper of Souls"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING1);
slimeKingRing.lock()->_EnchantItem("Attack Boost"sv);
REQUIRE(player->HasEnchant("Attack Boost"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING2);
REQUIRE(player->HasEnchant("Attack Boost"));
std::weak_ptr<Item>leatherHelmet{Inventory::AddItem("Leather Helmet"s)};
std::weak_ptr<Item>leatherArmor{Inventory::AddItem("Leather Armor"s)};
std::weak_ptr<Item>leatherPants{Inventory::AddItem("Leather Pants"s)};
std::weak_ptr<Item>leatherGloves{Inventory::AddItem("Leather Gloves"s)};
std::weak_ptr<Item>leatherShoes{Inventory::AddItem("Leather Shoes"s)};
std::weak_ptr<Item>woodenSword{Inventory::AddItem("Wooden Sword"s)};
Inventory::EquipItem(leatherHelmet,EquipSlot::HELMET);
Inventory::EquipItem(leatherArmor,EquipSlot::ARMOR);
Inventory::EquipItem(leatherPants,EquipSlot::PANTS);
Inventory::EquipItem(leatherGloves,EquipSlot::GLOVES);
Inventory::EquipItem(leatherShoes,EquipSlot::SHOES);
Inventory::EquipItem(woodenSword,EquipSlot::WEAPON);
leatherHelmet.lock()->_EnchantItem("Wizard's Soul"sv);
REQUIRE(player->HasEnchant("Wizard's Soul"));
leatherArmor.lock()->_EnchantItem("Ability Haste"sv);
REQUIRE(player->HasEnchant("Ability Haste"));
leatherPants.lock()->_EnchantItem("Improved Ground Slam"sv);
REQUIRE(player->HasEnchant("Improved Ground Slam"));
leatherGloves.lock()->_EnchantItem("Battle Shout"sv);
REQUIRE(player->HasEnchant("Battle Shout"));
leatherShoes.lock()->_EnchantItem("Attack Boost"sv);
Inventory::UnequipItem(EquipSlot::RING2);
REQUIRE(player->HasEnchant("Attack Boost"));
woodenSword.lock()->_EnchantItem("Mana Pool"sv);
REQUIRE(player->HasEnchant("Mana Pool"));
Inventory::UnequipItem(EquipSlot::HELMET);
Inventory::UnequipItem(EquipSlot::ARMOR);
Inventory::UnequipItem(EquipSlot::PANTS);
Inventory::UnequipItem(EquipSlot::GLOVES);
Inventory::UnequipItem(EquipSlot::SHOES);
Inventory::UnequipItem(EquipSlot::WEAPON);
Inventory::UnequipItem(EquipSlot::RING1);
Inventory::UnequipItem(EquipSlot::RING2);
REQUIRE(!player->HasEnchant("Emergency Recovery"));
REQUIRE(!player->HasEnchant("Reaper of Souls"));
REQUIRE(!player->HasEnchant("Attack Boost"));
REQUIRE(!player->HasEnchant("Wizard's Soul"));
REQUIRE(!player->HasEnchant("Ability Haste"));
REQUIRE(!player->HasEnchant("Improved Ground Slam"));
REQUIRE(!player->HasEnchant("Battle Shout"));
REQUIRE(!player->HasEnchant("Attack Boost"));
REQUIRE(!player->HasEnchant("Mana Pool"));
}
TEST(PlayerTests,"AutoAttackReductionFunctionCheck"){
REQUIRE(0.f==player->GetAutoAttackTimer());
player->AutoAttack();
REQUIRE("Warrior.Auto Attack.Cooldown"_F==player->GetAutoAttackTimer());
player->ReduceAutoAttackTimer(0.2f);
REQUIRE("Warrior.Auto Attack.Cooldown"_F-0.2f==player->GetAutoAttackTimer());
}
TEST(PlayerTests,"CooldownChargesCheck"){
player->GetAbility4()=Ranger::ability1;
for(const std::reference_wrapper<Ability>&a:player->GetAbilities()){
if(a.get().name=="???")continue;
REQUIRE(uint8_t(1)==a.get().charges);
REQUIRE(0.f==a.get().cooldown);
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(a.get(),testKeyboardInput);
REQUIRE(uint8_t(0)==a.get().charges);
REQUIRE(a.get().COOLDOWN_TIME==a.get().cooldown);
player->RestoreMana(100);
player->SetState(State::NORMAL);
player->CheckAndPerformAbility(a.get(),testKeyboardInput);
a.get().MAX_CHARGES=5;
for(int i:std::ranges::iota_view(0,5)){
testGame->SetElapsedTime(a.get().COOLDOWN_TIME);
testGame->OnUserUpdate(a.get().COOLDOWN_TIME);
REQUIRE(uint8_t(i+1)==a.get().charges);
if(i!=4)REQUIRE(a.get().COOLDOWN_TIME==a.get().cooldown);
else REQUIRE(0.f==a.get().cooldown);
}
player->RestoreMana(100);
player->SetState(State::NORMAL);
}
}
TEST(PlayerTests,"ChargesEquipBehaviorCheck"){
testGame->ResetPlayerAndChangeClass(RANGER);
player=testGame->GetPlayer();
player->GetAbility3().charges=3;
testKey->bHeld=true; //Force the key to be held down for testing purposes.
testGame->SetElapsedTime(0.25f);
testGame->OnUserUpdate(0.25f);
REQUIRE(uint8_t(1)==player->GetAbility3().charges);
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
REQUIRE(uint8_t(0)==player->GetAbility3().charges);
}
TEST(PlayerTests,"CooldownEquipBehaviorCheck"){
testGame->ResetPlayerAndChangeClass(RANGER);
player=testGame->GetPlayer();
float oldCooldownTime{player->GetAbility3().GetCooldownTime()};
player->GetAbility3().cooldown=player->GetAbility3().GetCooldownTime();
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
Inventory::EquipItem(nullRing,EquipSlot::RING1);
nullRing.lock()->_EnchantItem("Multi-Multishot"sv);
testKey->bHeld=true; //Force the key to be held down for testing purposes.
REQUIRE(player->GetAbility3().GetCooldownTime()==oldCooldownTime-oldCooldownTime*"Multi-Multishot"_ENC["COOLDOWN REDUCTION PCT"]/100.f);
testGame->SetElapsedTime(0.f);
testGame->OnUserUpdate(0.f);
REQUIRE(player->GetAbility3().GetCooldownTime()==player->GetAbility3().cooldown);
}
TEST(PlayerTests,"PlayerGetShieldCheck"){
player=testGame->GetPlayer();
REQUIRE(0U==player->GetShield());
}
TEST(PlayerTests,"PlayerAddShieldCheck"){
player=testGame->GetPlayer();
player->AddShield(60U,5.f,PlayerTimerType::ADVANCE_SHIELD_TIMER);
REQUIRE(60U==player->GetShield());
testGame->SetElapsedTime(5.f);
testGame->OnUserUpdate(5.f);
REQUIRE(0U==player->GetShield());
}
TEST(PlayerTests,"PlayerMultiShieldCheck"){
player=testGame->GetPlayer();
player->AddShield(60U,5.f,PlayerTimerType::ADVANCE_SHIELD_TIMER);
player->AddShield(100U,2.f,PlayerTimerType::PLAYER_OUTLINE_TIMER);
REQUIRE(160U==player->GetShield());
testGame->SetElapsedTime(2.f);
testGame->OnUserUpdate(2.f);
REQUIRE(60U==player->GetShield());
testGame->SetElapsedTime(3.f);
testGame->OnUserUpdate(3.f);
REQUIRE(0U==player->GetShield());
}
TEST(PlayerTests,"PlayerSubtractShieldCheck"){
player=testGame->GetPlayer();
player->AddShield(60U,5.f,PlayerTimerType::ADVANCE_SHIELD_TIMER);
REQUIRE(60U==player->GetShield());
player->SubtractShield(40U);
REQUIRE(20U==player->GetShield());
player->SubtractShield(200U);
REQUIRE(0U==player->GetShield());
}
TEST(PlayerTests,"PlayerDamageShieldCheck"){
player=testGame->GetPlayer();
player->AddShield(60U,5.f,PlayerTimerType::ADVANCE_SHIELD_TIMER);
REQUIRE(60U==player->GetShield());
player->Hurt(20U,player->OnUpperLevel(),player->GetZ());
REQUIRE(40U==player->GetShield());
REQUIRE(player->GetMaxHealth()==player->GetHealth());
player->Hurt(200U,player->OnUpperLevel(),player->GetZ());
REQUIRE(0U==player->GetShield());
REQUIRE(player->GetMaxHealth()==player->GetHealth());
player->Hurt(10U,player->OnUpperLevel(),player->GetZ());
REQUIRE(player->GetMaxHealth()-10==player->GetHealth());
}
TEST(PlayerTests,"PlayerHasteMoveCheck"){
Game::LoadLevelWithTMX("TEST_MAP",testGame.get());
player=testGame->GetPlayer();
player->ForceSetPos({12,12});
testKeyboardInput.AddKeybind(Input{InputType::KEY,D});
testKey=testGame->GetKeyboardState(D);
testKey->bHeld=true;
{
const vf2d originalPlayerPos{player->GetPos()};
Game::Update(0.5f);
REQUIRE(originalPlayerPos!=player->GetPos());
const float movedDist{(originalPlayerPos-player->GetPos()).mag()};
REQUIRE(50.f==movedDist);
}
Game::AddBuffToPlayer(BuffType::HASTEN,INFINITE,0.3f);
const vf2d originalHastenedPlayerPos{player->GetPos()};
Game::Update(0.5f);
const float hastenMovedDist{(originalHastenedPlayerPos-player->GetPos()).mag()};
REQUIRE(65.f==hastenMovedDist);
player->RemoveAllBuffs();
{
const vf2d originalPlayerPos{player->GetPos()};
Game::Update(0.5f);
const float movedDist{(originalPlayerPos-player->GetPos()).mag()};
REQUIRE(50.f==movedDist);
}
}
TEST(PlayerTests,"PlayerHasteCooldownCheck"){
Game::LoadLevelWithTMX("TEST_MAP",testGame.get());
player=testGame->GetPlayer();
player->ForceSetPos({12,12});
testKeyboardInput.AddKeybind(Input{InputType::KEY,D});
testKey=testGame->GetKeyboardState(D);
testKey->bHeld=true;
Game::CastAbilityAtLocation(player->GetAbility3(),{});
Game::Update(5.f);
REQUIRE(player->GetAbility3().GetCooldownTime()-5.f==player->GetAbility3().cooldown);
Game::ResetPlayerAndChangeClass(Class::WARRIOR,player,&*testGame);
Game::CastAbilityAtLocation(player->GetAbility3(),{});
Game::AddBuffToPlayer(BuffType::HASTEN,INFINITE,0.3f);
Game::Update(5.f);
REQUIRE(player->GetAbility3().GetCooldownTime()-6.5f==player->GetAbility3().cooldown);
player->RemoveAllBuffs();
player->GetAbility3().cooldown=0;
Game::CastAbilityAtLocation(player->GetAbility3(),{});
Game::Update(5.f);
REQUIRE(player->GetAbility3().GetCooldownTime()-5.f==player->GetAbility3().cooldown);
}

View File

@ -7,4 +7,6 @@
}
$_
} |
Out-File $args[0] -encoding ascii -nonewline
Out-File $args[0] -encoding ascii -nonewline
mv *Tests.cpp tests/

View File

@ -247,12 +247,12 @@ const std::string util::toLower(const std::string oldStr){
const std::string util::GetCaseInsensitiveFilename(const std::string&filename){
if(std::filesystem::exists(filename))return filename;
if(!std::filesystem::path{filename}.has_parent_path())ERR("WARNING! Invalid path {} for filename {}! THIS SHOULD NOT BE HAPPENING!");
if(!std::filesystem::path{filename}.has_parent_path())ERR(std::format("WARNING! Invalid path {} for filename {}! THIS SHOULD NOT BE HAPPENING!",std::filesystem::path{filename}.string(),filename));
for(auto&path:std::filesystem::recursive_directory_iterator(std::filesystem::path{filename}.parent_path())){
if(path.is_regular_file()&&util::toLower(path.path().filename().string())==util::toLower(std::filesystem::path{filename}.filename().string())){
return path.path().string();
}
}
ERR("WARNING! Could not find or match case for filename {}! THIS SHOULD NOT BE HAPPENING!");
ERR(std::format("WARNING! Could not find or match case for filename {}! THIS SHOULD NOT BE HAPPENING!",filename));
std::unreachable();
}

View File

@ -91,7 +91,7 @@ file(
GLOB SOURCE_CXX_FILES
"${SOURCE_CXX_SRC_DIR}/*.cpp"
)
if (NOT EMSCRIPTEN)
if (NOT EMSCRIPTEN AND NOT TEST_ONLY)
file(
GLOB SOURCE_CXX_FILES2
"${SOURCE_CXX_SRC_DIR}/discord-files/*.cpp"
@ -100,37 +100,26 @@ endif()
list(APPEND SOURCE_CXX_FILES ${SOURCE_CXX_FILES2})
if(TEST_ONLY)
file(
GLOB SOURCE_CXX_TEST_FILES
"${SOURCE_CXX_SRC_DIR}/tests/*.cpp"
)
list(APPEND SOURCE_CXX_FILES ${SOURCE_CXX_TEST_FILES})
else()
list(FILTER SOURCE_CXX_FILES EXCLUDE REGEX ".*Tests.cpp")
endif()
# Search in the "cmake" directory for additional CMake modules.
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
if(NOT TEST_ONLY)
# Executable aka binary output
# Executable aka binary output
if(TEST_ONLY)
add_executable(AiL_test ${SOURCE_CXX_FILES})
else()
add_executable("${OutputExecutable}" ${SOURCE_CXX_FILES})
endif()
if(TEST_ONLY)
set(SOURCE_CXX_TEST_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Adventures in Lestoria GTest")
# Source Files are Curated Here
file(
GLOB SOURCE_CXX_TEST_FILES
"${SOURCE_CXX_TEST_SRC_DIR}/*.cpp"
)
add_executable(AiL_test ${SOURCE_CXX_TEST_FILES})
endif()
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
######################################################################
# MacOS
######################################################################
@ -304,27 +293,6 @@ if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN AND NOT TEST_ONLY)
-static-libstdc++
"Adventures in Lestoria/discord_game_sdk.so"
"Adventures in Lestoria/libsteam_api.so")
enable_testing()
set(SOURCE_CXX_TEST_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Adventures in Lestoria GTest")
# Source Files are Curated Here
file(
GLOB SOURCE_CXX_TEST_FILES
"${SOURCE_CXX_TEST_SRC_DIR}/*.cpp"
)
add_executable(AiL_test ${SOURCE_CXX_TEST_FILES})
target_link_libraries(
AiL_test
GTest::gtest_main
)
include(GoogleTest)
gtest_discover_tests(AiL_test)
endif() # Linux
if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN AND TEST_ONLY)
@ -332,12 +300,12 @@ if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN AND TEST_ONLY)
find_package(Threads REQUIRED)
target_link_libraries(AiL_test Threads::Threads)
include_directories(${Threads_INCLUDE_DIRS})
target_link_libraries(AiL_test -lstdc++exp)
find_package(Freetype REQUIRED)
target_link_libraries(AiL_test ${FREETYPE_LIBRARIES})
target_include_directories(AiL_test PRIVATE ${FREETYPE_INCLUDE_DIRS})
target_include_directories(AiL_test PRIVATE "${C_CXX_SOURCES_DIR}/discord-files")
target_include_directories(AiL_test PRIVATE "${C_CXX_SOURCES_DIR}/steam")
link_directories("Adventures in Lestoria")
@ -346,7 +314,8 @@ if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN AND TEST_ONLY)
include_directories(${PNG_INCLUDE_DIRS})
# stdc++fs
target_link_libraries(AiL_test stdc++fs)
target_link_libraries(AiL_test stdc++fs)
target_link_libraries(AiL_test stdc++exp)
link_directories("${CMAKE_SOURCE_DIR}/Adventures in Lestoria")
add_library( discord_game_sdk SHARED IMPORTED )
set_property(TARGET discord_game_sdk PROPERTY IMPORTED_LOCATION "Adventures in Lestoria/discord_game_sdk.so")
@ -358,6 +327,7 @@ if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN AND TEST_ONLY)
target_link_libraries(AiL_test dl)
add_compile_definitions(OLC_PGE_HEADLESS)
add_compile_definitions(UNIT_TESTING)
target_link_options(
AiL_test
@ -366,17 +336,6 @@ if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN AND TEST_ONLY)
"Adventures in Lestoria/discord_game_sdk.so"
"Adventures in Lestoria/libsteam_api.so")
enable_testing()
target_link_libraries(
AiL_test
GTest::gtest_main
)
include(GoogleTest)
gtest_discover_tests(AiL_test)
endif() # Linux

View File

@ -0,0 +1,2 @@
#!/bin/sh
powershell.exe -ExecutionPolicy RemoteSigned -File '.git\hooks\post-commit.ps1'

View File

@ -0,0 +1,4 @@
Compress-Archive -Path "J:\AdventuresInLestoria\x64\Unit Testing\assets" -Force -DestinationPath "J:\AdventuresInLestoria\x64\Unit Testing\assets.zip"
pscp -r -pwfile "C:\Users\sigon\OneDrive\Documents\pw.DONOTOPEN" "J:\AdventuresInLestoria\x64\Unit Testing\assets.zip" sigonasr2@projectdivar.com:/home/sigonasr2
rm "J:\AdventuresInLestoria\x64\Unit Testing\assets.zip"

View File

@ -1,5 +1,5 @@
cd "bin"
cp "../Adventures in Lestoria/discord_game_sdk.so" .
cp "../Adventures in Lestoria/libsteam_api.so" .
cd ..
ctest --output-on-failure
mkdir "Adventures in Lestoria"
cp "../Adventures in Lestoria/discord_game_sdk.so" "./Adventures in Lestoria"
cp "../Adventures in Lestoria/libsteam_api.so" "./Adventures in Lestoria"
./AiL_test -d yes --use-colour yes

View File

@ -1,3 +1,5 @@
rm CMakeCache.txt
cp "./Adventures in Lestoria/discord_game_sdk.so" .
cp "./Adventures in Lestoria/libsteam_api.so" .
git update-index --assume-unchanged "Adventures in Lestoria/packkey.cpp"
cmake -DTEST_ONLY=ON -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Release .;make -j 8