Initial code commit
This commit is contained in:
parent
0b7cd1c6c6
commit
b0541f8bd3
23
Directory.Build.props
Normal file
23
Directory.Build.props
Normal file
@ -0,0 +1,23 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<VcpkgTriplet>x86-windows</VcpkgTriplet>
|
||||
<VcpkgRoot>C:\src\vcpkg\</VcpkgRoot>
|
||||
<VcpkgInstalledDir>$(VcpkgRoot)installed\$(VcpkgTriplet)\</VcpkgInstalledDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(VcpkgInstalledDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<ObjectFileName>$(IntDir)%(RelativeDir)%(Filename).obj</ObjectFileName>
|
||||
</ClCompile>
|
||||
|
||||
<Link>
|
||||
<AdditionalLibraryDirectories>$(VcpkgInstalledDir)lib;$(VcpkgInstalledDir)debug\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<PostBuildEvent>
|
||||
if exist "$(VcpkgInstalledDir)bin\" (
|
||||
xcopy /Y /I "$(VcpkgInstalledDir)bin\*.*" "$(OutDir)"
|
||||
)
|
||||
</PostBuildEvent>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
</Project>
|
||||
7
GTA2.props
Normal file
7
GTA2.props
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<GTA2_DIR>D:\GTA2</GTA2_DIR>
|
||||
<GTA2_EXE>gta2-seuko-mod.exe</GTA2_EXE>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
25
gta2-seuko-mod.sln
Normal file
25
gta2-seuko-mod.sln
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36301.6
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gta2-seuko-mod", "gta2-seuko-mod.vcxproj", "{E36BE4A1-BF14-45D3-94FB-73C0BC74A114}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E36BE4A1-BF14-45D3-94FB-73C0BC74A114}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{E36BE4A1-BF14-45D3-94FB-73C0BC74A114}.Debug|x86.Build.0 = Debug|Win32
|
||||
{E36BE4A1-BF14-45D3-94FB-73C0BC74A114}.Release|x86.ActiveCfg = Release|Win32
|
||||
{E36BE4A1-BF14-45D3-94FB-73C0BC74A114}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {2EB2C408-09DD-4DDB-82A3-1507A8F0B787}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
231
gta2-seuko-mod.vcxproj
Normal file
231
gta2-seuko-mod.vcxproj
Normal file
@ -0,0 +1,231 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" 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>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{e36be4a1-bf14-45d3-94fb-73c0bc74a114}</ProjectGuid>
|
||||
<RootNamespace>gta2seukomod</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>gta2-seuko-mod</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="GTA2.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="GTA2.props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;GTA2SEUKOMOD_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>
|
||||
</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderOutputFile />
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)third_party\spdlog\include\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
</Link>
|
||||
<PostBuildEvent>
|
||||
<Command>if "$(GTA2_DIR)"=="" (
|
||||
echo ERROR: GTA2_DIR is not set. Make sure to set in in GTA2.props file.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "$(GTA2_EXE)"=="" (
|
||||
echo ERROR: GTA2_EXE is not set. Make sure to set in in GTA2.props file.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "$(GTA2_DIR)\" (
|
||||
echo ERROR: GTA2_DIR "$(GTA2_DIR)" specified in GTA2.props does not exist.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "$(GTA2_DIR)\$(GTA2_EXE)" (
|
||||
echo ERROR: "$(GTA2_EXE)" not found in "$(GTA2_DIR)". Check GTA2.props file.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
copy /Y "$(TargetPath)" "$(GTA2_DIR)\"
|
||||
if exist "$(TargetDir)$(TargetName).pdb" copy /Y "$(TargetDir)$(TargetName).pdb" "$(GTA2_DIR)\"
|
||||
|
||||
exit /b 0
|
||||
</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;GTA2SEUKOMOD_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>
|
||||
</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderOutputFile />
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)third_party\spdlog\include\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
</Link>
|
||||
<PostBuildEvent>
|
||||
<Command>if "$(GTA2_DIR)"=="" (
|
||||
echo ERROR: GTA2_DIR is not set. Make sure to set in in GTA2.props file.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "$(GTA2_EXE)"=="" (
|
||||
echo ERROR: GTA2_EXE is not set. Make sure to set in in GTA2.props file.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "$(GTA2_DIR)\" (
|
||||
echo ERROR: GTA2_DIR "$(GTA2_DIR)" specified in GTA2.props does not exist.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "$(GTA2_DIR)\$(GTA2_EXE)" (
|
||||
echo ERROR: "$(GTA2_EXE)" not found in "$(GTA2_DIR)". Check GTA2.props file.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
copy /Y "$(TargetPath)" "$(GTA2_DIR)\"
|
||||
if exist "$(TargetDir)$(TargetName).pdb" copy /Y "$(TargetDir)$(TargetName).pdb" "$(GTA2_DIR)\"
|
||||
|
||||
exit /b 0
|
||||
</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="src\common.h" />
|
||||
<ClInclude Include="src\core\core.h" />
|
||||
<ClInclude Include="src\core\listener-base.h" />
|
||||
<ClInclude Include="src\core\make-resolver.h" />
|
||||
<ClInclude Include="src\core\types.h" />
|
||||
<ClInclude Include="src\core\watch-manager.h" />
|
||||
<ClInclude Include="src\core\watched-base.h" />
|
||||
<ClInclude Include="src\events\game-tick.h" />
|
||||
<ClInclude Include="src\events\keyboard.h" />
|
||||
<ClInclude Include="src\game\common.h" />
|
||||
<ClInclude Include="src\game\keycode.h" />
|
||||
<ClInclude Include="src\game\functions.h">
|
||||
<DeploymentContent>false</DeploymentContent>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\game\game.h" />
|
||||
<ClInclude Include="src\game\gta2.h" />
|
||||
<ClInclude Include="src\core\event-manager.h" />
|
||||
<ClInclude Include="src\core\event-base.h" />
|
||||
<ClInclude Include="src\core\hook-manager.h" />
|
||||
<ClInclude Include="src\game\gta2_wrapper.h" />
|
||||
<ClInclude Include="src\hook-types\function-call-hook.h" />
|
||||
<ClInclude Include="src\core\hook-base.h" />
|
||||
<ClInclude Include="src\core\module-manager.h" />
|
||||
<ClInclude Include="src\core\module-base.h" />
|
||||
<ClInclude Include="src\game\memory.h" />
|
||||
<ClInclude Include="src\core\common.h" />
|
||||
<ClInclude Include="src\events\draw.h" />
|
||||
<ClInclude Include="src\hook-types\function-multi-call-hook.h" />
|
||||
<ClInclude Include="src\modules\modmenu\common.h" />
|
||||
<ClInclude Include="src\modules\modmenu\menu-base.h" />
|
||||
<ClInclude Include="src\modules\modmenu\menus\main-menu.h" />
|
||||
<ClInclude Include="src\modules\modmenu\menus\menus.h" />
|
||||
<ClInclude Include="src\modules\modmenu\menus\player-menu.h" />
|
||||
<ClInclude Include="src\modules\modmenu\menus\player-pos-menu.h" />
|
||||
<ClInclude Include="src\modules\modmenu\modmenu.h" />
|
||||
<ClInclude Include="src\modules\modmenu\root.h" />
|
||||
<ClInclude Include="src\modules\ui\common.h" />
|
||||
<ClInclude Include="src\modules\ui\component.h" />
|
||||
<ClInclude Include="src\modules\ui\components\components.h" />
|
||||
<ClInclude Include="src\modules\ui\components\horcont.h" />
|
||||
<ClInclude Include="src\modules\ui\components\margin.h" />
|
||||
<ClInclude Include="src\modules\ui\components\selectable.h" />
|
||||
<ClInclude Include="src\modules\ui\components\spacer.h" />
|
||||
<ClInclude Include="src\modules\ui\components\text.h" />
|
||||
<ClInclude Include="src\modules\ui\components\vertcont.h" />
|
||||
<ClInclude Include="src\modules\ui\controller.h" />
|
||||
<ClInclude Include="src\modules\ui\controllers\controllers.h" />
|
||||
<ClInclude Include="src\modules\ui\controllers\var-text-editable.h" />
|
||||
<ClInclude Include="src\modules\ui\controllers\menu.h" />
|
||||
<ClInclude Include="src\modules\ui\controllers\var-text.h" />
|
||||
<ClInclude Include="src\modules\ui\rect.h" />
|
||||
<ClInclude Include="src\modules\ui\root.h" />
|
||||
<ClInclude Include="src\modules\ui\ui.h" />
|
||||
<ClInclude Include="src\modules\ui\utils\string-utils.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="src\console.cpp" />
|
||||
<ClCompile Include="src\core\core.cpp" />
|
||||
<ClCompile Include="src\core\event-listener-support.h" />
|
||||
<ClCompile Include="src\core\event-manager.cpp" />
|
||||
<ClCompile Include="src\core\hook-manager.cpp" />
|
||||
<ClCompile Include="src\core\module-manager.cpp" />
|
||||
<ClCompile Include="src\core\watch-manager.cpp" />
|
||||
<ClCompile Include="src\dllmain.cpp" />
|
||||
<ClCompile Include="src\events\draw.cpp" />
|
||||
<ClCompile Include="src\events\game-tick.cpp" />
|
||||
<ClCompile Include="src\events\keyboard.cpp" />
|
||||
<ClCompile Include="src\game\keycode.cpp" />
|
||||
<ClCompile Include="src\modules\modmenu\menu-base.cpp" />
|
||||
<ClCompile Include="src\modules\modmenu\menus\main-menu.cpp" />
|
||||
<ClCompile Include="src\modules\modmenu\menus\player-menu.cpp" />
|
||||
<ClCompile Include="src\modules\modmenu\menus\player-pos-menu.cpp" />
|
||||
<ClCompile Include="src\modules\modmenu\root.cpp" />
|
||||
<ClCompile Include="src\modules\ui\component.cpp" />
|
||||
<ClCompile Include="src\modules\ui\components\selectable.cpp" />
|
||||
<ClCompile Include="src\modules\ui\components\text.cpp" />
|
||||
<ClCompile Include="src\modules\ui\controllers\menu.cpp" />
|
||||
<ClCompile Include="src\modules\ui\root.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="GTA2.props" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
246
gta2-seuko-mod.vcxproj.filters
Normal file
246
gta2-seuko-mod.vcxproj.filters
Normal file
@ -0,0 +1,246 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="src\core\common.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\game\gta2.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\game\memory.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\core\event-manager.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\core\event-base.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\core\core.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\core\hook-manager.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\events\draw.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\core\hook-base.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\game\functions.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\core\module-manager.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\core\module-base.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\hook-types\function-call-hook.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\modmenu\modmenu.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\game\game.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\game\common.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\common.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\game\gta2_wrapper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\core\watch-manager.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\core\watched-base.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\core\make-resolver.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\events\game-tick.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\hook-types\function-multi-call-hook.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\core\types.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\events\keyboard.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\ui.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\game\keycode.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\root.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\component.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\components\text.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\common.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\rect.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\components\components.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\components\vertcont.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\components\horcont.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\components\spacer.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\components\margin.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\components\selectable.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\controllers\menu.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\controllers\controllers.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\controller.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\core\listener-base.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\modmenu\common.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\modmenu\menu-base.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\modmenu\menus\menus.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\modmenu\menus\main-menu.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\modmenu\root.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\modmenu\menus\player-menu.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\modmenu\menus\player-pos-menu.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\controllers\var-text.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\controllers\var-text-editable.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\modules\ui\utils\string-utils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="src\dllmain.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\core\hook-manager.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\core\event-manager.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\core\module-manager.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\events\draw.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\core\core.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\core\watch-manager.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\events\game-tick.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\console.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\events\keyboard.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\modules\ui\root.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\modules\ui\component.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\modules\ui\components\text.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\modules\ui\components\selectable.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\modules\ui\controllers\menu.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\core\event-listener-support.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\modules\modmenu\root.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\modules\modmenu\menus\main-menu.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\modules\modmenu\menus\player-menu.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\modules\modmenu\menu-base.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\modules\modmenu\menus\player-pos-menu.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\game\keycode.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="GTA2.props" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
16
gta2-seuko-mod.vcxproj.user
Normal file
16
gta2-seuko-mod.vcxproj.user
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ShowAllFiles>true</ShowAllFiles>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LocalDebuggerCommand>$(GTA2_DIR)\$(GTA2_EXE)</LocalDebuggerCommand>
|
||||
<LocalDebuggerWorkingDirectory>$(GTA2_DIR)</LocalDebuggerWorkingDirectory>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LocalDebuggerCommand>$(GTA2_DIR)\$(GTA2_EXE)</LocalDebuggerCommand>
|
||||
<LocalDebuggerWorkingDirectory>$(GTA2_DIR)</LocalDebuggerWorkingDirectory>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
6
src/common.h
Normal file
6
src/common.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <math.h>
|
||||
#include <atlstr.h>
|
||||
#include "spdlog/spdlog.h"
|
||||
31
src/console.cpp
Normal file
31
src/console.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
#include "common.h"
|
||||
|
||||
#if defined(_DEBUG)
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
static void InitConsole()
|
||||
{
|
||||
#if defined(_DEBUG)
|
||||
if (!AttachConsole(ATTACH_PARENT_PROCESS))
|
||||
AllocConsole();
|
||||
|
||||
FILE* fDummy;
|
||||
freopen_s(&fDummy, "CONOUT$", "w", stdout);
|
||||
freopen_s(&fDummy, "CONOUT$", "w", stderr);
|
||||
freopen_s(&fDummy, "CONIN$", "r", stdin);
|
||||
|
||||
std::ios::sync_with_stdio(true);
|
||||
|
||||
auto logger = spdlog::stdout_color_mt("console");
|
||||
spdlog::set_default_logger(logger);
|
||||
spdlog::set_level(spdlog::level::debug);
|
||||
spdlog::flush_on(spdlog::level::debug);
|
||||
|
||||
spdlog::info("Debug console ready. DLL initializing...");
|
||||
#else
|
||||
spdlog::set_level(spdlog::level::off);
|
||||
#endif
|
||||
}
|
||||
21
src/core/common.h
Normal file
21
src/core/common.h
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
|
||||
|
||||
#include <windows.h>
|
||||
#include <math.h>
|
||||
#include <atlstr.h>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <typeindex>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
#include <optional>
|
||||
#include <cstddef>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
15
src/core/core.cpp
Normal file
15
src/core/core.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
#include "core.h"
|
||||
|
||||
Core::Core* Core::Core::m_instance = nullptr;
|
||||
|
||||
Core::Core::Core()
|
||||
{
|
||||
assert(m_instance == nullptr, "Core instance already exists!");
|
||||
m_instance = this;
|
||||
spdlog::info("Done initializing Core.");
|
||||
}
|
||||
|
||||
Core::Core::~Core()
|
||||
{
|
||||
m_instance = nullptr;
|
||||
}
|
||||
31
src/core/core.h
Normal file
31
src/core/core.h
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "types.h"
|
||||
#include "hook-base.h"
|
||||
#include "hook-manager.h"
|
||||
#include "event-base.h"
|
||||
#include "event-manager.h"
|
||||
#include "event-listener-support.h"
|
||||
#include "make-resolver.h"
|
||||
#include "watched-base.h"
|
||||
#include "watch-manager.h"
|
||||
#include "module-base.h"
|
||||
#include "module-manager.h"
|
||||
|
||||
namespace Core {
|
||||
class Core
|
||||
{
|
||||
public:
|
||||
Core();
|
||||
~Core();
|
||||
Core(const Core&) = delete;
|
||||
Core& operator=(const Core&) = delete;
|
||||
|
||||
private:
|
||||
static Core* m_instance;
|
||||
EventManager m_eventManager;
|
||||
HookManager m_hookManager;
|
||||
WatchManager m_watchManager;
|
||||
ModuleManager m_moduleManager;
|
||||
};
|
||||
}
|
||||
11
src/core/event-base.h
Normal file
11
src/core/event-base.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class EventBase
|
||||
{
|
||||
protected:
|
||||
EventBase() = default;
|
||||
virtual ~EventBase() = default;
|
||||
};
|
||||
}
|
||||
63
src/core/event-listener-support.h
Normal file
63
src/core/event-listener-support.h
Normal file
@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "types.h"
|
||||
#include "event-base.h"
|
||||
#include "event-manager.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
class EventListenerSupport {
|
||||
protected:
|
||||
EventListenerSupport() = default;
|
||||
|
||||
virtual ~EventListenerSupport() {
|
||||
RemoveAllEventListeners();
|
||||
}
|
||||
|
||||
template<typename EventT, typename U>
|
||||
void AddEventListener(EventMethodListener<EventT, U> method) {
|
||||
static_assert(std::is_base_of<EventBase, EventT>::value, "EventT must derive from Core::EventBase");
|
||||
const std::type_index eventTypeIdx = typeid(EventT);
|
||||
|
||||
if (m_eventListenerIds.find(eventTypeIdx) != m_eventListenerIds.end()) {
|
||||
spdlog::error("Event listener for event type {} already added", eventTypeIdx.name());
|
||||
return;
|
||||
}
|
||||
|
||||
auto* eventManager = EventManager::GetInstance();
|
||||
EventListenerId listenerId = eventManager->AddListener<EventT, U>(static_cast<U*>(this), method);
|
||||
m_eventListenerIds[eventTypeIdx] = listenerId;
|
||||
}
|
||||
|
||||
template<typename EventT>
|
||||
void RemoveEventListener() {
|
||||
static_assert(std::is_base_of<EventBase, EventT>::value, "EventT must derive from Core::EventBase");
|
||||
const std::type_index eventTypeIdx = typeid(EventT);
|
||||
|
||||
auto it = m_eventListenerIds.find(eventTypeIdx);
|
||||
if (it == m_eventListenerIds.end()) {
|
||||
spdlog::warn("No event listener found for event type {}", eventTypeIdx.name());
|
||||
return;
|
||||
}
|
||||
auto* eventManager = EventManager::GetInstance();
|
||||
eventManager->RemoveListener(eventTypeIdx, it->second);
|
||||
m_eventListenerIds.erase(it);
|
||||
}
|
||||
|
||||
void RemoveAllEventListeners() {
|
||||
auto* eventManager = EventManager::GetInstance();
|
||||
for (const auto& pair : m_eventListenerIds) {
|
||||
eventManager->RemoveListener(pair.first, pair.second);
|
||||
}
|
||||
m_eventListenerIds.clear();
|
||||
}
|
||||
|
||||
EventListenerSupport(const EventListenerSupport&) = delete;
|
||||
EventListenerSupport& operator=(const EventListenerSupport&) = delete;
|
||||
EventListenerSupport(EventListenerSupport&&) = delete;
|
||||
EventListenerSupport& operator=(EventListenerSupport&&) = delete;
|
||||
|
||||
private:
|
||||
std::unordered_map<std::type_index, EventListenerId> m_eventListenerIds;
|
||||
};
|
||||
}
|
||||
21
src/core/event-manager.cpp
Normal file
21
src/core/event-manager.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include "common.h"
|
||||
#include "event-manager.h"
|
||||
|
||||
Core::EventManager* Core::EventManager::m_instance = nullptr;
|
||||
|
||||
Core::EventManager::EventManager()
|
||||
{
|
||||
assert(m_instance == nullptr, "EventManager instance already exists!");
|
||||
m_instance = this;
|
||||
}
|
||||
|
||||
Core::EventManager::~EventManager()
|
||||
{
|
||||
m_instance = nullptr;
|
||||
}
|
||||
|
||||
Core::EventManager* Core::EventManager::GetInstance()
|
||||
{
|
||||
assert(m_instance != nullptr, "Core not initialized!");
|
||||
return m_instance;
|
||||
}
|
||||
156
src/core/event-manager.h
Normal file
156
src/core/event-manager.h
Normal file
@ -0,0 +1,156 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "types.h"
|
||||
#include "event-base.h"
|
||||
#include <queue>
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class EventManager
|
||||
{
|
||||
public:
|
||||
static EventManager* GetInstance();
|
||||
|
||||
template<typename EventT>
|
||||
void Dispatch(const EventT& event) {
|
||||
static_assert(std::is_base_of<EventBase, EventT>::value, "EventT must derive from Core::EventBase");
|
||||
|
||||
auto it = m_listeners.find(typeid(EventT));
|
||||
if (it == m_listeners.end()) return;
|
||||
|
||||
m_dispatching = true;
|
||||
auto& listeners = it->second;
|
||||
for (auto& wrapper : listeners) {
|
||||
wrapper.func(event);
|
||||
}
|
||||
m_dispatching = false;
|
||||
ProcessPendingChanges();
|
||||
}
|
||||
|
||||
template<typename EventT>
|
||||
EventListenerId AddListener(EventListener<EventT> listener) {
|
||||
static_assert(std::is_base_of<EventBase, EventT>::value, "EventT must derive from Core::EventBase");
|
||||
std::type_index eventTypeIdx = typeid(EventT);
|
||||
|
||||
auto it = m_listeners.find(eventTypeIdx);
|
||||
if (it == m_listeners.end()) {
|
||||
callInit<EventT>();
|
||||
}
|
||||
|
||||
EventListenerId id = m_nextListenerId++;
|
||||
PendingChange change = { ChangeType::Add, eventTypeIdx, id, [listener](const EventBase& e) {
|
||||
listener(static_cast<const EventT&>(e));
|
||||
} };
|
||||
if (m_dispatching) {
|
||||
m_pendingChanges.push(change);
|
||||
return id;
|
||||
}
|
||||
AddListenerEntry(change);
|
||||
return id;
|
||||
}
|
||||
|
||||
template<typename EventT, typename U>
|
||||
EventListenerId AddListener(U* instance, EventMethodListener<EventT, U> method) {
|
||||
static_assert(std::is_base_of<EventBase, EventT>::value, "EventT must derive from Core::EventBase");
|
||||
return AddListener<EventT>([instance, method](const EventT& e) {
|
||||
(instance->*method)(e);
|
||||
});
|
||||
}
|
||||
|
||||
void RemoveListener(std::type_index eventType, EventListenerId id) {
|
||||
PendingChange change = { ChangeType::Remove, eventType, id, nullptr };
|
||||
if (m_dispatching) {
|
||||
m_pendingChanges.push(change);
|
||||
return;
|
||||
}
|
||||
RemoveListenerEntry(change);
|
||||
}
|
||||
|
||||
template<typename EventT>
|
||||
void RemoveListener(EventListenerId id) {
|
||||
static_assert(std::is_base_of<EventBase, EventT>::value, "EventT must derive from Core::EventBase");
|
||||
RemoveListener(typeid(EventT), id);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
EventManager();
|
||||
~EventManager();
|
||||
EventManager(const EventManager&) = delete;
|
||||
EventManager& operator=(const EventManager&) = delete;
|
||||
|
||||
static EventManager* m_instance;
|
||||
|
||||
struct ListenerWrapper {
|
||||
EventListenerId id;
|
||||
std::function<void(const EventBase&)> func;
|
||||
};
|
||||
|
||||
enum class ChangeType { Add, Remove };
|
||||
struct PendingChange {
|
||||
ChangeType type;
|
||||
std::type_index eventType;
|
||||
EventListenerId id;
|
||||
std::function<void(const EventBase&)> func; // Only used for Add
|
||||
};
|
||||
|
||||
void AddListenerEntry(PendingChange change) {
|
||||
spdlog::debug("Adding listener id {} for event type: {}", change.id, change.eventType.name());
|
||||
auto& listeners = m_listeners[change.eventType];
|
||||
listeners.push_back({ change.id, change.func });
|
||||
}
|
||||
|
||||
void RemoveListenerEntry(PendingChange change) {
|
||||
spdlog::debug("Removing listener id {} for event type: {}", change.id, change.eventType.name());
|
||||
auto it = m_listeners.find(change.eventType);
|
||||
if (it == m_listeners.end()) {
|
||||
spdlog::warn("No listeners found for event type: {}", change.eventType.name());
|
||||
return;
|
||||
}
|
||||
auto& listeners = it->second;
|
||||
auto lit = std::find_if(listeners.begin(), listeners.end(), [id = change.id](const ListenerWrapper& wrapper) {
|
||||
return wrapper.id == id;
|
||||
});
|
||||
if (lit == listeners.end()) {
|
||||
spdlog::warn("Listener id {} not found for event type: {}", change.id, change.eventType.name());
|
||||
return;
|
||||
}
|
||||
listeners.erase(lit);
|
||||
// Note: m_listeners entry is not being removed
|
||||
}
|
||||
|
||||
void ProcessPendingChanges() {
|
||||
while (!m_pendingChanges.empty()) {
|
||||
auto change = m_pendingChanges.front();
|
||||
m_pendingChanges.pop();
|
||||
|
||||
if (change.type == ChangeType::Add) {
|
||||
AddListenerEntry(change);
|
||||
}
|
||||
|
||||
if (change.type == ChangeType::Remove) {
|
||||
RemoveListenerEntry(change);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<std::type_index, std::vector<ListenerWrapper>> m_listeners;
|
||||
EventListenerId m_nextListenerId = 1;
|
||||
bool m_dispatching = false;
|
||||
std::queue<PendingChange> m_pendingChanges;
|
||||
|
||||
template<typename T, typename = void>
|
||||
struct has_init : std::false_type {};
|
||||
|
||||
template<typename T>
|
||||
struct has_init<T, std::void_t<decltype(T::Init())>> : std::true_type {};
|
||||
|
||||
template<typename EventT>
|
||||
void callInit() {
|
||||
if constexpr (has_init<EventT>::value) {
|
||||
spdlog::debug("Calling Init for event type: {}", typeid(EventT).name());
|
||||
EventT::Init();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
12
src/core/hook-base.h
Normal file
12
src/core/hook-base.h
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class HookBase
|
||||
{
|
||||
protected:
|
||||
HookBase() = default;
|
||||
virtual ~HookBase() = default;
|
||||
};
|
||||
}
|
||||
20
src/core/hook-manager.cpp
Normal file
20
src/core/hook-manager.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
#include "hook-manager.h"
|
||||
|
||||
Core::HookManager* Core::HookManager::m_instance = nullptr;
|
||||
|
||||
Core::HookManager::HookManager()
|
||||
{
|
||||
assert(m_instance == nullptr, "HookManager instance already exists!");
|
||||
m_instance = this;
|
||||
}
|
||||
|
||||
Core::HookManager::~HookManager()
|
||||
{
|
||||
m_instance = nullptr;
|
||||
}
|
||||
|
||||
Core::HookManager* Core::HookManager::GetInstance()
|
||||
{
|
||||
assert(m_instance != nullptr, "Core not initialized!");
|
||||
return m_instance;
|
||||
}
|
||||
41
src/core/hook-manager.h
Normal file
41
src/core/hook-manager.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "hook-base.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class HookManager
|
||||
{
|
||||
public:
|
||||
static HookManager* GetInstance();
|
||||
|
||||
template<typename HookT>
|
||||
bool AddHook(const HookT& hookDef) {
|
||||
static_assert(std::is_base_of<HookBase, HookT>::value, "HookT must derive from Core::HookBase");
|
||||
spdlog::debug("Adding hook: {}", typeid(HookT).name());
|
||||
|
||||
if (m_hooks.find(&hookDef) != m_hooks.end()) {
|
||||
spdlog::debug("Hook already added: {}", typeid(HookT).name());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!HookT::Hook(hookDef)) {
|
||||
spdlog::error("Failed to add hook: {}", typeid(HookT).name());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_hooks.insert(&hookDef);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
HookManager();
|
||||
~HookManager();
|
||||
HookManager(const HookManager&) = delete;
|
||||
HookManager& operator=(const HookManager&) = delete;
|
||||
|
||||
std::unordered_set<const HookBase*> m_hooks;
|
||||
static HookManager* m_instance;
|
||||
};
|
||||
}
|
||||
55
src/core/listener-base.h
Normal file
55
src/core/listener-base.h
Normal file
@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "types.h"
|
||||
#include "event-base.h"
|
||||
#include "event-manager.h"
|
||||
|
||||
namespace Core {
|
||||
class ListenerBase {
|
||||
ListenerBase() = delete;
|
||||
virtual ~ListenerBase() {
|
||||
RemoveAllEventListeners();
|
||||
}
|
||||
|
||||
template<typename EventT, typename U>
|
||||
void AddEventListener(EventMethodListener<EventT, U> method) {
|
||||
static_assert(std::is_base_of<Event, EventT>::value, "EventT must derive from Core::Event");
|
||||
const std::type_index eventTypeIdx = typeid(EventT);
|
||||
|
||||
if (m_eventListenerIds.find(eventTypeIdx) != m_eventListenerIds.end()) {
|
||||
spdlog::error("Event listener for event type {} already added", eventTypeIdx.name());
|
||||
return;
|
||||
}
|
||||
|
||||
auto* eventManager = EventManager::GetInstance();
|
||||
EventListenerId listenerId = eventManager->AddListener<EventT, U>(static_cast<U*>(this), method);
|
||||
m_eventListenerIds[eventTypeIdx] = listenerId;
|
||||
}
|
||||
|
||||
template<typename EventT>
|
||||
void RemoveEventListener() {
|
||||
static_assert(std::is_base_of<Event, EventT>::value, "EventT must derive from Core::Event");
|
||||
const std::type_index eventTypeIdx = typeid(EventT);
|
||||
|
||||
auto it = m_eventListenerIds.find(eventTypeIdx);
|
||||
if (it == m_eventListenerIds.end()) {
|
||||
spdlog::warn("No event listener found for event type {}", eventTypeIdx.name());
|
||||
return;
|
||||
}
|
||||
auto* eventManager = EventManager::GetInstance();
|
||||
eventManager->RemoveListener(eventTypeIdx, it->second);
|
||||
m_eventListenerIds.erase(it);
|
||||
}
|
||||
|
||||
void RemoveAllEventListeners() {
|
||||
auto* eventManager = EventManager::GetInstance();
|
||||
for (const auto& pair : m_eventListenerIds) {
|
||||
eventManager->RemoveListener(pair.first, pair.second);
|
||||
}
|
||||
m_eventListenerIds.clear();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unordered_map<std::type_index, EventListenerId> m_eventListenerIds;
|
||||
};
|
||||
}
|
||||
102
src/core/make-resolver.h
Normal file
102
src/core/make-resolver.h
Normal file
@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
namespace {
|
||||
template<typename Class, typename Member>
|
||||
struct MemStep {
|
||||
Member Class::* mptr;
|
||||
constexpr MemStep(Member Class::* p) : mptr(p) {}
|
||||
|
||||
template<typename Ptr>
|
||||
auto operator()(Ptr base) const
|
||||
-> std::conditional_t<std::is_pointer<Member>::value, Member, std::add_pointer_t<Member>>
|
||||
{
|
||||
using Ret = std::conditional_t<std::is_pointer<Member>::value, Member, std::add_pointer_t<Member>>;
|
||||
if (!base) return static_cast<Ret>(nullptr);
|
||||
if constexpr (std::is_pointer<Member>::value) {
|
||||
return base->*mptr;
|
||||
}
|
||||
else {
|
||||
return std::addressof(base->*mptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Class, typename Element, std::size_t N>
|
||||
struct MemStep<Class, Element[N]> {
|
||||
Element(Class::* mptr)[N];
|
||||
constexpr MemStep(Element(Class::* p)[N]) : mptr(p) {}
|
||||
template<typename Ptr>
|
||||
Element* operator()(Ptr base) const {
|
||||
if (!base) return nullptr;
|
||||
return &((base->*mptr)[0]);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Class, typename Member>
|
||||
constexpr auto mem(Member Class::* p) { return MemStep<Class, Member>(p); }
|
||||
|
||||
template<typename Class, typename Member>
|
||||
struct MemAddr {
|
||||
Member Class::* mptr;
|
||||
constexpr MemAddr(Member Class::* p) : mptr(p) {}
|
||||
|
||||
template<typename Ptr>
|
||||
auto operator()(Ptr base) const -> std::add_pointer_t<Member> {
|
||||
using Ret = std::add_pointer_t<Member>;
|
||||
if (!base) return static_cast<Ret>(nullptr);
|
||||
return std::addressof(base->*mptr); // always return address of the member
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Class, typename Member>
|
||||
constexpr auto mem_addr(Member Class::* p) { return MemAddr<Class, Member>(p); }
|
||||
|
||||
struct IdxStep {
|
||||
std::size_t idx;
|
||||
constexpr IdxStep(std::size_t i) : idx(i) {}
|
||||
|
||||
template<typename ArrPtr>
|
||||
auto operator()(ArrPtr arr) const {
|
||||
using Elem = std::remove_pointer_t<std::decay_t<ArrPtr>>;
|
||||
using Ret = std::conditional_t<std::is_pointer<Elem>::value, Elem, std::add_pointer_t<Elem>>;
|
||||
if (!arr) return static_cast<Ret>(nullptr);
|
||||
if constexpr (std::is_pointer<Elem>::value) {
|
||||
return static_cast<Ret>(arr[idx]);
|
||||
}
|
||||
else {
|
||||
return static_cast<Ret>(std::addressof(arr[idx]));
|
||||
}
|
||||
}
|
||||
};
|
||||
inline constexpr IdxStep idx(std::size_t i) { return IdxStep(i); }
|
||||
|
||||
template<std::size_t I, typename Cur, typename Tuple>
|
||||
auto ChainStep(Cur cur, const Tuple& t)
|
||||
{
|
||||
if constexpr (I >= std::tuple_size_v<Tuple>) {
|
||||
return cur;
|
||||
}
|
||||
else {
|
||||
auto next = std::get<I>(t)(cur);
|
||||
return ChainStep<I + 1>(next, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
template<typename BaseGetter, typename... Steps>
|
||||
auto MakeResolver(BaseGetter baseGetter, Steps... steps)
|
||||
-> std::function< decltype(ChainStep<0>(std::declval<std::invoke_result_t<BaseGetter>>(), std::declval<std::tuple<Steps...>>()))() >
|
||||
{
|
||||
auto stepsTuple = std::make_tuple(steps...);
|
||||
using BasePtr = std::invoke_result_t<BaseGetter>;
|
||||
using FinalPtr = decltype(ChainStep<0>(std::declval<BasePtr>(), stepsTuple));
|
||||
|
||||
return std::function<FinalPtr()>(
|
||||
[baseGetter = std::move(baseGetter), stepsTuple]() -> FinalPtr {
|
||||
BasePtr base = baseGetter();
|
||||
return ChainStep<0>(base, stepsTuple);
|
||||
});
|
||||
}
|
||||
}
|
||||
18
src/core/module-base.h
Normal file
18
src/core/module-base.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "event-listener-support.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class ModuleBase : public EventListenerSupport
|
||||
{
|
||||
public:
|
||||
virtual ~ModuleBase() override = default;
|
||||
|
||||
virtual bool Attach() = 0;
|
||||
virtual void Detach() = 0;
|
||||
|
||||
protected:
|
||||
ModuleBase() = default;
|
||||
};
|
||||
}
|
||||
22
src/core/module-manager.cpp
Normal file
22
src/core/module-manager.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
#include "common.h"
|
||||
#include "module-manager.h"
|
||||
|
||||
Core::ModuleManager* Core::ModuleManager::m_instance = nullptr;
|
||||
|
||||
Core::ModuleManager::ModuleManager()
|
||||
{
|
||||
assert(m_instance == nullptr, "ModuleManager instance already exists!");
|
||||
m_instance = this;
|
||||
}
|
||||
|
||||
Core::ModuleManager::~ModuleManager()
|
||||
{
|
||||
RemoveAllModules();
|
||||
m_instance = nullptr;
|
||||
}
|
||||
|
||||
Core::ModuleManager* Core::ModuleManager::GetInstance()
|
||||
{
|
||||
assert(m_instance != nullptr, "Core not initialized!");
|
||||
return m_instance;
|
||||
}
|
||||
80
src/core/module-manager.h
Normal file
80
src/core/module-manager.h
Normal file
@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "module-base.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class ModuleManager
|
||||
{
|
||||
public:
|
||||
static ModuleManager* GetInstance();
|
||||
|
||||
template<typename ModuleT, typename... Args>
|
||||
ModuleT* AddModule(Args&&... args) {
|
||||
static_assert(std::is_base_of<ModuleBase, ModuleT>::value, "ModuleT must derive from Core::ModuleBase");
|
||||
spdlog::debug("Adding module: {}", typeid(ModuleT).name());
|
||||
|
||||
std::type_index typeIdx(typeid(ModuleT));
|
||||
if (m_modules.find(typeIdx) != m_modules.end()) {
|
||||
spdlog::warn("Module already added: {}", typeid(ModuleT).name());
|
||||
return static_cast<ModuleT*>(m_modules[typeIdx].get());
|
||||
}
|
||||
|
||||
auto module = std::make_unique<ModuleT>(std::forward<Args>(args)...);
|
||||
if (!module->Attach()) {
|
||||
spdlog::error("Failed to attach module: {}", typeid(ModuleT).name());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ModuleT* modulePtr = module.get();
|
||||
m_modules[typeIdx] = std::move(module);
|
||||
return modulePtr;
|
||||
}
|
||||
|
||||
template<typename ModuleT>
|
||||
void RemoveModule() {
|
||||
static_assert(std::is_base_of<ModuleBase, ModuleT>::value, "ModuleT must derive from Core::ModuleBase");
|
||||
spdlog::debug("Removing module: {}", typeid(ModuleT).name());
|
||||
|
||||
std::type_index typeIdx(typeid(ModuleT));
|
||||
auto it = m_modules.find(typeIdx);
|
||||
if(it == m_modules.end()) {
|
||||
spdlog::warn("Module not found: {}", typeid(ModuleT).name());
|
||||
return;
|
||||
}
|
||||
|
||||
it->second->Detach();
|
||||
m_modules.erase(it);
|
||||
}
|
||||
|
||||
template<typename ModuleT>
|
||||
ModuleT* GetModule() {
|
||||
static_assert(std::is_base_of<ModuleBase, ModuleT>::value, "ModuleT must derive from Core::ModuleBase");
|
||||
std::type_index typeIdx(typeid(ModuleT));
|
||||
auto it = m_modules.find(typeIdx);
|
||||
if(it == m_modules.end()) {
|
||||
spdlog::warn("Module not found: {}", typeid(ModuleT).name());
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<ModuleT*>(it->second.get());
|
||||
}
|
||||
|
||||
void RemoveAllModules() {
|
||||
while (!m_modules.empty()) {
|
||||
auto it = std::prev(m_modules.end());
|
||||
it->second->Detach();
|
||||
m_modules.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
ModuleManager();
|
||||
~ModuleManager();
|
||||
ModuleManager(const ModuleManager&) = delete;
|
||||
ModuleManager& operator=(const ModuleManager&) = delete;
|
||||
|
||||
std::unordered_map<std::type_index, std::unique_ptr<ModuleBase>> m_modules;
|
||||
static ModuleManager* m_instance;
|
||||
};
|
||||
}
|
||||
26
src/core/types.h
Normal file
26
src/core/types.h
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
/* EventManager */
|
||||
template<typename EventT>
|
||||
using EventListener = std::function<void(const EventT&)>;
|
||||
|
||||
template<typename EventT, typename U>
|
||||
using EventMethodListener = void (U::*)(const EventT&);
|
||||
|
||||
using EventListenerId = size_t;
|
||||
|
||||
/* WatchManager */
|
||||
template<typename T>
|
||||
using Resolver = std::function<T* ()>;
|
||||
|
||||
template<typename T>
|
||||
using WatchedListener = std::function<void(std::optional<T>, std::optional<T>)>;
|
||||
|
||||
template<typename T, typename U>
|
||||
using WatchedMethodListener = void (U::*)(std::optional<T>, std::optional<T>);
|
||||
|
||||
using WatchedId = size_t;
|
||||
}
|
||||
21
src/core/watch-manager.cpp
Normal file
21
src/core/watch-manager.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include "common.h"
|
||||
#include "watch-manager.h"
|
||||
|
||||
Core::WatchManager* Core::WatchManager::m_instance = nullptr;
|
||||
|
||||
Core::WatchManager::WatchManager()
|
||||
{
|
||||
assert(m_instance == nullptr, "WatchManager instance already exists!");
|
||||
m_instance = this;
|
||||
}
|
||||
|
||||
Core::WatchManager::~WatchManager()
|
||||
{
|
||||
m_instance = nullptr;
|
||||
}
|
||||
|
||||
Core::WatchManager* Core::WatchManager::GetInstance()
|
||||
{
|
||||
assert(m_instance != nullptr, "WatchManager not initialized!");
|
||||
return m_instance;
|
||||
}
|
||||
135
src/core/watch-manager.h
Normal file
135
src/core/watch-manager.h
Normal file
@ -0,0 +1,135 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "types.h"
|
||||
#include "watched-base.h"
|
||||
#include "event-base.h"
|
||||
#include "event-manager.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class WatchManager {
|
||||
public:
|
||||
static WatchManager* GetInstance();
|
||||
|
||||
template<typename EventT, typename T>
|
||||
Watched<T>* Watch(Resolver<T> resolver, WatchedListener<T> listener)
|
||||
{
|
||||
static_assert(std::is_copy_constructible<T>::value, "ComponentT must be copy-constructible");
|
||||
static_assert(std::is_base_of_v<EventBase, EventT> || std::is_same<EventT, void>::value, "EventT must derive from Core::Event or be void");
|
||||
spdlog::debug("Adding watched: {} (Event: {})", typeid(T).name(), typeid(EventT).name());
|
||||
|
||||
auto entry = new Watched<T>(m_nextEntryId++, std::move(resolver), std::move(listener));
|
||||
WatchedId id = entry->m_id;
|
||||
|
||||
EnsureEventType<EventT>();
|
||||
|
||||
auto eventTypeIdx = std::type_index(typeid(EventT));
|
||||
m_entries[eventTypeIdx].push_back(std::unique_ptr<WatchedBase>(entry));
|
||||
m_idToEventType.push_back({ id, eventTypeIdx });
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
template<typename EventT, typename T, typename U>
|
||||
Watched<T>* Watch(Resolver<T> resolver, U* instance, WatchedMethodListener<T, U> method) {
|
||||
static_assert(std::is_copy_constructible<T>::value, "ComponentT must be copy-constructible");
|
||||
static_assert(std::is_base_of_v<EventBase, EventT> || std::is_same<EventT, void>::value, "EventT must derive from Core::Event or be void");
|
||||
|
||||
return Watch<EventT, T>(std::move(resolver), [instance, method](std::optional<T> oldValue, std::optional<T> newValue) {
|
||||
(instance->*method)(oldValue, newValue);
|
||||
});
|
||||
}
|
||||
|
||||
void Unwatch(WatchedId id) {
|
||||
spdlog::debug("Removing watched with id: {}", id);
|
||||
|
||||
auto it = std::find_if(m_idToEventType.begin(), m_idToEventType.end(),
|
||||
[id](const auto& pair) { return pair.first == id; });
|
||||
|
||||
if (it == m_idToEventType.end()) {
|
||||
spdlog::warn("No watched found with id: {}", id);
|
||||
/// TODO
|
||||
}
|
||||
|
||||
auto eventTypeIdx = it->second;
|
||||
m_idToEventType.erase(it);
|
||||
|
||||
auto& vec = m_entries[eventTypeIdx];
|
||||
vec.erase(std::remove_if(vec.begin(), vec.end(),
|
||||
[id](const auto& entry) { return entry->m_id == id; }), vec.end());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Unwatch(Watched<T>* watched) {
|
||||
Unwatch(watched->m_id);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
WatchManager();
|
||||
~WatchManager();
|
||||
WatchManager(const WatchManager&) = delete;
|
||||
WatchManager& operator=(const WatchManager&) = delete;
|
||||
|
||||
template<typename EventT>
|
||||
void EnsureEventType() {
|
||||
static_assert(std::is_base_of_v<EventBase, EventT>, "EventT must derive from Core::Event or be void");
|
||||
|
||||
auto eventTypeIdx = std::type_index(typeid(EventT));
|
||||
if (m_typeToListenerId.find(eventTypeIdx) != m_typeToListenerId.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_entries[eventTypeIdx] = std::vector<std::unique_ptr<WatchedBase>>();
|
||||
|
||||
spdlog::debug("First watched for event type: {}, adding listener", eventTypeIdx.name());
|
||||
EventListenerId listenerId = EventManager::GetInstance()->AddListener<EventT>([this](const EventT& event) {
|
||||
this->Update(std::type_index(typeid(EventT)));
|
||||
});
|
||||
|
||||
m_typeToListenerId[eventTypeIdx] = listenerId;
|
||||
}
|
||||
|
||||
template<>
|
||||
void EnsureEventType<void>() {
|
||||
auto eventTypeIdx = std::type_index(typeid(void));
|
||||
if (m_typeToListenerId.find(eventTypeIdx) != m_typeToListenerId.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_entries[eventTypeIdx] = std::vector<std::unique_ptr<WatchedBase>>();
|
||||
|
||||
spdlog::debug("First watched of type void, no event listener needed");
|
||||
}
|
||||
|
||||
void RemoveEventType(std::type_index eventType) {
|
||||
// Note: m_entries entry is not being removed
|
||||
spdlog::debug("No more watched for event type: {}, removing listener", eventType.name());
|
||||
|
||||
auto it = m_typeToListenerId.find(eventType);
|
||||
if (it == m_typeToListenerId.end()) return;
|
||||
EventManager::GetInstance()->RemoveListener(eventType, it->second);
|
||||
m_typeToListenerId.erase(it);
|
||||
}
|
||||
|
||||
void Update(std::type_index eventType) {
|
||||
auto it = m_entries.find(eventType);
|
||||
if (it == m_entries.end()) return;
|
||||
|
||||
if (it->second.empty()) {
|
||||
RemoveEventType(eventType);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& entry : it->second) {
|
||||
entry->Update();
|
||||
}
|
||||
}
|
||||
|
||||
static WatchManager* m_instance;
|
||||
std::unordered_map<std::type_index, std::vector<std::unique_ptr<WatchedBase>>> m_entries;
|
||||
std::vector<std::pair<WatchedId, std::type_index>> m_idToEventType;
|
||||
std::unordered_map<std::type_index, EventListenerId> m_typeToListenerId;
|
||||
WatchedId m_nextEntryId = 1;
|
||||
};
|
||||
}
|
||||
73
src/core/watched-base.h
Normal file
73
src/core/watched-base.h
Normal file
@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class WatchedBase
|
||||
{
|
||||
public:
|
||||
virtual ~WatchedBase() = default;
|
||||
virtual void Update() = 0;
|
||||
protected:
|
||||
friend class WatchManager;
|
||||
WatchedBase() = default;
|
||||
WatchedId m_id = 0;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class Watched : public WatchedBase {
|
||||
public:
|
||||
T* GetPointer() {
|
||||
return m_resolver();
|
||||
}
|
||||
|
||||
std::optional<T> GetValue() {
|
||||
T* p = m_resolver();
|
||||
if (p) return *p;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool SetValue(T value) {
|
||||
T* p = m_resolver();
|
||||
if (p) {
|
||||
*p = value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Update() override {
|
||||
T* p = m_resolver();
|
||||
bool present = (p != nullptr);
|
||||
std::optional<T> value = std::nullopt;
|
||||
if (present) value = *p;
|
||||
|
||||
if (m_first || value != m_lastValue) {
|
||||
m_listener(m_lastValue, value);
|
||||
m_first = false;
|
||||
m_lastValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
WatchedId GetId() const {
|
||||
return m_id;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class WatchManager;
|
||||
|
||||
Watched(WatchedId id_, std::function<T* ()> r, std::function<void(std::optional<T>, std::optional<T>)> l)
|
||||
: m_resolver(std::move(r)), m_listener(std::move(l))
|
||||
{
|
||||
m_id = id_;
|
||||
}
|
||||
|
||||
Watched(const Watched&) = delete;
|
||||
Watched& operator=(const Watched&) = delete;
|
||||
|
||||
std::function<T* ()> m_resolver;
|
||||
std::function<void(std::optional<T>, std::optional<T>)> m_listener;
|
||||
std::optional<T> m_lastValue;
|
||||
bool m_first = true;
|
||||
};
|
||||
}
|
||||
48
src/dllmain.cpp
Normal file
48
src/dllmain.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
// dllmain.cpp : Defines the entry point for the DLL application.
|
||||
#include "common.h"
|
||||
#include "core/core.h"
|
||||
#include "modules/ui/ui.h"
|
||||
#include "modules/modmenu/modmenu.h"
|
||||
#include "console.cpp"
|
||||
#include "events/game-tick.h"
|
||||
|
||||
Core::Core* coreInstance = nullptr;
|
||||
|
||||
static void Init()
|
||||
{
|
||||
InitConsole();
|
||||
coreInstance = new Core::Core();
|
||||
Core::ModuleManager* moduleManager = Core::ModuleManager::GetInstance();
|
||||
UiModule::RootModule* rootModule = moduleManager->AddModule<UiModule::RootModule>();
|
||||
ModMenuModule::RootModule* modMenu = moduleManager->AddModule<ModMenuModule::RootModule>();
|
||||
modMenu->AddMenu<ModMenuModule::MainMenu>();
|
||||
}
|
||||
|
||||
static void Deinit()
|
||||
{
|
||||
Core::ModuleManager* moduleManager = Core::ModuleManager::GetInstance();
|
||||
moduleManager->RemoveModule<UiModule::RootModule>();
|
||||
delete coreInstance;
|
||||
coreInstance = nullptr;
|
||||
}
|
||||
|
||||
BOOL APIENTRY DllMain( HMODULE hModule,
|
||||
DWORD ul_reason_for_call,
|
||||
LPVOID lpReserved
|
||||
)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
Init();
|
||||
break;
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
Deinit();
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
43
src/events/draw.cpp
Normal file
43
src/events/draw.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#include "../game/game.h"
|
||||
#include "../core/core.h"
|
||||
#include "../hook-types/function-call-hook.h"
|
||||
#include "draw.h"
|
||||
|
||||
static void DispatchPreDrawUIEvent()
|
||||
{
|
||||
PreDrawUIEvent event;
|
||||
Core::EventManager::GetInstance()->Dispatch(event);
|
||||
}
|
||||
|
||||
static void DispatchDrawUIEvent()
|
||||
{
|
||||
DrawUIEvent event;
|
||||
Core::EventManager::GetInstance()->Dispatch(event);
|
||||
}
|
||||
|
||||
static __declspec(naked) void DrawUIHookFunction(void)
|
||||
{
|
||||
__asm {
|
||||
push ecx
|
||||
call DispatchPreDrawUIEvent
|
||||
pop ecx
|
||||
call Game::Functions::DrawUI
|
||||
call DispatchDrawUIEvent
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
const FunctionCallHook drawHook = {
|
||||
0x0045a61f,
|
||||
(DWORD)&DrawUIHookFunction
|
||||
};
|
||||
|
||||
bool PreDrawUIEvent::Init()
|
||||
{
|
||||
return Core::HookManager::GetInstance()->AddHook(drawHook);
|
||||
}
|
||||
|
||||
bool DrawUIEvent::Init()
|
||||
{
|
||||
return Core::HookManager::GetInstance()->AddHook(drawHook);
|
||||
}
|
||||
24
src/events/draw.h
Normal file
24
src/events/draw.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include "../core/core.h"
|
||||
|
||||
/*
|
||||
Dispatched right before the UI is drawn.
|
||||
Things drawn here will appear on top of the game world and exploding scores but behind all other UI elements.
|
||||
*/
|
||||
class PreDrawUIEvent : public Core::EventBase {
|
||||
public:
|
||||
static bool Init();
|
||||
PreDrawUIEvent() {};
|
||||
virtual ~PreDrawUIEvent() override {};
|
||||
};
|
||||
|
||||
/*
|
||||
Dispatched right after the UI is drawn.
|
||||
Things drawn here will appear on top of all other UI elements (excluding mike's test).
|
||||
*/
|
||||
class DrawUIEvent : public Core::EventBase {
|
||||
public:
|
||||
static bool Init();
|
||||
DrawUIEvent() {};
|
||||
virtual ~DrawUIEvent() override {};
|
||||
};
|
||||
43
src/events/game-tick.cpp
Normal file
43
src/events/game-tick.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#include "../game/game.h"
|
||||
#include "../core/core.h"
|
||||
#include "../hook-types/function-multi-call-hook.h"
|
||||
#include "game-tick.h"
|
||||
|
||||
static void DispatchPreGameTickEvent()
|
||||
{
|
||||
PreGameTickEvent event;
|
||||
Core::EventManager::GetInstance()->Dispatch(event);
|
||||
}
|
||||
|
||||
static void DispatchGameTickEvent()
|
||||
{
|
||||
GameTickEvent event;
|
||||
Core::EventManager::GetInstance()->Dispatch(event);
|
||||
}
|
||||
|
||||
static __declspec(naked) void GameTickHookFunction(void)
|
||||
{
|
||||
__asm {
|
||||
push ecx
|
||||
call DispatchPreGameTickEvent
|
||||
pop ecx
|
||||
call Game::Functions::GameTick
|
||||
call DispatchGameTickEvent
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
const FunctionMultiCallHook gameTickHook = {
|
||||
std::vector<DWORD>{ 0x0045c3f2, 0x0045c3e9 },
|
||||
(DWORD)&GameTickHookFunction
|
||||
};
|
||||
|
||||
bool PreGameTickEvent::Init()
|
||||
{
|
||||
return Core::HookManager::GetInstance()->AddHook(gameTickHook);
|
||||
}
|
||||
|
||||
bool GameTickEvent::Init()
|
||||
{
|
||||
return Core::HookManager::GetInstance()->AddHook(gameTickHook);
|
||||
}
|
||||
22
src/events/game-tick.h
Normal file
22
src/events/game-tick.h
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "../core/core.h"
|
||||
|
||||
/*
|
||||
Dispatched every game tick, before any game logic is processed. Runs only when the game is unpaused.
|
||||
*/
|
||||
class PreGameTickEvent : public Core::EventBase {
|
||||
public:
|
||||
static bool Init();
|
||||
PreGameTickEvent() {};
|
||||
virtual ~PreGameTickEvent() override {};
|
||||
};
|
||||
|
||||
/*
|
||||
Dispatched every game tick, after all game logic is processed. Runs only when the game is unpaused.
|
||||
*/
|
||||
class GameTickEvent : public Core::EventBase {
|
||||
public:
|
||||
static bool Init();
|
||||
GameTickEvent() {};
|
||||
virtual ~GameTickEvent() override {};
|
||||
};
|
||||
63
src/events/keyboard.cpp
Normal file
63
src/events/keyboard.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
#include "../game/game.h"
|
||||
#include "../core/core.h"
|
||||
#include "../hook-types/function-call-hook.h"
|
||||
#include "keyboard.h"
|
||||
|
||||
static int isShiftPressed = false;
|
||||
static int isCtrlPressed = false;
|
||||
static int isAltPressed = false;
|
||||
|
||||
static void UpdateModifierKeys(bool down, Game::KeyCode keyCode)
|
||||
{
|
||||
if(keyCode == Game::KeyCode::DIK_LSHIFT) {
|
||||
isShiftPressed = down;
|
||||
}
|
||||
else if (keyCode == Game::KeyCode::DIK_LCONTROL) {
|
||||
isCtrlPressed = down;
|
||||
}
|
||||
else if (keyCode == Game::KeyCode::DIK_LMENU) {
|
||||
isAltPressed = down;
|
||||
}
|
||||
}
|
||||
|
||||
static void DispatchKeyboardEvent()
|
||||
{
|
||||
Game::KeyCode keyCode = *Game::Memory::GetKeyPressCode();
|
||||
int keyState = *Game::Memory::GetKeyPressState();
|
||||
|
||||
if (keyState == 0) {
|
||||
// Key up event
|
||||
UpdateModifierKeys(false, keyCode);
|
||||
KeyUpEvent event(keyCode, isShiftPressed, isCtrlPressed, isAltPressed);
|
||||
Core::EventManager::GetInstance()->Dispatch(event);
|
||||
}
|
||||
else {
|
||||
// Key down event
|
||||
UpdateModifierKeys(true, keyCode);
|
||||
KeyDownEvent event(keyCode, isShiftPressed, isCtrlPressed, isAltPressed);
|
||||
Core::EventManager::GetInstance()->Dispatch(event);
|
||||
}
|
||||
}
|
||||
|
||||
static __declspec(naked) void OnKeyUpDownHookFunction(void)
|
||||
{
|
||||
__asm {
|
||||
call DispatchKeyboardEvent
|
||||
jmp Game::Functions::OnKeyUpDown
|
||||
}
|
||||
}
|
||||
|
||||
const FunctionCallHook keyboardHook = {
|
||||
0x0044c48d,
|
||||
(DWORD)&OnKeyUpDownHookFunction
|
||||
};
|
||||
|
||||
bool KeyDownEvent::Init()
|
||||
{
|
||||
return Core::HookManager::GetInstance()->AddHook(keyboardHook);
|
||||
}
|
||||
|
||||
bool KeyUpEvent::Init()
|
||||
{
|
||||
return Core::HookManager::GetInstance()->AddHook(keyboardHook);
|
||||
}
|
||||
49
src/events/keyboard.h
Normal file
49
src/events/keyboard.h
Normal file
@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include "../core/core.h"
|
||||
#include "../game/game.h"
|
||||
|
||||
/*
|
||||
Dispatched when a key is pressed down.
|
||||
*/
|
||||
class KeyDownEvent : public Core::EventBase {
|
||||
public:
|
||||
static bool Init();
|
||||
KeyDownEvent(Game::KeyCode keyCode, bool shift = false, bool ctrl = false, bool alt = false)
|
||||
: m_keyCode(keyCode), m_shift(shift), m_ctrl(ctrl), m_alt(alt) {
|
||||
};
|
||||
virtual ~KeyDownEvent() override {};
|
||||
|
||||
Game::KeyCode GetKeyCode() const { return m_keyCode; }
|
||||
bool IsShiftPressed() const { return m_shift; }
|
||||
bool IsCtrlPressed() const { return m_ctrl; }
|
||||
bool IsAltPressed() const { return m_alt; }
|
||||
|
||||
private:
|
||||
Game::KeyCode m_keyCode;
|
||||
bool m_shift = false;
|
||||
bool m_ctrl = false;
|
||||
bool m_alt = false;
|
||||
};
|
||||
|
||||
/*
|
||||
Dispatched when a key is released.
|
||||
*/
|
||||
class KeyUpEvent : public Core::EventBase {
|
||||
public:
|
||||
static bool Init();
|
||||
KeyUpEvent(Game::KeyCode keyCode, bool shift = false, bool ctrl = false, bool alt = false)
|
||||
: m_keyCode(keyCode), m_shift(shift), m_ctrl(ctrl), m_alt(alt) {
|
||||
};
|
||||
virtual ~KeyUpEvent() override {};
|
||||
|
||||
Game::KeyCode GetKeyCode() const { return m_keyCode; }
|
||||
bool IsShiftPressed() const { return m_shift; }
|
||||
bool IsCtrlPressed() const { return m_ctrl; }
|
||||
bool IsAltPressed() const { return m_alt; }
|
||||
|
||||
private:
|
||||
Game::KeyCode m_keyCode;
|
||||
bool m_shift = false;
|
||||
bool m_ctrl = false;
|
||||
bool m_alt = false;
|
||||
};
|
||||
3
src/game/common.h
Normal file
3
src/game/common.h
Normal file
@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#include <atlstr.h>
|
||||
24
src/game/functions.h
Normal file
24
src/game/functions.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include "gta2_wrapper.h"
|
||||
#include "keycode.h"
|
||||
|
||||
namespace Game {
|
||||
class Functions
|
||||
{
|
||||
public:
|
||||
typedef void(__fastcall EachFrameDrawType)(Game* game, DWORD edx);
|
||||
static constexpr EachFrameDrawType* EachFrameDraw = (EachFrameDrawType*)0x0045a5a0;
|
||||
|
||||
typedef void(__fastcall GameTickType)(Game* game, DWORD edx);
|
||||
static constexpr GameTickType* GameTick = (GameTickType*)0x0045c1f0;
|
||||
|
||||
typedef void(__stdcall OnKeyUpDownType)(DWORD keyboard, KeyCode* keyCode);
|
||||
static constexpr OnKeyUpDownType* OnKeyUpDown = (OnKeyUpDownType*)0x0044bfb0;
|
||||
|
||||
typedef void(__fastcall DrawUIType)(S10* s10, DWORD edx);
|
||||
static constexpr DrawUIType* DrawUI = (DrawUIType*)0x004ca440;
|
||||
|
||||
typedef void(__stdcall DrawGTATextType)(WCHAR* str, SCR_f x, SCR_f y, int param_4, SCR_f scale, S4_ENUM1* param_6, int param_7, SPRITE_INVISIBILITY spriteInvisibility, SCR_f param_9);
|
||||
static constexpr DrawGTATextType* DrawGTAText = (DrawGTATextType*)0x004cc100;
|
||||
};
|
||||
}
|
||||
5
src/game/game.h
Normal file
5
src/game/game.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "gta2_wrapper.h"
|
||||
#include "memory.h"
|
||||
#include "functions.h"
|
||||
9229
src/game/gta2.h
Normal file
9229
src/game/gta2.h
Normal file
File diff suppressed because it is too large
Load Diff
6
src/game/gta2_wrapper.h
Normal file
6
src/game/gta2_wrapper.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
namespace Game {
|
||||
#include "gta2.h"
|
||||
}
|
||||
94
src/game/keycode.cpp
Normal file
94
src/game/keycode.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
#include "keycode.h"
|
||||
|
||||
char Game::GetCharFromKeyCode(KeyCode keyCode, bool shift, bool capsLock)
|
||||
{
|
||||
auto letter = [&](char upper)->char {
|
||||
return (capsLock ^ shift) ? upper : static_cast<char>(upper + ('a' - 'A'));
|
||||
};
|
||||
|
||||
switch (keyCode) {
|
||||
case KeyCode::DIK_1: return shift ? '!' : '1';
|
||||
case KeyCode::DIK_2: return shift ? '@' : '2';
|
||||
case KeyCode::DIK_3: return shift ? '#' : '3';
|
||||
case KeyCode::DIK_4: return shift ? '$' : '4';
|
||||
case KeyCode::DIK_5: return shift ? '%' : '5';
|
||||
case KeyCode::DIK_6: return shift ? '^' : '6';
|
||||
case KeyCode::DIK_7: return shift ? '&' : '7';
|
||||
case KeyCode::DIK_8: return shift ? '*' : '8';
|
||||
case KeyCode::DIK_9: return shift ? '(' : '9';
|
||||
case KeyCode::DIK_0: return shift ? ')' : '0';
|
||||
|
||||
case KeyCode::DIK_MINUS: return shift ? '_' : '-';
|
||||
case KeyCode::DIK_EQUALS: return shift ? '+' : '=';
|
||||
case KeyCode::DIK_LBRACKET: return shift ? '{' : '[';
|
||||
case KeyCode::DIK_RBRACKET: return shift ? '}' : ']';
|
||||
case KeyCode::DIK_BACKSLASH: return shift ? '|' : '\\';
|
||||
case KeyCode::DIK_SEMICOLON: return shift ? ':' : ';';
|
||||
case KeyCode::DIK_APOSTROPHE:return shift ? '"' : '\'';
|
||||
case KeyCode::DIK_GRAVE: return shift ? '~' : '`';
|
||||
case KeyCode::DIK_COMMA: return shift ? '<' : ',';
|
||||
case KeyCode::DIK_PERIOD: return shift ? '>' : '.';
|
||||
case KeyCode::DIK_SLASH: return shift ? '?' : '/';
|
||||
|
||||
case KeyCode::DIK_SPACE: return ' ';
|
||||
case KeyCode::DIK_TAB: return '\t';
|
||||
case KeyCode::DIK_RETURN:return '\n';
|
||||
|
||||
case KeyCode::DIK_NUMPAD0: return '0';
|
||||
case KeyCode::DIK_NUMPAD1: return '1';
|
||||
case KeyCode::DIK_NUMPAD2: return '2';
|
||||
case KeyCode::DIK_NUMPAD3: return '3';
|
||||
case KeyCode::DIK_NUMPAD4: return '4';
|
||||
case KeyCode::DIK_NUMPAD5: return '5';
|
||||
case KeyCode::DIK_NUMPAD6: return '6';
|
||||
case KeyCode::DIK_NUMPAD7: return '7';
|
||||
case KeyCode::DIK_NUMPAD8: return '8';
|
||||
case KeyCode::DIK_NUMPAD9: return '9';
|
||||
case KeyCode::DIK_DECIMAL: return '.';
|
||||
case KeyCode::DIK_ADD: return '+';
|
||||
case KeyCode::DIK_SUBTRACT: return '-';
|
||||
case KeyCode::DIK_MULTIPLY: return '*';
|
||||
case KeyCode::DIK_DIVIDE: return '/';
|
||||
case KeyCode::DIK_NUMPADCOMMA: return ',';
|
||||
case KeyCode::DIK_NUMPADENTER: return '\n';
|
||||
case KeyCode::DIK_NUMPADEQUALS: return '=';
|
||||
|
||||
case KeyCode::DIK_A: return letter('A');
|
||||
case KeyCode::DIK_B: return letter('B');
|
||||
case KeyCode::DIK_C: return letter('C');
|
||||
case KeyCode::DIK_D: return letter('D');
|
||||
case KeyCode::DIK_E: return letter('E');
|
||||
case KeyCode::DIK_F: return letter('F');
|
||||
case KeyCode::DIK_G: return letter('G');
|
||||
case KeyCode::DIK_H: return letter('H');
|
||||
case KeyCode::DIK_I: return letter('I');
|
||||
case KeyCode::DIK_J: return letter('J');
|
||||
case KeyCode::DIK_K: return letter('K');
|
||||
case KeyCode::DIK_L: return letter('L');
|
||||
case KeyCode::DIK_M: return letter('M');
|
||||
case KeyCode::DIK_N: return letter('N');
|
||||
case KeyCode::DIK_O: return letter('O');
|
||||
case KeyCode::DIK_P: return letter('P');
|
||||
case KeyCode::DIK_Q: return letter('Q');
|
||||
case KeyCode::DIK_R: return letter('R');
|
||||
case KeyCode::DIK_S: return letter('S');
|
||||
case KeyCode::DIK_T: return letter('T');
|
||||
case KeyCode::DIK_U: return letter('U');
|
||||
case KeyCode::DIK_V: return letter('V');
|
||||
case KeyCode::DIK_W: return letter('W');
|
||||
case KeyCode::DIK_X: return letter('X');
|
||||
case KeyCode::DIK_Y: return letter('Y');
|
||||
case KeyCode::DIK_Z: return letter('Z');
|
||||
|
||||
case KeyCode::DIK_OEM_102: return shift ? '>' : '<'; // common on non-US 102-key
|
||||
case KeyCode::DIK_ABNT_C1: return shift ? '?' : '/'; // brazilian
|
||||
case KeyCode::DIK_ABNT_C2: return ','; // brazilian keypad dot
|
||||
case KeyCode::DIK_YEN: return '\\';
|
||||
case KeyCode::DIK_AT: return '@';
|
||||
case KeyCode::DIK_COLON: return ':';
|
||||
case KeyCode::DIK_UNDERLINE:return '_';
|
||||
|
||||
default:
|
||||
return '\0';
|
||||
}
|
||||
}
|
||||
155
src/game/keycode.h
Normal file
155
src/game/keycode.h
Normal file
@ -0,0 +1,155 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
// Source: https://gist.github.com/tracend/912308
|
||||
|
||||
namespace Game {
|
||||
enum class KeyCode : uint32_t {
|
||||
DIK_ESCAPE = 0x01,
|
||||
DIK_1 = 0x02,
|
||||
DIK_2 = 0x03,
|
||||
DIK_3 = 0x04,
|
||||
DIK_4 = 0x05,
|
||||
DIK_5 = 0x06,
|
||||
DIK_6 = 0x07,
|
||||
DIK_7 = 0x08,
|
||||
DIK_8 = 0x09,
|
||||
DIK_9 = 0x0A,
|
||||
DIK_0 = 0x0B,
|
||||
DIK_MINUS = 0x0C, /* - on main keyboard */
|
||||
DIK_EQUALS = 0x0D,
|
||||
DIK_BACK = 0x0E, /* backspace */
|
||||
DIK_TAB = 0x0F,
|
||||
DIK_Q = 0x10,
|
||||
DIK_W = 0x11,
|
||||
DIK_E = 0x12,
|
||||
DIK_R = 0x13,
|
||||
DIK_T = 0x14,
|
||||
DIK_Y = 0x15,
|
||||
DIK_U = 0x16,
|
||||
DIK_I = 0x17,
|
||||
DIK_O = 0x18,
|
||||
DIK_P = 0x19,
|
||||
DIK_LBRACKET = 0x1A,
|
||||
DIK_RBRACKET = 0x1B,
|
||||
DIK_RETURN = 0x1C, /* Enter on main keyboard */
|
||||
DIK_LCONTROL = 0x1D,
|
||||
DIK_A = 0x1E,
|
||||
DIK_S = 0x1F,
|
||||
DIK_D = 0x20,
|
||||
DIK_F = 0x21,
|
||||
DIK_G = 0x22,
|
||||
DIK_H = 0x23,
|
||||
DIK_J = 0x24,
|
||||
DIK_K = 0x25,
|
||||
DIK_L = 0x26,
|
||||
DIK_SEMICOLON = 0x27,
|
||||
DIK_APOSTROPHE = 0x28,
|
||||
DIK_GRAVE = 0x29, /* accent grave */
|
||||
DIK_LSHIFT = 0x2A,
|
||||
DIK_BACKSLASH = 0x2B,
|
||||
DIK_Z = 0x2C,
|
||||
DIK_X = 0x2D,
|
||||
DIK_C = 0x2E,
|
||||
DIK_V = 0x2F,
|
||||
DIK_B = 0x30,
|
||||
DIK_N = 0x31,
|
||||
DIK_M = 0x32,
|
||||
DIK_COMMA = 0x33,
|
||||
DIK_PERIOD = 0x34, /* . on main keyboard */
|
||||
DIK_SLASH = 0x35, /* / on main keyboard */
|
||||
DIK_RSHIFT = 0x36,
|
||||
DIK_MULTIPLY = 0x37, /* * on numeric keypad */
|
||||
DIK_LMENU = 0x38, /* left Alt */
|
||||
DIK_SPACE = 0x39,
|
||||
DIK_CAPITAL = 0x3A,
|
||||
DIK_F1 = 0x3B,
|
||||
DIK_F2 = 0x3C,
|
||||
DIK_F3 = 0x3D,
|
||||
DIK_F4 = 0x3E,
|
||||
DIK_F5 = 0x3F,
|
||||
DIK_F6 = 0x40,
|
||||
DIK_F7 = 0x41,
|
||||
DIK_F8 = 0x42,
|
||||
DIK_F9 = 0x43,
|
||||
DIK_F10 = 0x44,
|
||||
DIK_NUMLOCK = 0x45,
|
||||
DIK_SCROLL = 0x46, /* Scroll Lock */
|
||||
DIK_NUMPAD7 = 0x47,
|
||||
DIK_NUMPAD8 = 0x48,
|
||||
DIK_NUMPAD9 = 0x49,
|
||||
DIK_SUBTRACT = 0x4A, /* - on numeric keypad */
|
||||
DIK_NUMPAD4 = 0x4B,
|
||||
DIK_NUMPAD5 = 0x4C,
|
||||
DIK_NUMPAD6 = 0x4D,
|
||||
DIK_ADD = 0x4E, /* + on numeric keypad */
|
||||
DIK_NUMPAD1 = 0x4F,
|
||||
DIK_NUMPAD2 = 0x50,
|
||||
DIK_NUMPAD3 = 0x51,
|
||||
DIK_NUMPAD0 = 0x52,
|
||||
DIK_DECIMAL = 0x53, /* . on numeric keypad */
|
||||
DIK_OEM_102 = 0x56, /* <> or \| on RT 102-key keyboard (Non-U.S.) */
|
||||
DIK_F11 = 0x57,
|
||||
DIK_F12 = 0x58,
|
||||
DIK_F13 = 0x64, /* (NEC PC98) */
|
||||
DIK_F14 = 0x65, /* (NEC PC98) */
|
||||
DIK_F15 = 0x66, /* (NEC PC98) */
|
||||
DIK_KANA = 0x70, /* (Japanese keyboard) */
|
||||
DIK_ABNT_C1 = 0x73, /* /? on Brazilian keyboard */
|
||||
DIK_CONVERT = 0x79, /* (Japanese keyboard) */
|
||||
DIK_NOCONVERT = 0x7B, /* (Japanese keyboard) */
|
||||
DIK_YEN = 0x7D, /* (Japanese keyboard) */
|
||||
DIK_ABNT_C2 = 0x7E, /* Numpad . on Brazilian keyboard */
|
||||
DIK_NUMPADEQUALS = 0x8D, /* = on numeric keypad (NEC PC98) */
|
||||
DIK_PREVTRACK = 0x90, /* Previous Track (DIK_CIRCUMFLEX on Japanese keyboard) */
|
||||
DIK_AT = 0x91, /* (NEC PC98) */
|
||||
DIK_COLON = 0x92, /* (NEC PC98) */
|
||||
DIK_UNDERLINE = 0x93, /* (NEC PC98) */
|
||||
DIK_KANJI = 0x94, /* (Japanese keyboard) */
|
||||
DIK_STOP = 0x95, /* (NEC PC98) */
|
||||
DIK_AX = 0x96, /* (Japan AX) */
|
||||
DIK_UNLABELED = 0x97, /* (J3100) */
|
||||
DIK_NEXTTRACK = 0x99, /* Next Track */
|
||||
DIK_NUMPADENTER = 0x9C, /* Enter on numeric keypad */
|
||||
DIK_RCONTROL = 0x9D,
|
||||
DIK_MUTE = 0xA0, /* Mute */
|
||||
DIK_CALCULATOR = 0xA1, /* Calculator */
|
||||
DIK_PLAYPAUSE = 0xA2, /* Play / Pause */
|
||||
DIK_MEDIASTOP = 0xA4, /* Media Stop */
|
||||
DIK_VOLUMEDOWN = 0xAE, /* Volume - */
|
||||
DIK_VOLUMEUP = 0xB0, /* Volume + */
|
||||
DIK_WEBHOME = 0xB2, /* Web home */
|
||||
DIK_NUMPADCOMMA = 0xB3, /* , on numeric keypad (NEC PC98) */
|
||||
DIK_DIVIDE = 0xB5, /* / on numeric keypad */
|
||||
DIK_SYSRQ = 0xB7,
|
||||
DIK_RMENU = 0xB8, /* right Alt */
|
||||
DIK_PAUSE = 0xC5, /* Pause */
|
||||
DIK_HOME = 0xC7, /* Home on arrow keypad */
|
||||
DIK_UP = 0xC8, /* UpArrow on arrow keypad */
|
||||
DIK_PRIOR = 0xC9, /* PgUp on arrow keypad */
|
||||
DIK_LEFT = 0xCB, /* LeftArrow on arrow keypad */
|
||||
DIK_RIGHT = 0xCD, /* RightArrow on arrow keypad */
|
||||
DIK_END = 0xCF, /* End on arrow keypad */
|
||||
DIK_DOWN = 0xD0, /* DownArrow on arrow keypad */
|
||||
DIK_NEXT = 0xD1, /* PgDn on arrow keypad */
|
||||
DIK_INSERT = 0xD2, /* Insert on arrow keypad */
|
||||
DIK_DELETE = 0xD3, /* Delete on arrow keypad */
|
||||
DIK_LWIN = 0xDB, /* Left Windows key */
|
||||
DIK_RWIN = 0xDC, /* Right Windows key */
|
||||
DIK_APPS = 0xDD, /* AppMenu key */
|
||||
DIK_POWER = 0xDE, /* System Power */
|
||||
DIK_SLEEP = 0xDF, /* System Sleep */
|
||||
DIK_WAKE = 0xE3, /* System Wake */
|
||||
DIK_WEBSEARCH = 0xE5, /* Web Search */
|
||||
DIK_WEBFAVORITES = 0xE6, /* Web Favorites */
|
||||
DIK_WEBREFRESH = 0xE7, /* Web Refresh */
|
||||
DIK_WEBSTOP = 0xE8, /* Web Stop */
|
||||
DIK_WEBFORWARD = 0xE9, /* Web Forward */
|
||||
DIK_WEBBACK = 0xEA, /* Web Back */
|
||||
DIK_MYCOMPUTER = 0xEB, /* My Computer */
|
||||
DIK_MAIL = 0xEC, /* Mail */
|
||||
DIK_MEDIASELECT = 0xED /* Media Select */
|
||||
};
|
||||
|
||||
char GetCharFromKeyCode(KeyCode key, bool shift, bool capsLock);
|
||||
}
|
||||
13
src/game/memory.h
Normal file
13
src/game/memory.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "gta2_wrapper.h"
|
||||
#include "keycode.h"
|
||||
|
||||
namespace Game {
|
||||
class Memory
|
||||
{
|
||||
public:
|
||||
static constexpr Game* GetGame() { return (Game*)*(DWORD*)0x005eb4fc; }
|
||||
static constexpr KeyCode* GetKeyPressCode() { return (KeyCode*)0x005e8f8c; }
|
||||
static constexpr uint* GetKeyPressState() { return (uint*)0x005e8f90; }
|
||||
};
|
||||
}
|
||||
33
src/hook-types/function-call-hook.h
Normal file
33
src/hook-types/function-call-hook.h
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include "../core/core.h"
|
||||
|
||||
/*
|
||||
Replaces a function call with a call to a custom function.
|
||||
@param originalCallAddress The address of the original function call instruction (the CALL opcode).
|
||||
@param hookFunction The address of the custom function to call instead.
|
||||
*/
|
||||
class FunctionCallHook : public Core::HookBase
|
||||
{
|
||||
public:
|
||||
FunctionCallHook(const DWORD originalCallAddress, const DWORD hookFunction)
|
||||
: originalCallAddress(originalCallAddress), hookFunction(hookFunction) {
|
||||
}
|
||||
~FunctionCallHook() = default;
|
||||
|
||||
static bool Hook(const FunctionCallHook& hookDef) {
|
||||
DWORD OldProtection = { 0 };
|
||||
bool success = VirtualProtectEx(GetCurrentProcess(), (LPVOID)hookDef.originalCallAddress, 5, PAGE_EXECUTE_READWRITE, &OldProtection);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*(BYTE*)((LPBYTE)hookDef.originalCallAddress) = 0xE8;
|
||||
DWORD offset = hookDef.hookFunction - hookDef.originalCallAddress - 5;
|
||||
*(DWORD*)((LPBYTE)hookDef.originalCallAddress + 1) = offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const DWORD originalCallAddress;
|
||||
const DWORD hookFunction;
|
||||
};
|
||||
37
src/hook-types/function-multi-call-hook.h
Normal file
37
src/hook-types/function-multi-call-hook.h
Normal file
@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include "../core/core.h"
|
||||
|
||||
/*
|
||||
Replaces multiple calls of the same function with calls to a custom function.
|
||||
@param originalCallAddresses The addresses of the original function call instructions (the CALL opcodes).
|
||||
@param hookFunction The address of the custom function to call instead.
|
||||
*/
|
||||
class FunctionMultiCallHook : public Core::HookBase
|
||||
{
|
||||
public:
|
||||
FunctionMultiCallHook(const std::vector<DWORD>& originalCallAddresses, DWORD hookFunction)
|
||||
: originalCallAddresses(originalCallAddresses), hookFunction(hookFunction) {
|
||||
}
|
||||
~FunctionMultiCallHook() = default;
|
||||
|
||||
static bool Hook(const FunctionMultiCallHook& hookDef) {
|
||||
DWORD OldProtection = { 0 };
|
||||
for (const auto& originalCallAddress : hookDef.originalCallAddresses) {
|
||||
bool success = VirtualProtectEx(GetCurrentProcess(), (LPVOID)originalCallAddress, 5, PAGE_EXECUTE_READWRITE, &OldProtection);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& originalCallAddress : hookDef.originalCallAddresses) {
|
||||
*(BYTE*)((LPBYTE)originalCallAddress) = 0xE8;
|
||||
DWORD offset = hookDef.hookFunction - originalCallAddress - 5;
|
||||
*(DWORD*)((LPBYTE)originalCallAddress + 1) = offset;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<DWORD> originalCallAddresses;
|
||||
const DWORD hookFunction;
|
||||
};
|
||||
5
src/modules/modmenu/common.h
Normal file
5
src/modules/modmenu/common.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../game/game.h"
|
||||
#include "../../core/core.h"
|
||||
#include "../ui/ui.h"
|
||||
70
src/modules/modmenu/menu-base.cpp
Normal file
70
src/modules/modmenu/menu-base.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
#include "menu-base.h"
|
||||
#include "root.h"
|
||||
|
||||
bool ModMenuModule::MenuBase::Attach()
|
||||
{
|
||||
UiModule::Component* vertCont = nullptr;
|
||||
CreateMenu(L"", vertCont);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModMenuModule::MenuBase::Detach()
|
||||
{
|
||||
DestroyMenu();
|
||||
}
|
||||
|
||||
void ModMenuModule::MenuBase::CreateMenu(std::wstring title, UiModule::Component*& vertCont)
|
||||
{
|
||||
assert(!m_mainComponent && !m_menuController, "Menu already created");
|
||||
|
||||
UiModule::RootModule* uiRoot = UiModule::RootModule::GetInstance();
|
||||
ModMenuOptions options = RootModule::GetInstance()->GetOptions();
|
||||
|
||||
UiModule::Margin* mainCont = uiRoot->AddComponent<UiModule::Margin>(nullptr, options.menuX, options.menuY);
|
||||
mainCont->SetVisible(false);
|
||||
m_mainComponent = static_cast<UiModule::Component*>(mainCont);
|
||||
|
||||
vertCont = uiRoot->AddComponent<UiModule::VertCont>(mainCont);
|
||||
UiModule::Margin* titleMargin = uiRoot->AddComponent<UiModule::Margin>(vertCont, options.menuHeaderMarginX, options.menuHeaderMarginY);
|
||||
UiModule::Text* titleText = uiRoot->AddComponent<UiModule::Text>(titleMargin, title, options.menuHeaderTextSize);
|
||||
|
||||
m_menuController = uiRoot->AddController<UiModule::MenuController>(options.menuControllerOptions);
|
||||
|
||||
m_menuController->SetOnActionCallback([this](UiModule::Selectable* item, UiModule::MenuItemId id) {
|
||||
this->OnMenuAction(item, id);
|
||||
});
|
||||
}
|
||||
|
||||
void ModMenuModule::MenuBase::DestroyMenu()
|
||||
{
|
||||
UiModule::RootModule* uiRoot = UiModule::RootModule::GetInstance();
|
||||
if (m_menuController) {
|
||||
uiRoot->RemoveController(m_menuController);
|
||||
m_menuController = nullptr;
|
||||
}
|
||||
if (m_mainComponent) {
|
||||
uiRoot->RemoveComponent(m_mainComponent, true);
|
||||
m_mainComponent = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ModMenuModule::MenuBase::SetVisible(bool visible)
|
||||
{
|
||||
m_visible = visible;
|
||||
|
||||
if (visible) {
|
||||
OnShow();
|
||||
}
|
||||
|
||||
if (m_menuController) {
|
||||
m_menuController->SetActive(visible);
|
||||
}
|
||||
|
||||
if (m_mainComponent) {
|
||||
m_mainComponent->SetVisible(visible);
|
||||
}
|
||||
|
||||
if (!visible) {
|
||||
OnHide();
|
||||
}
|
||||
}
|
||||
30
src/modules/modmenu/menu-base.h
Normal file
30
src/modules/modmenu/menu-base.h
Normal file
@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
namespace ModMenuModule {
|
||||
class MenuBase {
|
||||
public:
|
||||
virtual ~MenuBase() = default;
|
||||
protected:
|
||||
friend class RootModule;
|
||||
MenuBase() = default;
|
||||
|
||||
virtual bool Attach();
|
||||
virtual void Detach();
|
||||
|
||||
virtual void CreateMenu(std::wstring title, UiModule::Component*& vertCont) final;
|
||||
virtual void DestroyMenu() final;
|
||||
|
||||
virtual void OnShow() {}
|
||||
virtual void OnHide() {}
|
||||
|
||||
virtual void SetVisible(bool visible) final;
|
||||
virtual bool IsVisible() const final { return m_visible; }
|
||||
|
||||
virtual void OnMenuAction(UiModule::Selectable* item, UiModule::MenuItemId id) = 0;
|
||||
|
||||
UiModule::Component* m_mainComponent = nullptr;
|
||||
UiModule::MenuController* m_menuController = nullptr;
|
||||
bool m_visible = false;
|
||||
};
|
||||
}
|
||||
40
src/modules/modmenu/menus/main-menu.cpp
Normal file
40
src/modules/modmenu/menus/main-menu.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
#include "main-menu.h"
|
||||
#include "../root.h"
|
||||
#include "player-menu.h"
|
||||
|
||||
ModMenuModule::MainMenu::MainMenu()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ModMenuModule::MainMenu::~MainMenu()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool ModMenuModule::MainMenu::Attach()
|
||||
{
|
||||
UiModule::Component* vertCont;
|
||||
CreateMenu(L"#Mod Menu#", vertCont);
|
||||
ModMenuModule::ModMenuOptions options = ModMenuModule::RootModule::GetInstance()->GetOptions();
|
||||
|
||||
m_menuController->CreateItem<UiModule::Text>(vertCont, L"Player", options.textSize);
|
||||
m_menuController->CreateItem<UiModule::Text>(vertCont, L"Vehicles", options.textSize);
|
||||
m_menuController->CreateItem<UiModule::Text>(vertCont, L"Weapons", options.textSize);
|
||||
m_menuController->CreateItem<UiModule::Text>(vertCont, L"Camera", options.textSize);
|
||||
m_menuController->CreateItem<UiModule::Text>(vertCont, L"World", options.textSize);
|
||||
m_menuController->CreateItem<UiModule::Text>(vertCont, L"Misc", options.textSize);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModMenuModule::MainMenu::OnMenuAction(UiModule::Selectable* item, UiModule::MenuItemId id)
|
||||
{
|
||||
switch (id) {
|
||||
case 0: // Player
|
||||
ModMenuModule::RootModule::GetInstance()->AddMenu<ModMenuModule::PlayerMenu>();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
16
src/modules/modmenu/menus/main-menu.h
Normal file
16
src/modules/modmenu/menus/main-menu.h
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include "../common.h"
|
||||
#include "../menu-base.h"
|
||||
|
||||
namespace ModMenuModule {
|
||||
class MainMenu : public MenuBase {
|
||||
public:
|
||||
MainMenu();
|
||||
virtual ~MainMenu();
|
||||
|
||||
virtual bool Attach() override;
|
||||
|
||||
private:
|
||||
void OnMenuAction(UiModule::Selectable* item, UiModule::MenuItemId id) override;
|
||||
};
|
||||
}
|
||||
5
src/modules/modmenu/menus/menus.h
Normal file
5
src/modules/modmenu/menus/menus.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "main-menu.h"
|
||||
#include "player-menu.h"
|
||||
#include "player-pos-menu.h"
|
||||
39
src/modules/modmenu/menus/player-menu.cpp
Normal file
39
src/modules/modmenu/menus/player-menu.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
#include "player-menu.h"
|
||||
#include "player-pos-menu.h"
|
||||
#include "../root.h"
|
||||
|
||||
ModMenuModule::PlayerMenu::PlayerMenu()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ModMenuModule::PlayerMenu::~PlayerMenu()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool ModMenuModule::PlayerMenu::Attach()
|
||||
{
|
||||
UiModule::Component* vertCont;
|
||||
CreateMenu(L"#Player#", vertCont);
|
||||
ModMenuModule::ModMenuOptions options = ModMenuModule::RootModule::GetInstance()->GetOptions();
|
||||
|
||||
m_menuController->CreateItem<UiModule::Text>(vertCont, L"Go back", options.textSize);
|
||||
m_menuController->CreateItem<UiModule::Text>(vertCont, L"Position", options.textSize);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModMenuModule::PlayerMenu::OnMenuAction(UiModule::Selectable* item, UiModule::MenuItemId id)
|
||||
{
|
||||
switch (id) {
|
||||
case 0: // Go back
|
||||
ModMenuModule::RootModule::GetInstance()->RemoveLastMenu();
|
||||
break;
|
||||
case 1: // Position
|
||||
ModMenuModule::RootModule::GetInstance()->AddMenu<ModMenuModule::PlayerPosMenu>();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
16
src/modules/modmenu/menus/player-menu.h
Normal file
16
src/modules/modmenu/menus/player-menu.h
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include "../common.h"
|
||||
#include "../menu-base.h"
|
||||
|
||||
namespace ModMenuModule {
|
||||
class PlayerMenu : public MenuBase {
|
||||
public:
|
||||
PlayerMenu();
|
||||
virtual ~PlayerMenu();
|
||||
|
||||
virtual bool Attach() override;
|
||||
|
||||
private:
|
||||
void OnMenuAction(UiModule::Selectable* item, UiModule::MenuItemId id) override;
|
||||
};
|
||||
}
|
||||
109
src/modules/modmenu/menus/player-pos-menu.cpp
Normal file
109
src/modules/modmenu/menus/player-pos-menu.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
#include "player-pos-menu.h"
|
||||
#include "../root.h"
|
||||
|
||||
ModMenuModule::PlayerPosMenu::PlayerPosMenu()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ModMenuModule::PlayerPosMenu::~PlayerPosMenu()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool ModMenuModule::PlayerPosMenu::Attach()
|
||||
{
|
||||
UiModule::Component* vertCont;
|
||||
CreateMenu(L"#Player position#", vertCont);
|
||||
UiModule::RootModule* uiRoot = UiModule::RootModule::GetInstance();
|
||||
ModMenuModule::ModMenuOptions options = ModMenuModule::RootModule::GetInstance()->GetOptions();
|
||||
|
||||
m_menuController->CreateItem<UiModule::Text>(vertCont, L"Go back", options.textSize);
|
||||
|
||||
Core::Resolver<Game::Sprite> spriteResolver = Core::MakeResolver(
|
||||
[]() { return Game::Memory::GetGame(); },
|
||||
mem(&Game::Game::players), idx(0),
|
||||
mem(&Game::Player::ped),
|
||||
mem(&Game::Ped::gameObject),
|
||||
mem(&Game::GameObject::sprite)
|
||||
);
|
||||
|
||||
auto onEditStop = [this]() {
|
||||
if(m_visible) m_menuController->SetActive(true);
|
||||
};
|
||||
|
||||
UiModule::Text* xText = m_menuController->CreateItem<UiModule::Text>(vertCont, L"", options.textSize);
|
||||
m_xController = uiRoot->AddController<UiModule::VarTextEditableController<Game::SCR_f>>(
|
||||
xText,
|
||||
Core::MakeResolver(spriteResolver, mem(&Game::Sprite::x)),
|
||||
UiModule::VarTextEditableControllerOptions{ L"X: #", L"#" }
|
||||
);
|
||||
m_xController->SetOnEditStopCallback(onEditStop);
|
||||
|
||||
UiModule::Text* yText = m_menuController->CreateItem<UiModule::Text>(vertCont, L"", options.textSize);
|
||||
m_yController = uiRoot->AddController<UiModule::VarTextEditableController<Game::SCR_f>>(
|
||||
yText,
|
||||
Core::MakeResolver(spriteResolver, mem(&Game::Sprite::y)),
|
||||
UiModule::VarTextEditableControllerOptions{ L"Y: #", L"#" }
|
||||
);
|
||||
m_yController->SetOnEditStopCallback(onEditStop);
|
||||
|
||||
UiModule::Text* zText = m_menuController->CreateItem<UiModule::Text>(vertCont, L"", options.textSize);
|
||||
m_zController = uiRoot->AddController<UiModule::VarTextEditableController<Game::SCR_f>>(
|
||||
zText,
|
||||
Core::MakeResolver(spriteResolver, mem(&Game::Sprite::z)),
|
||||
UiModule::VarTextEditableControllerOptions{ L"Z: #", L"#" }
|
||||
);
|
||||
m_zController->SetOnEditStopCallback(onEditStop);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModMenuModule::PlayerPosMenu::Detach()
|
||||
{
|
||||
UiModule::RootModule* uiRoot = UiModule::RootModule::GetInstance();
|
||||
uiRoot->RemoveController(m_xController);
|
||||
uiRoot->RemoveController(m_yController);
|
||||
uiRoot->RemoveController(m_zController);
|
||||
DestroyMenu();
|
||||
}
|
||||
|
||||
void ModMenuModule::PlayerPosMenu::OnShow()
|
||||
{
|
||||
m_xController->SetWatching(true);
|
||||
m_yController->SetWatching(true);
|
||||
m_zController->SetWatching(true);
|
||||
}
|
||||
|
||||
void ModMenuModule::PlayerPosMenu::OnHide()
|
||||
{
|
||||
m_xController->SetWatching(false);
|
||||
m_xController->SetEditing(false);
|
||||
m_yController->SetWatching(false);
|
||||
m_yController->SetEditing(false);
|
||||
m_zController->SetWatching(false);
|
||||
m_zController->SetEditing(false);
|
||||
}
|
||||
|
||||
void ModMenuModule::PlayerPosMenu::OnMenuAction(UiModule::Selectable* item, UiModule::MenuItemId id)
|
||||
{
|
||||
switch (id) {
|
||||
case 0: // Go back
|
||||
ModMenuModule::RootModule::GetInstance()->RemoveLastMenu();
|
||||
break;
|
||||
case 1: // X
|
||||
m_menuController->SetActive(false);
|
||||
m_xController->SetEditing(true);
|
||||
break;
|
||||
case 2: // Y
|
||||
m_menuController->SetActive(false);
|
||||
m_yController->SetEditing(true);
|
||||
break;
|
||||
case 3: // Z
|
||||
m_menuController->SetActive(false);
|
||||
m_zController->SetEditing(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
23
src/modules/modmenu/menus/player-pos-menu.h
Normal file
23
src/modules/modmenu/menus/player-pos-menu.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include "../common.h"
|
||||
#include "../menu-base.h"
|
||||
|
||||
namespace ModMenuModule {
|
||||
class PlayerPosMenu : public MenuBase {
|
||||
public:
|
||||
PlayerPosMenu();
|
||||
virtual ~PlayerPosMenu();
|
||||
|
||||
virtual bool Attach() override;
|
||||
virtual void Detach() override;
|
||||
|
||||
virtual void OnShow() override;
|
||||
virtual void OnHide() override;
|
||||
private:
|
||||
void OnMenuAction(UiModule::Selectable* item, UiModule::MenuItemId id) override;
|
||||
|
||||
UiModule::VarTextEditableController<Game::SCR_f>* m_xController = nullptr;
|
||||
UiModule::VarTextEditableController<Game::SCR_f>* m_yController = nullptr;
|
||||
UiModule::VarTextEditableController<Game::SCR_f>* m_zController = nullptr;
|
||||
};
|
||||
}
|
||||
6
src/modules/modmenu/modmenu.h
Normal file
6
src/modules/modmenu/modmenu.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
#include "menu-base.h"
|
||||
#include "menus/menus.h"
|
||||
#include "root.h"
|
||||
82
src/modules/modmenu/root.cpp
Normal file
82
src/modules/modmenu/root.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
#include "root.h"
|
||||
|
||||
ModMenuModule::RootModule* ModMenuModule::RootModule::m_instance = nullptr;
|
||||
|
||||
ModMenuModule::RootModule::RootModule(ModMenuModule::ModMenuOptions options)
|
||||
: m_options(options)
|
||||
{
|
||||
assert(UiModule::RootModule::GetInstance() != nullptr, "ModMenu::RootModule requires Ui::RootModule to be initialized first!");
|
||||
assert(m_instance == nullptr, "ModMenu::RootModule instance already exists!");
|
||||
m_instance = this;
|
||||
spdlog::info("ModMenu::RootModule module initialized.");
|
||||
}
|
||||
|
||||
ModMenuModule::RootModule::~RootModule()
|
||||
{
|
||||
m_instance = nullptr;
|
||||
}
|
||||
|
||||
ModMenuModule::RootModule* ModMenuModule::RootModule::GetInstance()
|
||||
{
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
bool ModMenuModule::RootModule::Attach()
|
||||
{
|
||||
AddEventListener<KeyDownEvent>(&RootModule::OnKeyDown);
|
||||
spdlog::info("ModMenu::RootModule module attached.");
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModMenuModule::RootModule::Detach()
|
||||
{
|
||||
RemoveAllEventListeners();
|
||||
spdlog::info("ModMenu::RootModule module detached.");
|
||||
}
|
||||
|
||||
void ModMenuModule::RootModule::RemoveLastMenu()
|
||||
{
|
||||
if (m_menus.empty()) {
|
||||
spdlog::warn("Attempted to remove menu from empty stack");
|
||||
return;
|
||||
}
|
||||
m_menus.back()->Detach();
|
||||
m_menus.pop_back();
|
||||
|
||||
if (m_menus.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModMenuModule::MenuBase* menu = m_menus.back().get();
|
||||
menu->Attach();
|
||||
menu->SetVisible(m_visible);
|
||||
}
|
||||
|
||||
void ModMenuModule::RootModule::ClearMenus()
|
||||
{
|
||||
while (!m_menus.empty()) {
|
||||
m_menus.back()->Detach();
|
||||
m_menus.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
void ModMenuModule::RootModule::SetVisible(bool visible)
|
||||
{
|
||||
if (visible == m_visible) {
|
||||
return;
|
||||
}
|
||||
m_visible = visible;
|
||||
|
||||
if (m_menus.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_menus.back()->SetVisible(visible);
|
||||
}
|
||||
|
||||
void ModMenuModule::RootModule::OnKeyDown(const KeyDownEvent& event)
|
||||
{
|
||||
if (event.GetKeyCode() == m_options.toggleKey) {
|
||||
SetVisible(!m_visible);
|
||||
}
|
||||
}
|
||||
61
src/modules/modmenu/root.h
Normal file
61
src/modules/modmenu/root.h
Normal file
@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "menu-base.h"
|
||||
#include "../../events/keyboard.h"
|
||||
|
||||
namespace ModMenuModule {
|
||||
struct ModMenuOptions {
|
||||
Game::KeyCode toggleKey = Game::KeyCode::DIK_F11;
|
||||
Game::SCR_f menuX = 0x40000;
|
||||
Game::SCR_f menuY = 0x200000;
|
||||
Game::SCR_f menuHeaderMarginX = 0x60000;
|
||||
Game::SCR_f menuHeaderMarginY = 0x30000;
|
||||
Game::SCR_f menuHeaderTextSize = 0x6000;
|
||||
Game::SCR_f textSize = 0x6000;
|
||||
UiModule::MenuControllerOptions menuControllerOptions = {};
|
||||
};
|
||||
|
||||
class RootModule : public Core::ModuleBase {
|
||||
public:
|
||||
RootModule(ModMenuOptions options = {});
|
||||
virtual ~RootModule() override;
|
||||
static RootModule* GetInstance();
|
||||
bool Attach();
|
||||
void Detach();
|
||||
|
||||
template<typename MenuT, typename... Args>
|
||||
MenuT* AddMenu(Args&&... args) {
|
||||
static_assert(std::is_base_of_v<MenuBase, MenuT>, "MenuT must be derived from MenuBase");
|
||||
|
||||
MenuT* newMenu = new MenuT(std::forward<Args>(args)...);
|
||||
if (!newMenu->Attach()) {
|
||||
spdlog::error("Failed to attach mod menu {}", typeid(MenuT).name());
|
||||
delete newMenu;
|
||||
return nullptr;
|
||||
}
|
||||
if (m_visible) {
|
||||
newMenu->SetVisible(true);
|
||||
}
|
||||
|
||||
if (!m_menus.empty()) {
|
||||
m_menus.back()->Detach();
|
||||
}
|
||||
|
||||
m_menus.emplace_back(std::unique_ptr<MenuBase>(newMenu));
|
||||
return newMenu;
|
||||
}
|
||||
|
||||
void RemoveLastMenu();
|
||||
void ClearMenus();
|
||||
void SetVisible(bool visible);
|
||||
ModMenuOptions GetOptions() const { return m_options; }
|
||||
|
||||
private:
|
||||
void OnKeyDown(const KeyDownEvent& event);
|
||||
static RootModule* m_instance;
|
||||
|
||||
ModMenuOptions m_options;
|
||||
std::vector<std::unique_ptr<MenuBase>> m_menus;
|
||||
bool m_visible = false;
|
||||
};
|
||||
}
|
||||
8
src/modules/ui/common.h
Normal file
8
src/modules/ui/common.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../game/game.h"
|
||||
#include "../../core/core.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cfenv>
|
||||
#include "spdlog/spdlog.h"
|
||||
102
src/modules/ui/component.cpp
Normal file
102
src/modules/ui/component.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
#include "component.h"
|
||||
|
||||
UiModule::Component::~Component()
|
||||
{
|
||||
while(!m_children.empty()) {
|
||||
RemoveChild(m_children.back());
|
||||
}
|
||||
|
||||
if(m_parent) {
|
||||
m_parent->RemoveChild(this);
|
||||
}
|
||||
}
|
||||
|
||||
void UiModule::Component::DrawIfVisible()
|
||||
{
|
||||
if (!m_visibleInherited) {
|
||||
return;
|
||||
}
|
||||
Draw();
|
||||
}
|
||||
|
||||
void UiModule::Component::AddChild(Component* child)
|
||||
{
|
||||
if (!child) {
|
||||
spdlog::error("Attempted to add null child to component");
|
||||
return;
|
||||
}
|
||||
|
||||
child->m_parent = this;
|
||||
m_children.push_back(child);
|
||||
UpdateChildrenPos();
|
||||
child->UpdateChildrenVisibility();
|
||||
}
|
||||
|
||||
void UiModule::Component::RemoveChild(Component* child)
|
||||
{
|
||||
if (!child) {
|
||||
spdlog::error("Attempted to remove null child from component");
|
||||
return;
|
||||
}
|
||||
auto it = std::find(m_children.begin(), m_children.end(), child);
|
||||
if (it == m_children.end()) {
|
||||
spdlog::error("Attempted to remove a child that is not a child of this component");
|
||||
return;
|
||||
}
|
||||
m_children.erase(it);
|
||||
child->m_parent = nullptr;
|
||||
child->SetPosition(0, 0);
|
||||
UpdateChildrenPos();
|
||||
child->UpdateChildrenVisibility();
|
||||
}
|
||||
|
||||
void UiModule::Component::UpdateChildrenPos()
|
||||
{
|
||||
for (auto child : m_children) {
|
||||
child->SetPosition(m_rect.x, m_rect.y);
|
||||
}
|
||||
}
|
||||
|
||||
void UiModule::Component::SetPosition(Game::SCR_f x, Game::SCR_f y)
|
||||
{
|
||||
m_rect.x = x;
|
||||
m_rect.y = y;
|
||||
UpdateChildrenPos();
|
||||
}
|
||||
|
||||
void UiModule::Component::OnChildSizeUpdate(Component* child)
|
||||
{
|
||||
Rect prevRect = m_rect;
|
||||
UpdateChildrenPos();
|
||||
if (m_parent && (m_rect.width != prevRect.width || m_rect.height != prevRect.height)) {
|
||||
m_parent->OnChildSizeUpdate(this);
|
||||
}
|
||||
}
|
||||
|
||||
void UiModule::Component::SetVisible(bool visible)
|
||||
{
|
||||
if (m_visible == visible) {
|
||||
return;
|
||||
}
|
||||
m_visible = visible;
|
||||
UpdateChildrenVisibility();
|
||||
}
|
||||
|
||||
void UiModule::Component::UpdateSize(Game::SCR_f* width, Game::SCR_f* height)
|
||||
{
|
||||
Rect prevRect = m_rect;
|
||||
if(width) m_rect.width = *width;
|
||||
if(height) m_rect.height = *height;
|
||||
if (m_parent && (m_rect.width != prevRect.width || m_rect.height != prevRect.height)) {
|
||||
m_parent->OnChildSizeUpdate(this);
|
||||
}
|
||||
}
|
||||
|
||||
void UiModule::Component::UpdateChildrenVisibility()
|
||||
{
|
||||
bool parentVisible = m_parent ? m_parent->m_visibleInherited : true;
|
||||
m_visibleInherited = m_visible && parentVisible;
|
||||
for (auto child : m_children) {
|
||||
child->UpdateChildrenVisibility();
|
||||
}
|
||||
}
|
||||
32
src/modules/ui/component.h
Normal file
32
src/modules/ui/component.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "rect.h"
|
||||
|
||||
namespace UiModule {
|
||||
class Component {
|
||||
public:
|
||||
virtual ~Component();
|
||||
virtual void DrawIfVisible();
|
||||
virtual void Draw() {}
|
||||
|
||||
virtual void AddChild(Component* child);
|
||||
virtual void RemoveChild(Component* child);
|
||||
virtual void UpdateChildrenPos();
|
||||
void SetPosition(Game::SCR_f x, Game::SCR_f y);
|
||||
|
||||
Rect GetRect() const { return m_rect; }
|
||||
Component* GetParent() const { return m_parent; }
|
||||
std::vector<Component*> GetChildren() const { return m_children; }
|
||||
void OnChildSizeUpdate(Component* child);
|
||||
void SetVisible(bool visible);
|
||||
bool IsVisible() const { return m_visibleInherited; }
|
||||
protected:
|
||||
void UpdateSize(Game::SCR_f* width, Game::SCR_f* height);
|
||||
void UpdateChildrenVisibility();
|
||||
Component* m_parent = nullptr;
|
||||
std::vector<Component*> m_children = {};
|
||||
Rect m_rect = { 0, 0, 0, 0 };
|
||||
bool m_visible = true;
|
||||
bool m_visibleInherited = true;
|
||||
};
|
||||
}
|
||||
8
src/modules/ui/components/components.h
Normal file
8
src/modules/ui/components/components.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "./text.h"
|
||||
#include "./vertcont.h"
|
||||
#include "./horcont.h"
|
||||
#include "./spacer.h"
|
||||
#include "./margin.h"
|
||||
#include "./selectable.h"
|
||||
28
src/modules/ui/components/horcont.h
Normal file
28
src/modules/ui/components/horcont.h
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include "../common.h"
|
||||
#include "../component.h"
|
||||
|
||||
namespace UiModule {
|
||||
class HorCont : public Component {
|
||||
public:
|
||||
HorCont(Component* parent) {
|
||||
if (parent) {
|
||||
parent->AddChild(this);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void UpdateChildrenPos() override {
|
||||
Game::SCR_f currentX = m_rect.x;
|
||||
Game::SCR_f maxHeight = 0;
|
||||
for (auto child : m_children) {
|
||||
child->SetPosition(currentX, m_rect.y);
|
||||
Rect childRect = child->GetRect();
|
||||
currentX += childRect.width;
|
||||
maxHeight = max(maxHeight, childRect.height);
|
||||
}
|
||||
|
||||
Game::SCR_f newWidth = currentX - m_rect.x;
|
||||
UpdateSize(&newWidth, &maxHeight);
|
||||
}
|
||||
};
|
||||
}
|
||||
47
src/modules/ui/components/margin.h
Normal file
47
src/modules/ui/components/margin.h
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include "../common.h"
|
||||
#include "../component.h"
|
||||
|
||||
namespace UiModule {
|
||||
class Margin : public Component {
|
||||
public:
|
||||
Margin(Component* parent, Game::SCR_f marginX, Game::SCR_f marginY) {
|
||||
m_marginX = marginX;
|
||||
m_marginY = marginY;
|
||||
m_rect.width = marginX*2;
|
||||
m_rect.height = marginY*2;
|
||||
|
||||
if (parent) {
|
||||
parent->AddChild(this);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void UpdateChildrenPos() override {
|
||||
Game::SCR_f maxWidth = 0;
|
||||
Game::SCR_f maxHeight = 0;
|
||||
|
||||
for (auto child : m_children) {
|
||||
child->SetPosition(m_rect.x + m_marginX, m_rect.y + m_marginY);
|
||||
Rect childRect = child->GetRect();
|
||||
maxWidth = max(maxWidth, childRect.width);
|
||||
maxHeight = max(maxHeight, childRect.height);
|
||||
}
|
||||
|
||||
Game::SCR_f newWidth = maxWidth + m_marginX * 2;
|
||||
Game::SCR_f newHeight = maxHeight + m_marginY * 2;
|
||||
UpdateSize(&newWidth, &newHeight);
|
||||
}
|
||||
|
||||
void SetMargin(Game::SCR_f marginX, Game::SCR_f marginY) {
|
||||
m_marginX = marginX;
|
||||
m_marginY = marginY;
|
||||
UpdateChildrenPos();
|
||||
}
|
||||
Game::SCR_f GetMarginX() const { return m_marginX; }
|
||||
Game::SCR_f GetMarginY() const { return m_marginY; }
|
||||
|
||||
protected:
|
||||
Game::SCR_f m_marginX = 0;
|
||||
Game::SCR_f m_marginY = 0;
|
||||
};
|
||||
}
|
||||
47
src/modules/ui/components/selectable.cpp
Normal file
47
src/modules/ui/components/selectable.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
#include "selectable.h"
|
||||
|
||||
UiModule::Selectable::Selectable(Component* parent, SelectableOptions options)
|
||||
{
|
||||
m_options = options;
|
||||
m_rect.width = options.markerOffsetX;
|
||||
m_rect.height = options.markerScale * 20;
|
||||
|
||||
if (parent) {
|
||||
parent->AddChild(this);
|
||||
}
|
||||
}
|
||||
|
||||
void UiModule::Selectable::Draw()
|
||||
{
|
||||
if(!m_options.selected) {
|
||||
return;
|
||||
}
|
||||
|
||||
Game::S4_ENUM1 s4enum2 = (Game::S4_ENUM1)2;
|
||||
Game::Functions::DrawGTAText(
|
||||
(WCHAR*)m_options.markerText.c_str(),
|
||||
m_rect.x,
|
||||
m_rect.y + m_options.markerAdditionalOffsetY,
|
||||
1,
|
||||
m_options.markerScale,
|
||||
&s4enum2,
|
||||
0,
|
||||
Game::SPRITE_INVISIBILITY_VISIBLE,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
void UiModule::Selectable::UpdateChildrenPos()
|
||||
{
|
||||
Game::SCR_f maxWidth = m_options.markerOffsetX;
|
||||
Game::SCR_f maxHeight = 0;
|
||||
for (auto child : m_children) {
|
||||
child->SetPosition(m_rect.x + m_options.markerOffsetX, m_rect.y);
|
||||
Rect childRect = child->GetRect();
|
||||
maxWidth = max(maxWidth, m_options.markerOffsetX + childRect.width);
|
||||
maxHeight = max(maxHeight, childRect.height);
|
||||
|
||||
}
|
||||
|
||||
UpdateSize(&maxWidth, &maxHeight);
|
||||
}
|
||||
29
src/modules/ui/components/selectable.h
Normal file
29
src/modules/ui/components/selectable.h
Normal file
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
#include "../common.h"
|
||||
#include "../component.h"
|
||||
|
||||
namespace UiModule {
|
||||
struct SelectableOptions {
|
||||
bool selected = false;
|
||||
Game::SCR_f markerScale = 0x6000;
|
||||
Game::SCR_f markerOffsetX = 0x60000;
|
||||
Game::SCR_f markerAdditionalOffsetY = 0x18000; /* doesn't affect component size */
|
||||
std::wstring markerText = L"*";
|
||||
};
|
||||
|
||||
class Selectable : public Component {
|
||||
public:
|
||||
Selectable(Component* parent, SelectableOptions options = {});
|
||||
virtual void Draw() override;
|
||||
|
||||
virtual void UpdateChildrenPos() override;
|
||||
|
||||
void SetSelected(bool selected) {
|
||||
m_options.selected = selected;
|
||||
}
|
||||
bool IsSelected() const { return m_options.selected; }
|
||||
|
||||
protected:
|
||||
SelectableOptions m_options;
|
||||
};
|
||||
}
|
||||
22
src/modules/ui/components/spacer.h
Normal file
22
src/modules/ui/components/spacer.h
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "../common.h"
|
||||
#include "../component.h"
|
||||
|
||||
namespace UiModule {
|
||||
class Spacer : public Component {
|
||||
public:
|
||||
Spacer(Component* parent, Game::SCR_f width, Game::SCR_f height) {
|
||||
m_rect.width = width;
|
||||
m_rect.height = height;
|
||||
if (parent) {
|
||||
parent->AddChild(this);
|
||||
}
|
||||
}
|
||||
|
||||
void SetSize(Game::SCR_f width, Game::SCR_f height) {
|
||||
UpdateSize(&width, &height);
|
||||
}
|
||||
Game::SCR_f GetWidth() const { return m_rect.width; }
|
||||
Game::SCR_f GetHeight() const { return m_rect.height; }
|
||||
};
|
||||
}
|
||||
43
src/modules/ui/components/text.cpp
Normal file
43
src/modules/ui/components/text.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#include "text.h"
|
||||
|
||||
UiModule::Text::Text(Component* parent, const std::wstring& text, Game::SCR_f scale)
|
||||
{
|
||||
m_text = text;
|
||||
m_scale = scale;
|
||||
m_rect.width = 0; /// TODO
|
||||
m_rect.height = scale * 20;
|
||||
|
||||
if (parent) {
|
||||
parent->AddChild(this);
|
||||
}
|
||||
}
|
||||
|
||||
void UiModule::Text::Draw()
|
||||
{
|
||||
Game::S4_ENUM1 s4enum2 = (Game::S4_ENUM1)2;
|
||||
Game::Functions::DrawGTAText(
|
||||
(WCHAR*)m_text.c_str(),
|
||||
m_rect.x,
|
||||
m_rect.y,
|
||||
1,
|
||||
m_scale,
|
||||
&s4enum2,
|
||||
1,
|
||||
Game::SPRITE_INVISIBILITY_VISIBLE,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
void UiModule::Text::SetText(const std::wstring& text)
|
||||
{
|
||||
m_text = text;
|
||||
/// TODO: update width
|
||||
}
|
||||
|
||||
void UiModule::Text::SetScale(Game::SCR_f scale)
|
||||
{
|
||||
m_scale = scale;
|
||||
Game::SCR_f newHeight = scale * 20;
|
||||
UpdateSize(nullptr, &newHeight);
|
||||
}
|
||||
|
||||
20
src/modules/ui/components/text.h
Normal file
20
src/modules/ui/components/text.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include "../common.h"
|
||||
#include "../component.h"
|
||||
|
||||
namespace UiModule {
|
||||
class Text : public Component {
|
||||
public:
|
||||
Text(Component* parent, const std::wstring& text, Game::SCR_f scale = 0x6000);
|
||||
virtual void Draw() override;
|
||||
|
||||
void SetText(const std::wstring& text);
|
||||
const std::wstring& GetText() const { return m_text; }
|
||||
void SetScale(Game::SCR_f scale);
|
||||
Game::SCR_f GetScale() const { return m_scale; }
|
||||
|
||||
protected:
|
||||
std::wstring m_text;
|
||||
Game::SCR_f m_scale;
|
||||
};
|
||||
}
|
||||
28
src/modules/ui/components/vertcont.h
Normal file
28
src/modules/ui/components/vertcont.h
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include "../common.h"
|
||||
#include "../component.h"
|
||||
|
||||
namespace UiModule {
|
||||
class VertCont : public Component {
|
||||
public:
|
||||
VertCont(Component* parent) {
|
||||
if (parent) {
|
||||
parent->AddChild(this);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void UpdateChildrenPos() override {
|
||||
Game::SCR_f currentY = m_rect.y;
|
||||
Game::SCR_f maxWidth = 0;
|
||||
for (auto child : m_children) {
|
||||
child->SetPosition(m_rect.x, currentY);
|
||||
Rect childRect = child->GetRect();
|
||||
currentY += childRect.height;
|
||||
maxWidth = max(maxWidth, childRect.width);
|
||||
}
|
||||
|
||||
Game::SCR_f newHeight = currentY - m_rect.y;
|
||||
UpdateSize(&maxWidth, &newHeight);
|
||||
}
|
||||
};
|
||||
}
|
||||
14
src/modules/ui/controller.h
Normal file
14
src/modules/ui/controller.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
namespace UiModule
|
||||
{
|
||||
class Controller
|
||||
{
|
||||
public:
|
||||
virtual ~Controller() = default;
|
||||
|
||||
protected:
|
||||
Controller() = default;
|
||||
};
|
||||
}
|
||||
5
src/modules/ui/controllers/controllers.h
Normal file
5
src/modules/ui/controllers/controllers.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "menu.h"
|
||||
#include "var-text.h"
|
||||
#include "var-text-editable.h"
|
||||
131
src/modules/ui/controllers/menu.cpp
Normal file
131
src/modules/ui/controllers/menu.cpp
Normal file
@ -0,0 +1,131 @@
|
||||
#include "menu.h"
|
||||
#include "var-text.h"
|
||||
|
||||
UiModule::MenuController::MenuController(MenuControllerOptions options)
|
||||
{
|
||||
m_options = options;
|
||||
}
|
||||
|
||||
UiModule::MenuController::~MenuController()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
UiModule::MenuItemId UiModule::MenuController::AddItem(Selectable* item)
|
||||
{
|
||||
if (!item) {
|
||||
spdlog::error("Attempted to add null item to MenuController");
|
||||
return -1;
|
||||
}
|
||||
m_items.push_back(item);
|
||||
if (m_items.size() == 1) {
|
||||
SetIndex(0);
|
||||
}
|
||||
return m_items.size() - 1;
|
||||
}
|
||||
|
||||
std::vector<UiModule::MenuItemId> UiModule::MenuController::AddItems(const std::vector<Selectable*>& items)
|
||||
{
|
||||
std::vector<UiModule::MenuItemId> ids;
|
||||
for (auto* item : items) {
|
||||
ids.push_back(AddItem(item));
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
void UiModule::MenuController::RemoveItem(MenuItemId id)
|
||||
{
|
||||
if (id < 0 || id >= m_items.size()) {
|
||||
spdlog::error("Attempted to remove invalid item from MenuController");
|
||||
return;
|
||||
}
|
||||
m_items.erase(m_items.begin() + id);
|
||||
|
||||
MenuItemId index = m_currentIndex;
|
||||
if (m_currentIndex >= id && m_currentIndex > 0) {
|
||||
index = m_currentIndex - 1;
|
||||
}
|
||||
|
||||
SetIndex(index);
|
||||
}
|
||||
|
||||
void UiModule::MenuController::SetActive(bool active)
|
||||
{
|
||||
if(active == m_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_active = active;
|
||||
if (active) {
|
||||
AddEventListener<KeyDownEvent>(&MenuController::OnKeyDown);
|
||||
} else {
|
||||
RemoveEventListener<KeyDownEvent>();
|
||||
}
|
||||
|
||||
if (m_options.hideMarkerNonActive) {
|
||||
SetIndex(m_currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void UiModule::MenuController::SetIndex(MenuItemId index)
|
||||
{
|
||||
if (index < 0 || index >= m_items.size()) {
|
||||
spdlog::error("Attempted to set invalid index in MenuController");
|
||||
return;
|
||||
}
|
||||
m_currentIndex = index;
|
||||
bool hideIndex = !m_active && m_options.hideMarkerNonActive;
|
||||
for (size_t i = 0; i < m_items.size(); ++i) {
|
||||
m_items[i]->SetSelected(i == m_currentIndex && !hideIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void UiModule::MenuController::Next()
|
||||
{
|
||||
if(!m_options.loop && m_currentIndex + 1 >= m_items.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_items.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetIndex((m_currentIndex + 1) % m_items.size());
|
||||
}
|
||||
|
||||
void UiModule::MenuController::Previous()
|
||||
{
|
||||
if(!m_options.loop && m_currentIndex == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_items.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetIndex((m_currentIndex - 1 + m_items.size()) % m_items.size());
|
||||
}
|
||||
|
||||
void UiModule::MenuController::Action()
|
||||
{
|
||||
if (m_onAction && m_currentIndex >= 0 && m_currentIndex < m_items.size()) {
|
||||
m_onAction(m_items[m_currentIndex], m_currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void UiModule::MenuController::OnKeyDown(const KeyDownEvent& event)
|
||||
{
|
||||
if (!m_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
Game::KeyCode key = event.GetKeyCode();
|
||||
|
||||
if( key == m_options.keyPrev ) {
|
||||
Previous();
|
||||
} else if( key == m_options.keyNext ) {
|
||||
Next();
|
||||
} else if( key == m_options.keyAction ) {
|
||||
Action();
|
||||
}
|
||||
}
|
||||
60
src/modules/ui/controllers/menu.h
Normal file
60
src/modules/ui/controllers/menu.h
Normal file
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
#include "../common.h"
|
||||
#include "../controller.h"
|
||||
#include "../components/selectable.h"
|
||||
#include "../root.h"
|
||||
#include "../../../events/keyboard.h"
|
||||
|
||||
namespace UiModule {
|
||||
using MenuItemId = size_t;
|
||||
using MenuActionCallback = std::function<void(Selectable*, MenuItemId)>;
|
||||
|
||||
struct MenuControllerOptions {
|
||||
bool loop = true;
|
||||
bool hideMarkerNonActive = false;
|
||||
Game::KeyCode keyPrev = Game::KeyCode::DIK_LBRACKET;
|
||||
Game::KeyCode keyNext = Game::KeyCode::DIK_RBRACKET;
|
||||
Game::KeyCode keyAction = Game::KeyCode::DIK_BACKSLASH;
|
||||
SelectableOptions createdSelectableOptions = {};
|
||||
};
|
||||
|
||||
class MenuController : public Controller, public Core::EventListenerSupport {
|
||||
public:
|
||||
MenuController(MenuControllerOptions options = {});
|
||||
virtual ~MenuController();
|
||||
|
||||
template<typename ComponentT, typename... Args>
|
||||
ComponentT* CreateItem(Component* parent, Args&&... otherArgs) {
|
||||
static_assert(std::is_base_of_v<Component, ComponentT>, "ComponentT must be derived from Component");
|
||||
Selectable* selectable = RootModule::GetInstance()->AddComponent<Selectable>(parent, m_options.createdSelectableOptions);
|
||||
ComponentT* item = RootModule::GetInstance()->AddComponent<ComponentT>(selectable, std::forward<Args>(otherArgs)...);
|
||||
AddItem(selectable);
|
||||
return item;
|
||||
}
|
||||
|
||||
MenuItemId AddItem(Selectable* item);
|
||||
std::vector<MenuItemId> AddItems(const std::vector<Selectable*>& items);
|
||||
void RemoveItem(MenuItemId id);
|
||||
|
||||
void SetActive(bool active);
|
||||
bool IsActive() const { return m_active; }
|
||||
|
||||
void SetIndex(MenuItemId index);
|
||||
MenuItemId GetIndex() const { return m_currentIndex; }
|
||||
|
||||
void Next();
|
||||
void Previous();
|
||||
void Action();
|
||||
|
||||
void SetOnActionCallback(MenuActionCallback callback) { m_onAction = callback; }
|
||||
|
||||
protected:
|
||||
void OnKeyDown(const KeyDownEvent& event);
|
||||
|
||||
MenuControllerOptions m_options;
|
||||
std::vector<Selectable*> m_items = {};
|
||||
MenuItemId m_currentIndex = 0;
|
||||
bool m_active = false;
|
||||
MenuActionCallback m_onAction = nullptr;
|
||||
};
|
||||
}
|
||||
243
src/modules/ui/controllers/var-text-editable.h
Normal file
243
src/modules/ui/controllers/var-text-editable.h
Normal file
@ -0,0 +1,243 @@
|
||||
#pragma once
|
||||
#include "../common.h"
|
||||
#include "../controller.h"
|
||||
#include "../components/text.h"
|
||||
#include "../utils/string-utils.h"
|
||||
#include "../../../events/draw.h"
|
||||
#include "../../../events/keyboard.h"
|
||||
|
||||
namespace UiModule {
|
||||
using VarTextEditableEditStopCallback = std::function<void()>;
|
||||
|
||||
struct VarTextEditableControllerOptions {
|
||||
std::wstring prefix = L"";
|
||||
std::wstring suffix = L"";
|
||||
std::wstring nullText = L"N/A";
|
||||
std::wstring marker = L"-";
|
||||
Game::KeyCode keyAction = Game::KeyCode::DIK_BACKSLASH;
|
||||
bool allowControlChars = false; // whether to allow \t, \n, \r
|
||||
int blinkInterval = 15; // frames
|
||||
bool removeTrailingZeros = true; // for floating point types
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class VarTextEditableController : public Controller, public Core::EventListenerSupport {
|
||||
public:
|
||||
VarTextEditableController(Text* text, Core::Resolver<T> resolver, VarTextEditableControllerOptions options = {}) {
|
||||
static_assert(std::is_copy_constructible<T>::value, "T must be copy-constructible");
|
||||
m_textComponent = text;
|
||||
m_options = options;
|
||||
m_resolver = resolver;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
virtual ~VarTextEditableController() {
|
||||
SetWatching(false);
|
||||
}
|
||||
|
||||
void SetWatching(bool watching) {
|
||||
m_watchingBeforeEdit = watching;
|
||||
if (m_editing && watching) return;
|
||||
if (m_watching == watching) return;
|
||||
m_watching = watching;
|
||||
if (watching) {
|
||||
m_watched = Core::WatchManager::GetInstance()->Watch<PreDrawUIEvent, T>(
|
||||
m_resolver,
|
||||
this,
|
||||
&VarTextEditableController<T>::OnValueUpdate
|
||||
);
|
||||
}
|
||||
else {
|
||||
Core::WatchManager::GetInstance()->Unwatch(m_watched);
|
||||
m_watched = nullptr;
|
||||
}
|
||||
|
||||
if(!m_editing) Update();
|
||||
}
|
||||
|
||||
bool IsWatching() const { return m_watching; }
|
||||
|
||||
void SetActive(bool active) {
|
||||
m_activeBeforeEdit = active;
|
||||
if (active == m_active) return;
|
||||
m_active = active;
|
||||
|
||||
if (active) {
|
||||
AddEventListener<KeyDownEvent>(&VarTextEditableController<T>::OnKeyDown);
|
||||
if (m_editing) SetPreDrawUIListener(true);
|
||||
}
|
||||
else {
|
||||
RemoveEventListener<KeyDownEvent>();
|
||||
SetPreDrawUIListener(false);
|
||||
}
|
||||
}
|
||||
|
||||
void SetPreDrawUIListener(bool enable) {
|
||||
if (enable == m_hasPreDrawListener) return;
|
||||
m_hasPreDrawListener = enable;
|
||||
if (enable) {
|
||||
AddEventListener<PreDrawUIEvent>(&VarTextEditableController<T>::OnPreDrawUI);
|
||||
m_blinkCounter = 0;
|
||||
}
|
||||
else {
|
||||
RemoveEventListener<PreDrawUIEvent>();
|
||||
}
|
||||
}
|
||||
|
||||
void SetEditing(bool editing) {
|
||||
if (m_editing == editing) return;
|
||||
m_editing = editing;
|
||||
if (editing) {
|
||||
if (!Load()) {
|
||||
m_editing = false;
|
||||
if (m_onEditStop) m_onEditStop();
|
||||
return;
|
||||
}
|
||||
bool watching = m_watching;
|
||||
bool active = m_active;
|
||||
SetWatching(false);
|
||||
SetActive(true);
|
||||
m_watchingBeforeEdit = watching;
|
||||
m_activeBeforeEdit = active;
|
||||
}
|
||||
else {
|
||||
if (m_watchingBeforeEdit) {
|
||||
SetWatching(true);
|
||||
}
|
||||
if (!m_activeBeforeEdit) {
|
||||
SetActive(false);
|
||||
}
|
||||
SetPreDrawUIListener(false);
|
||||
m_editBuffer = L"";
|
||||
Update();
|
||||
|
||||
if (m_onEditStop) m_onEditStop();
|
||||
}
|
||||
}
|
||||
|
||||
void SetOnEditStopCallback(VarTextEditableEditStopCallback callback) {
|
||||
m_onEditStop = callback;
|
||||
}
|
||||
|
||||
bool IsEditing() const { return m_editing; }
|
||||
|
||||
void Update() {
|
||||
T* resolved = m_resolver();
|
||||
std::optional<T> value = resolved ? std::optional<T>(*resolved) : std::nullopt;
|
||||
OnValueUpdate(std::nullopt, value);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool Load() {
|
||||
if (!m_editing) return false;
|
||||
T* resolved = m_resolver();
|
||||
if (resolved == nullptr) {
|
||||
spdlog::warn("Failed to resolve variable for editing");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_editBuffer = ToString<T>(*resolved);
|
||||
if ((std::is_same_v<T, Game::SCR_f> || std::is_floating_point_v<T>) && m_options.removeTrailingZeros) {
|
||||
RemoveTrailingZeros(m_editBuffer);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Save() {
|
||||
if (!m_editing) return;
|
||||
|
||||
try {
|
||||
T newValue = FromString<T>(m_editBuffer);
|
||||
T* resolved = m_resolver();
|
||||
if (resolved) {
|
||||
*resolved = newValue;
|
||||
}
|
||||
else {
|
||||
spdlog::warn("Failed to resolve variable for setting new value");
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
spdlog::warn("Failed to parse input '{}': {}", std::string(m_editBuffer.begin(), m_editBuffer.end()), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void SetValueText(std::wstring text) {
|
||||
std::wstring marker = m_editing && (m_blinkCounter < m_options.blinkInterval) ? m_options.marker : L"";
|
||||
m_textComponent->SetText(m_options.prefix + text + marker + m_options.suffix);
|
||||
}
|
||||
|
||||
void OnValueUpdate(std::optional<T> oldValue, std::optional<T> newValue) {
|
||||
if (m_editing) {
|
||||
spdlog::warn("Tried to update VarTextEditableController value while editing");
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue.has_value()) {
|
||||
std::wstring text = ToString<T>(newValue.value());
|
||||
if ((std::is_same_v<T, Game::SCR_f> || std::is_floating_point_v<T>) && m_options.removeTrailingZeros) {
|
||||
UiModule::RemoveTrailingZeros(text);
|
||||
}
|
||||
SetValueText(text);
|
||||
}
|
||||
else {
|
||||
SetValueText(m_options.nullText);
|
||||
}
|
||||
}
|
||||
|
||||
void OnKeyDown(const KeyDownEvent& event) {
|
||||
if (!m_active) return;
|
||||
|
||||
Game::KeyCode key = event.GetKeyCode();
|
||||
bool isShiftPressed = event.IsShiftPressed();
|
||||
|
||||
if (!m_editing) {
|
||||
if (key == m_options.keyAction) {
|
||||
SetEditing(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == m_options.keyAction) {
|
||||
Save();
|
||||
SetEditing(!m_editing);
|
||||
return;
|
||||
} else if (key == Game::KeyCode::DIK_BACK) {
|
||||
if (!m_editBuffer.empty()) {
|
||||
m_editBuffer.pop_back();
|
||||
}
|
||||
SetValueText(m_editBuffer);
|
||||
} else {
|
||||
char c = Game::GetCharFromKeyCode(key, isShiftPressed, false); /// TODO handle caps lock
|
||||
if (c == '\0') return;
|
||||
|
||||
if (!IsValidCharForType<T>(c, m_options.allowControlChars)) return;
|
||||
|
||||
m_editBuffer += c;
|
||||
SetValueText(m_editBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
void OnPreDrawUI(const PreDrawUIEvent& event) {
|
||||
if (!m_editing) return;
|
||||
if (m_blinkCounter == 0 || m_blinkCounter == m_options.blinkInterval) {
|
||||
SetValueText(m_editBuffer);
|
||||
}
|
||||
m_blinkCounter = (m_blinkCounter + 1) % (m_options.blinkInterval * 2);
|
||||
}
|
||||
|
||||
VarTextEditableControllerOptions m_options;
|
||||
Text* m_textComponent = nullptr;
|
||||
Core::Resolver<T> m_resolver = nullptr;
|
||||
Core::Watched<T>* m_watched = nullptr;
|
||||
bool m_watching = false;
|
||||
bool m_editing = false;
|
||||
bool m_active = false;
|
||||
bool m_watchingBeforeEdit = false;
|
||||
bool m_activeBeforeEdit = false;
|
||||
bool m_hasPreDrawListener = false;
|
||||
std::wstring m_editBuffer = L"";
|
||||
VarTextEditableEditStopCallback m_onEditStop = nullptr;
|
||||
int m_blinkCounter = 0;
|
||||
};
|
||||
}
|
||||
82
src/modules/ui/controllers/var-text.h
Normal file
82
src/modules/ui/controllers/var-text.h
Normal file
@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
#include "../common.h"
|
||||
#include "../controller.h"
|
||||
#include "../components/text.h"
|
||||
#include "../utils/string-utils.h"
|
||||
#include "../../../events/draw.h"
|
||||
|
||||
namespace UiModule {
|
||||
struct VarTextControllerOptions {
|
||||
std::wstring prefix = L"";
|
||||
std::wstring suffix = L"";
|
||||
std::wstring nullText = L"N/A";
|
||||
bool removeTrailingZeros = true; // for floating point types
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class VarTextController : public Controller, public Core::EventListenerSupport {
|
||||
public:
|
||||
VarTextController(Text* text, Core::Resolver<T> resolver, VarTextControllerOptions options = {}) {
|
||||
static_assert(std::is_copy_constructible<T>::value, "T must be copy-constructible");
|
||||
m_textComponent = text;
|
||||
m_options = options;
|
||||
m_resolver = resolver;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
virtual ~VarTextController() {
|
||||
SetWatching(false);
|
||||
}
|
||||
|
||||
void SetWatching(bool watching) {
|
||||
if (m_watching == watching) return;
|
||||
m_watching = watching;
|
||||
if (m_watching) {
|
||||
m_watched = Core::WatchManager::GetInstance()->Watch<PreDrawUIEvent, T>(
|
||||
m_resolver,
|
||||
this,
|
||||
&VarTextController<T>::OnValueUpdate
|
||||
);
|
||||
}
|
||||
else {
|
||||
Core::WatchManager::GetInstance()->Unwatch(m_watched);
|
||||
m_watched = nullptr;
|
||||
}
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
bool IsWatching() const { return m_watching; }
|
||||
|
||||
void Update() {
|
||||
T* resolved = m_resolver();
|
||||
std::optional<T> value = resolved ? std::optional<T>(*resolved) : std::nullopt;
|
||||
OnValueUpdate(std::nullopt, value);
|
||||
}
|
||||
|
||||
protected:
|
||||
void SetValueText(std::wstring text) {
|
||||
m_textComponent->SetText(m_options.prefix + text + m_options.suffix);
|
||||
}
|
||||
|
||||
void OnValueUpdate(std::optional<T> oldValue, std::optional<T> newValue) {
|
||||
if (newValue.has_value()) {
|
||||
std::wstring text = ToString<T>(newValue.value());
|
||||
if ((std::is_same_v<T, Game::SCR_f> || std::is_floating_point_v<T>) && m_options.removeTrailingZeros) {
|
||||
UiModule::RemoveTrailingZeros(text);
|
||||
}
|
||||
SetValueText(text);
|
||||
}
|
||||
else {
|
||||
SetValueText(m_options.nullText);
|
||||
}
|
||||
}
|
||||
|
||||
VarTextControllerOptions m_options;
|
||||
Text* m_textComponent = nullptr;
|
||||
Core::Resolver<T> m_resolver = nullptr;
|
||||
Core::Watched<T>* m_watched = nullptr;
|
||||
bool m_watching = false;
|
||||
};
|
||||
}
|
||||
11
src/modules/ui/rect.h
Normal file
11
src/modules/ui/rect.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
namespace UiModule {
|
||||
struct Rect {
|
||||
Game::SCR_f x;
|
||||
Game::SCR_f y;
|
||||
Game::SCR_f width;
|
||||
Game::SCR_f height;
|
||||
};
|
||||
}
|
||||
42
src/modules/ui/root.cpp
Normal file
42
src/modules/ui/root.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#include "root.h"
|
||||
|
||||
UiModule::RootModule* UiModule::RootModule::m_instance = nullptr;
|
||||
|
||||
UiModule::RootModule::RootModule()
|
||||
{
|
||||
assert(m_instance == nullptr, "UiModule::RootModule instance already exists!");
|
||||
m_instance = this;
|
||||
spdlog::info("UiModule::RootModule instance created");
|
||||
}
|
||||
|
||||
UiModule::RootModule::~RootModule()
|
||||
{
|
||||
ClearControllers();
|
||||
ClearComponents();
|
||||
m_instance = nullptr;
|
||||
}
|
||||
|
||||
UiModule::RootModule* UiModule::RootModule::GetInstance()
|
||||
{
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
bool UiModule::RootModule::Attach()
|
||||
{
|
||||
AddEventListener<DrawUIEvent>(&UiModule::RootModule::OnDraw);
|
||||
spdlog::info("UiModule::RootModule attached");
|
||||
return true;
|
||||
}
|
||||
|
||||
void UiModule::RootModule::Detach()
|
||||
{
|
||||
RemoveAllEventListeners();
|
||||
spdlog::info("UiModule::RootModule detached");
|
||||
}
|
||||
|
||||
void UiModule::RootModule::OnDraw(const DrawUIEvent& event)
|
||||
{
|
||||
for (const auto& component : m_components) {
|
||||
component->DrawIfVisible();
|
||||
}
|
||||
}
|
||||
97
src/modules/ui/root.h
Normal file
97
src/modules/ui/root.h
Normal file
@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "../../events/draw.h"
|
||||
#include "component.h"
|
||||
#include "controller.h"
|
||||
|
||||
namespace UiModule {
|
||||
class RootModule : public Core::ModuleBase {
|
||||
public:
|
||||
RootModule();
|
||||
virtual ~RootModule() override;
|
||||
static RootModule* GetInstance();
|
||||
bool Attach();
|
||||
void Detach();
|
||||
|
||||
template<typename ComponentT, typename... Args>
|
||||
ComponentT* AddComponent(Args&&... args) {
|
||||
static_assert(std::is_base_of_v<Component, ComponentT>, "ComponentT must be derived from Component");
|
||||
ComponentT* component = new ComponentT(std::forward<Args>(args)...);
|
||||
m_components.emplace_back(std::unique_ptr<Component>(component));
|
||||
return component;
|
||||
}
|
||||
|
||||
template<typename ComponentT>
|
||||
void RemoveComponent(ComponentT* component, bool removeChildren = false) {
|
||||
static_assert(std::is_base_of_v<Component, ComponentT>, "ComponentT must be derived from Component");
|
||||
auto it = std::find_if(m_components.begin(), m_components.end(), [component](const std::unique_ptr<Component>& ptr) {
|
||||
return ptr.get() == component;
|
||||
});
|
||||
if (it == m_components.end()) {
|
||||
spdlog::error("Attempted to remove a component that is not managed by RootModule");
|
||||
return;
|
||||
}
|
||||
|
||||
if (removeChildren) {
|
||||
std::vector<Component*> children = component->GetChildren();
|
||||
for (Component* child : children) {
|
||||
RemoveComponent(child, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (it->get() != component) {
|
||||
it = std::find_if(m_components.begin(), m_components.end(), [component](const std::unique_ptr<Component>& ptr) {
|
||||
return ptr.get() == component;
|
||||
});
|
||||
}
|
||||
|
||||
if (it != m_components.end()) {
|
||||
m_components.erase(it);
|
||||
}
|
||||
else {
|
||||
spdlog::error("Attempted to remove a component that, after removing children, is not managed by RootModule anymore");
|
||||
}
|
||||
}
|
||||
|
||||
void ClearComponents() {
|
||||
while (!m_components.empty()) {
|
||||
m_components.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename ControllerT, typename... Args>
|
||||
ControllerT* AddController(Args&&... args) {
|
||||
static_assert(std::is_base_of_v<Controller, ControllerT>, "ControllerT must be derived from Controller");
|
||||
ControllerT* controller = new ControllerT(std::forward<Args>(args)...);
|
||||
m_controllers.emplace_back(std::unique_ptr<Controller>(controller));
|
||||
return controller;
|
||||
}
|
||||
|
||||
template<typename ControllerT>
|
||||
void RemoveController(ControllerT* controller) {
|
||||
static_assert(std::is_base_of_v<Controller, ControllerT>, "ControllerT must be derived from Controller");
|
||||
auto it = std::find_if(m_controllers.begin(), m_controllers.end(), [controller](const std::unique_ptr<Controller>& ptr) {
|
||||
return ptr.get() == controller;
|
||||
});
|
||||
if (it != m_controllers.end()) {
|
||||
m_controllers.erase(it);
|
||||
}
|
||||
else {
|
||||
spdlog::error("Attempted to remove a controller that is not managed by RootModule");
|
||||
}
|
||||
}
|
||||
|
||||
void ClearControllers() {
|
||||
while (!m_controllers.empty()) {
|
||||
m_controllers.erase(m_controllers.begin());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void OnDraw(const DrawUIEvent& event);
|
||||
|
||||
std::vector<std::unique_ptr<Component>> m_components = {};
|
||||
std::vector<std::unique_ptr<Controller>> m_controllers = {};
|
||||
static RootModule* m_instance;
|
||||
};
|
||||
}
|
||||
9
src/modules/ui/ui.h
Normal file
9
src/modules/ui/ui.h
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
#include "rect.h"
|
||||
#include "root.h"
|
||||
#include "component.h"
|
||||
#include "components/components.h"
|
||||
#include "controller.h"
|
||||
#include "controllers/controllers.h"
|
||||
84
src/modules/ui/utils/string-utils.h
Normal file
84
src/modules/ui/utils/string-utils.h
Normal file
@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
#include "../common.h"
|
||||
|
||||
template <typename T>
|
||||
inline constexpr bool always_false = false;
|
||||
|
||||
namespace UiModule {
|
||||
// Removes trailing zeros from a floating-point string representation.
|
||||
inline void RemoveTrailingZeros(std::wstring& str) {
|
||||
if (str.empty()) return;
|
||||
auto dotPos = str.find(L'.');
|
||||
if (dotPos == std::wstring::npos) return;
|
||||
while (!str.empty() && str.back() == L'0') {
|
||||
str.pop_back();
|
||||
}
|
||||
if (!str.empty() && str.back() == L'.') {
|
||||
str.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
// Converts various types to their string representation.
|
||||
template <typename T>
|
||||
inline std::wstring ToString(T value) {
|
||||
std::fesetround(FE_TONEAREST);
|
||||
if constexpr (std::is_same_v<T, Game::SCR_f>) {
|
||||
return ToString(static_cast<float>(value) / 16384.0f);
|
||||
}
|
||||
|
||||
return std::to_wstring(value);
|
||||
}
|
||||
|
||||
// Converts a string representation back to the specified type.
|
||||
template <typename T>
|
||||
inline T FromString(const std::wstring& str) {
|
||||
using BaseT = std::remove_cv_t<T>;
|
||||
if constexpr (std::is_same_v<BaseT, std::wstring>) {
|
||||
return str;
|
||||
}
|
||||
if constexpr (std::is_floating_point_v<BaseT>) {
|
||||
return static_cast<BaseT>(std::stof(str));
|
||||
}
|
||||
if constexpr (std::is_integral_v<BaseT>) {
|
||||
return static_cast<BaseT>(std::stoll(str));
|
||||
}
|
||||
static_assert(always_false<T>, "FromString is not implemented for this type");
|
||||
return T{};
|
||||
}
|
||||
|
||||
// Explicit specialization for SCR_f
|
||||
template <>
|
||||
inline Game::SCR_f FromString<Game::SCR_f>(const std::wstring& str) {
|
||||
float f = std::stof(str);
|
||||
return static_cast<Game::SCR_f>(f * 16384.0f);
|
||||
}
|
||||
|
||||
// Checks if a character is valid for the specified type.
|
||||
template <typename T>
|
||||
inline bool IsValidCharForType(char c, bool allowControlChars = false) {
|
||||
using BaseT = std::remove_cv_t<T>;
|
||||
if constexpr (std::is_same_v<BaseT, std::wstring>) {
|
||||
if (std::isprint(static_cast<unsigned char>(c))) {
|
||||
return true;
|
||||
}
|
||||
if (allowControlChars) {
|
||||
return c == '\t' || c == '\n' || c == '\r';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if constexpr (std::is_floating_point_v<BaseT>) {
|
||||
return std::isdigit(c) || (c == '-') || (c == '.');
|
||||
}
|
||||
if constexpr (std::is_integral_v<BaseT>) {
|
||||
return std::isdigit(c) || (c == '-');
|
||||
}
|
||||
static_assert(always_false<T>, "IsValidCharForType is not implemented for this type. Type name: " __FUNCSIG__);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Explicit specialization for SCR_f
|
||||
template <>
|
||||
inline bool IsValidCharForType<Game::SCR_f>(char c, bool /*allowControlChars*/) {
|
||||
return std::isdigit(c) || (c == '-') || (c == '.');
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user