Upload from upload_mods.ps1

This commit is contained in:
Nathaniel Cosford
2025-06-04 16:16:46 +09:30
commit 78abcd79dd
20 changed files with 1000 additions and 0 deletions

80
.gitignore vendored Normal file
View File

@@ -0,0 +1,80 @@
# Build and Object Folders
bin/
obj/
# Nuget packages directory
packages/
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
x64/
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.log
*.vspscc
*.vssscc
.builds
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*
# NCrunch
*.ncrunch*
.*crunch*.local.xml
# Installshield output folder
[Ee]xpress
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help
UpgradeLog*.XML
# Lightswitch
_Pvt_Extensions
GeneratedArtifacts
*.xap
ModelManifest.xml
#Backup file
*.bak
#zzzili
v15/
ActionSetSaves.pref

View File

@@ -0,0 +1 @@
{"RootPath":"E:\\SteamLibrary\\steamapps\\common\\7 Days To Die\\Mods\\CustomPlayerActionManager","ProjectFileName":"CustomPlayerActionManager.csproj","Configuration":"Release|AnyCPU","FrameworkPath":"","Sources":[{"SourceFile":"Harmony\\Init.cs"},{"SourceFile":"Harmony\\Patches.cs"},{"SourceFile":"Properties\\AssemblyInfo.cs"},{"SourceFile":"Scripts\\Core\\CustomPlayerActionBase.cs"},{"SourceFile":"Scripts\\Core\\CustomPlayerActionManager.cs"},{"SourceFile":"Scripts\\Utilities\\ActionSetUserDataExtension.cs"},{"SourceFile":"obj\\Release\\.NETFramework,Version=v4.8.AssemblyAttributes.cs"}],"References":[{"Reference":"E:\\SteamLibrary\\steamapps\\common\\7 Days To Die\\Mods\\0_TFP_Harmony\\0Harmony.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"E:\\SteamLibrary\\steamapps\\common\\7 Days To Die\\7DaysToDie_Data\\Managed\\Assembly-CSharp.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"E:\\SteamLibrary\\steamapps\\common\\7 Days To Die\\7DaysToDie_Data\\Managed\\InControl.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"E:\\SteamLibrary\\steamapps\\common\\7 Days To Die\\7DaysToDie_Data\\Managed\\LogLibrary.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.8\\Microsoft.CSharp.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.8\\mscorlib.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.8\\System.Core.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.8\\System.Data.DataSetExtensions.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.8\\System.Data.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.8\\System.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.8\\System.Net.Http.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.8\\System.Xml.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.8\\System.Xml.Linq.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""}],"Analyzers":[],"Outputs":[{"OutputItemFullPath":"E:\\SteamLibrary\\steamapps\\common\\7 Days To Die\\Mods\\CustomPlayerActionManager\\CustomPlayerActionManager.dll","OutputItemRelativePath":"CustomPlayerActionManager.dll"},{"OutputItemFullPath":"E:\\SteamLibrary\\steamapps\\common\\7 Days To Die\\Mods\\CustomPlayerActionManager\\CustomPlayerActionManager.pdb","OutputItemRelativePath":"CustomPlayerActionManager.pdb"}],"CopyToOutputEntries":[]}

View File

@@ -0,0 +1,18 @@
<configs name="XUi_Menu/controls.xml">
<!-- <append xpath="/controls">
<custom_option_control_page>
<rect>
<grid name="controlsGrid" pos="8,-40" cols="2" rows="{keybindingEntryCount}" cell_width="700" cell_height="45" repeat_content="true" arrangement="horizontal" >
<keyboard_binding_entry name="bindingEntry"/>
</grid>
<scrollbar name="bar" width="20" height="550" pos="15,-320" pivot="center" foregroundname="handler" backgroundname="background" depth="10">
<button name="handler" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[white]" color="[white]" defaultcolor="[white]" hoverscale="1" />
<button name="background" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[black]" color="[black]" defaultcolor="[black]" hoverscale="1" />
</scrollbar>
<scrollview scroll_speed="0" scrollbar="bar" pos="0,-40" width="1408" height="550">
<grid name="controlsGrid" pos="8,-40" cols="2" cell_width="700" cell_height="45" repeat_content="false" arrangement="horizontal" />
</scrollview>
</rect>
</custom_option_control_page>
</append> -->
</configs>

120
Config/XUi_Menu/windows.xml Normal file
View File

@@ -0,0 +1,120 @@
<configs name="XUi_Menu/windows.xml">
<!-- <setattribute xpath="/windows/window[@name='optionsControls']/rect[@name='tabs']/rect[@name='controlTemplate']" name="depth">3</setattribute> -->
<setattribute xpath="/windows/window[@name='optionsControls']" name="controller">XUiC_OptionsControlsCLS,CustomPlayerActionManager</setattribute>
<remove xpath="/windows/window[@name='optionsControls']/rect[@name='tabs']/rect[@name='tabsContents']/rect[not(@*)]"/>
<insertAfter xpath="/windows/window[@name='optionsControls']/rect[@name='tabs']/rect[@name='tabsContents']/rect[@tab_key='xuiOptionsControlsGeneric']">
<rect>
<scrollbar name="bar" width="20" height="550" pos="15,-320" pivot="center" foregroundname="handler" backgroundname="background" depth="10">
<button name="handler" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[white]" color="[white]" defaultcolor="[white]" hoverscale="1" />
<button name="background" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[black]" color="[black]" defaultcolor="[black]" hoverscale="1" />
</scrollbar>
<scrollview scroll_speed="0" scrollbar="bar" pos="0,-40" width="1408" height="550">
<grid name="controlsGrid" pos="8,-40" cols="2" rows="{keybindingEntryCount0}" cell_width="700" cell_height="45" repeat_content="true" arrangement="horizontal" >
<keyboard_binding_entry name="bindingEntry"/>
</grid>
</scrollview>
</rect>
<rect>
<scrollbar name="bar" width="20" height="550" pos="15,-320" pivot="center" foregroundname="handler" backgroundname="background" depth="10">
<button name="handler" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[white]" color="[white]" defaultcolor="[white]" hoverscale="1" />
<button name="background" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[black]" color="[black]" defaultcolor="[black]" hoverscale="1" />
</scrollbar>
<scrollview scroll_speed="0" scrollbar="bar" pos="0,-40" width="1408" height="550">
<grid name="controlsGrid" pos="8,-40" cols="2" rows="{keybindingEntryCount1}" cell_width="700" cell_height="45" repeat_content="true" arrangement="horizontal" >
<keyboard_binding_entry name="bindingEntry"/>
</grid>
</scrollview>
</rect>
<rect>
<scrollbar name="bar" width="20" height="550" pos="15,-320" pivot="center" foregroundname="handler" backgroundname="background" depth="10">
<button name="handler" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[white]" color="[white]" defaultcolor="[white]" hoverscale="1" />
<button name="background" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[black]" color="[black]" defaultcolor="[black]" hoverscale="1" />
</scrollbar>
<scrollview scroll_speed="0" scrollbar="bar" pos="0,-40" width="1408" height="550">
<grid name="controlsGrid" pos="8,-40" cols="2" rows="{keybindingEntryCount2}" cell_width="700" cell_height="45" repeat_content="true" arrangement="horizontal" >
<keyboard_binding_entry name="bindingEntry"/>
</grid>
</scrollview>
</rect>
<rect>
<scrollbar name="bar" width="20" height="550" pos="15,-320" pivot="center" foregroundname="handler" backgroundname="background" depth="10">
<button name="handler" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[white]" color="[white]" defaultcolor="[white]" hoverscale="1" />
<button name="background" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[black]" color="[black]" defaultcolor="[black]" hoverscale="1" />
</scrollbar>
<scrollview scroll_speed="0" scrollbar="bar" pos="0,-40" width="1408" height="550">
<grid name="controlsGrid" pos="8,-40" cols="2" rows="{keybindingEntryCount3}" cell_width="700" cell_height="45" repeat_content="true" arrangement="horizontal" >
<keyboard_binding_entry name="bindingEntry"/>
</grid>
</scrollview>
</rect>
<rect>
<scrollbar name="bar" width="20" height="550" pos="15,-320" pivot="center" foregroundname="handler" backgroundname="background" depth="10">
<button name="handler" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[white]" color="[white]" defaultcolor="[white]" hoverscale="1" />
<button name="background" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[black]" color="[black]" defaultcolor="[black]" hoverscale="1" />
</scrollbar>
<scrollview scroll_speed="0" scrollbar="bar" pos="0,-40" width="1408" height="550">
<grid name="controlsGrid" pos="8,-40" cols="2" rows="{keybindingEntryCount4}" cell_width="700" cell_height="45" repeat_content="true" arrangement="horizontal" >
<keyboard_binding_entry name="bindingEntry"/>
</grid>
</scrollview>
</rect>
<rect>
<scrollbar name="bar" width="20" height="550" pos="15,-320" pivot="center" foregroundname="handler" backgroundname="background" depth="10">
<button name="handler" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[white]" color="[white]" defaultcolor="[white]" hoverscale="1" />
<button name="background" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[black]" color="[black]" defaultcolor="[black]" hoverscale="1" />
</scrollbar>
<scrollview scroll_speed="0" scrollbar="bar" pos="0,-40" width="1408" height="550">
<grid name="controlsGrid" pos="8,-40" cols="2" rows="{keybindingEntryCount5}" cell_width="700" cell_height="45" repeat_content="true" arrangement="horizontal" >
<keyboard_binding_entry name="bindingEntry"/>
</grid>
</scrollview>
</rect>
<rect>
<scrollbar name="bar" width="20" height="550" pos="15,-320" pivot="center" foregroundname="handler" backgroundname="background" depth="10">
<button name="handler" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[white]" color="[white]" defaultcolor="[white]" hoverscale="1" />
<button name="background" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[black]" color="[black]" defaultcolor="[black]" hoverscale="1" />
</scrollbar>
<scrollview scroll_speed="0" scrollbar="bar" pos="0,-40" width="1408" height="550">
<grid name="controlsGrid" pos="8,-40" cols="2" rows="{keybindingEntryCount6}" cell_width="700" cell_height="45" repeat_content="true" arrangement="horizontal" >
<keyboard_binding_entry name="bindingEntry"/>
</grid>
</scrollview>
</rect>
<rect>
<scrollbar name="bar" width="20" height="550" pos="15,-320" pivot="center" foregroundname="handler" backgroundname="background" depth="10">
<button name="handler" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[white]" color="[white]" defaultcolor="[white]" hoverscale="1" />
<button name="background" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[black]" color="[black]" defaultcolor="[black]" hoverscale="1" />
</scrollbar>
<scrollview scroll_speed="0" scrollbar="bar" pos="0,-40" width="1408" height="550">
<grid name="controlsGrid" pos="8,-40" cols="2" rows="{keybindingEntryCount7}" cell_width="700" cell_height="45" repeat_content="true" arrangement="horizontal" >
<keyboard_binding_entry name="bindingEntry"/>
</grid>
</scrollview>
</rect>
</insertAfter>
<remove xpath="/windows/window[@name='optionsController']/rect[@name='tabs']/rect[@name='tabsContents']/rect[not(@*)]"/>
<insertAfter xpath="/windows/window[@name='optionsController']/rect[@name='tabs']/rect[@name='tabsContents']/rect[@tab_key='xuiOptionsControlsGeneric']">
<rect>
<scrollbar name="bar" width="20" height="550" pos="15,-320" pivot="center" foregroundname="handler" backgroundname="background" depth="10">
<button name="handler" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[white]" color="[white]" defaultcolor="[white]" hoverscale="1" />
<button name="background" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[black]" color="[black]" defaultcolor="[black]" hoverscale="1" />
</scrollbar>
<scrollview scroll_speed="0" scrollbar="bar" pos="0,-40" width="1408" height="550">
<grid name="controlsGrid" pos="8,-40" cols="2" cell_width="600" cell_height="66" rows="{keybindingEntryCount0}" repeat_content="true" arrangement="horizontal" >
<controller_binding_entry name="bindingEntry"/>
</grid>
</scrollview>
</rect>
<rect>
<scrollbar name="bar" width="20" height="550" pos="15,-320" pivot="center" foregroundname="handler" backgroundname="background" depth="10">
<button name="handler" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[white]" color="[white]" defaultcolor="[white]" hoverscale="1" />
<button name="background" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[black]" color="[black]" defaultcolor="[black]" hoverscale="1" />
</scrollbar>
<scrollview scroll_speed="0" scrollbar="bar" pos="0,-40" width="1408" height="550">
<grid name="controlsGrid" pos="8,-40" cols="2" cell_width="600" cell_height="66" rows="{keybindingEntryCount1}" repeat_content="true" arrangement="horizontal" >
<controller_binding_entry name="bindingEntry"/>
</grid>
</scrollview>
</rect>
</insertAfter>
</configs>

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A32D3424-8454-4B87-9554-AB0734819328}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>CustomPlayerActionManager</RootNamespace>
<AssemblyName>CustomPlayerActionManager</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<OutputPath>.\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>..\0_TFP_Harmony\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>..\..\7DaysToDie_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="InControl">
<HintPath>..\..\7DaysToDie_Data\Managed\InControl.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="LogLibrary">
<HintPath>..\..\7DaysToDie_Data\Managed\LogLibrary.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Harmony\Init.cs" />
<Compile Include="Harmony\Patches.cs" />
<Compile Include="Harmony\ReversePatches.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scripts\Core\CustomPlayerActionBase.cs" />
<Compile Include="Scripts\Core\CustomPlayerActionManager.cs" />
<Compile Include="Scripts\Utilities\ActionSetUserDataExtension.cs" />
<Compile Include="Scripts\XUi\XUiC_OptionsControlsCLS.cs" />
</ItemGroup>
<ItemGroup>
<None Include="ModInfo.xml" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

Binary file not shown.

Binary file not shown.

17
Harmony/Init.cs Normal file
View File

@@ -0,0 +1,17 @@
using System.Reflection;
public class CustomPlayerActionManagerInit : IModApi
{
private static bool inited = false;
public void InitMod(Mod _modInstance)
{
if(inited)
return;
inited = true;
Log.Out(" Loading Patch: " + GetType());
ModEvents.GameAwake.RegisterHandler(CustomPlayerActionManager.InitCustomControls);
var harmony = new HarmonyLib.Harmony(GetType().ToString());
harmony.PatchAll(Assembly.GetExecutingAssembly());
}
}

141
Harmony/Patches.cs Normal file
View File

@@ -0,0 +1,141 @@
using HarmonyLib;
using System.Collections.Generic;
using System.Reflection.Emit;
[HarmonyPatch]
public class Patches
{
[HarmonyPatch(typeof(BindingInfo), MethodType.Constructor, new[] { typeof(XUiView), typeof(string), typeof(string) })]
[HarmonyPostfix]
private static void Postfix_ctor_BindingInfo(BindingInfo __instance, string _sourceText)
{
if (!string.IsNullOrEmpty(_sourceText) && _sourceText.StartsWith("{keybindingEntryCount"))
{
__instance.RefreshValue(true);
}
}
[HarmonyPatch(typeof(XUiC_OptionsController), nameof(XUiC_OptionsController.GetBindingValue))]
[HarmonyPostfix]
private static void Postfix_GetBindingValue_XUiC_OptionsController(ref string _value, string _bindingName, ref bool __result, XUiC_OptionsController __instance)
{
if (__result)
{
return;
}
if (!string.IsNullOrEmpty(_bindingName) && _bindingName.StartsWith("keybindingEntryCount"))
{
if (CustomPlayerActionManager.arr_row_counts_controller == null)
{
ReversePatches.InitControllerActionList(__instance);
}
int index = int.Parse(_bindingName.Substring(_bindingName.Length - 1));
_value = CustomPlayerActionManager.arr_row_counts_controller[index].ToString();
__result = true;
return;
}
}
[HarmonyPatch(typeof(XUiC_OptionsControls), nameof(XUiC_OptionsControls.createControlsEntries))]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_createControlsEntries_XUiC_OptionsControls(IEnumerable<CodeInstruction> instructions)
{
var codes = new List<CodeInstruction>(instructions);
for(int i = 0; i < codes.Count; ++i)
{
if (codes[i].opcode == OpCodes.Stloc_2)
{
codes.Insert(i, CodeInstruction.Call(typeof(CustomPlayerActionManager), nameof(CustomPlayerActionManager.CreateActionArray)));
break;
}
}
return codes;
}
[HarmonyPatch(typeof(XUiC_OptionsController), nameof(XUiC_OptionsController.createControlsEntries))]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_createControlsEntries_XUiC_OptionsController(IEnumerable<CodeInstruction> instructions)
{
var codes = new List<CodeInstruction>(instructions);
for(int i = 0; i < codes.Count; ++i)
{
if (codes[i].opcode == OpCodes.Stloc_0)
{
codes.InsertRange(i - 1, new[]
{
new CodeInstruction(OpCodes.Ldarg_0),
CodeInstruction.LoadField(typeof(XUiC_OptionsController), nameof(XUiC_OptionsController.actionTabGroups)),
CodeInstruction.Call(typeof(CustomPlayerActionManager), nameof(CustomPlayerActionManager.CreateControllerActions))
});
break;
}
}
return codes;
}
[HarmonyPatch(typeof(XUiC_OptionsController), nameof(XUiC_OptionsController.storeCurrentBindings))]
[HarmonyPostfix]
private static void Postfix_storeCurrentBindings_XUiC_OptionsController(XUiC_OptionsController __instance)
{
CustomPlayerActionManager.StoreCurrentCustomBindings(__instance.actionBindingsOnOpen);
}
[HarmonyPatch(typeof(XUiC_OptionsControls), nameof(XUiC_OptionsControls.storeCurrentBindings))]
[HarmonyPostfix]
private static void Postfix_storeCurrentBindings_XUiC_OptionsControls(XUiC_OptionsControls __instance)
{
CustomPlayerActionManager.StoreCurrentCustomBindings(__instance.actionBindingsOnOpen);
}
[HarmonyPatch(typeof(GameOptionsManager), nameof(GameOptionsManager.SaveControls))]
[HarmonyPostfix]
private static void Postfix_SaveControls_GameOptionsManager()
{
CustomPlayerActionManager.SaveCustomControls();
}
[HarmonyPatch(typeof(GameOptionsManager), nameof(GameOptionsManager.ResetGameOptions))]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_ResetGameOptions_GameOptionsManager(IEnumerable<CodeInstruction> instructions)
{
var codes = new List<CodeInstruction>(instructions);
var mtd_save_controls = AccessTools.Method(typeof(GameOptionsManager), nameof(GameOptionsManager.SaveControls));
for (int i = 0; i < codes.Count; ++i)
{
if (codes[i].Calls(mtd_save_controls))
{
codes.Insert(i + 1, CodeInstruction.Call(typeof(CustomPlayerActionManager), nameof(CustomPlayerActionManager.ResetCustomControls)));
i++;
}
}
return codes;
}
[HarmonyPatch(typeof(ActionSetManager), nameof(ActionSetManager.LogActionSets))]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> Transpiler_LogActionSets_ActionSetManager(IEnumerable<CodeInstruction> instructions)
{
var codes = new List<CodeInstruction>(instructions);
for(int i = codes.Count - 1; i >= 0; --i)
{
if (codes[i].opcode == OpCodes.Ldloc_1)
{
codes.Insert(i + 1, CodeInstruction.Call(typeof(CustomPlayerActionManager), nameof(CustomPlayerActionManager.CreateDebugInfo)));
break;
}
}
return codes;
}
}

138
Harmony/ReversePatches.cs Normal file
View File

@@ -0,0 +1,138 @@
using HarmonyLib;
using InControl;
using System;
using System.Collections.Generic;
using System.Reflection.Emit;
internal static class ReversePatches
{
//[HarmonyPatch(typeof(XUiC_OptionsControls), nameof(XUiC_OptionsControls.createControlsEntries))]
//[HarmonyReversePatch(HarmonyReversePatchType.Snapshot)]
internal static void InitPlayerActionList(XUiC_OptionsControls __instance)
{
//IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
//{
// if (instructions == null)
// {
// yield break;
// }
// yield return CodeInstruction.Call(typeof(CustomPlayerActionManager), nameof(CustomPlayerActionManager.InitCustomControls));
// foreach (var code in instructions)
// {
// if (code.opcode != OpCodes.Stloc_1)
// {
// yield return code;
// }
// else
// {
// yield return new CodeInstruction(OpCodes.Pop);
// yield return new CodeInstruction(OpCodes.Ldloc_0);
// yield return CodeInstruction.Call(typeof(CustomPlayerActionManager), nameof(CustomPlayerActionManager.ResizeGrid));
// yield return new CodeInstruction(OpCodes.Ret);
// break;
// }
// }
//}
//_ = Transpiler(null);
SortedDictionary<PlayerActionData.ActionTab, SortedDictionary<PlayerActionData.ActionGroup, List<PlayerAction>>> sortedDictionary = new SortedDictionary<PlayerActionData.ActionTab, SortedDictionary<PlayerActionData.ActionGroup, List<PlayerAction>>>();
PlayerActionsBase[] array = CustomPlayerActionManager.CreateActionArray(new PlayerActionsBase[5]
{
__instance.xui.playerUI.playerInput,
__instance.xui.playerUI.playerInput.VehicleActions,
__instance.xui.playerUI.playerInput.PermanentActions,
__instance.xui.playerUI.playerInput.GUIActions,
PlayerActionsGlobal.Instance
});
for (int i = 0; i < array.Length; i++)
{
foreach (PlayerAction action in array[i].Actions)
{
if (!(action.UserData is PlayerActionData.ActionUserData actionUserData))
{
continue;
}
switch (actionUserData.appliesToInputType)
{
default:
throw new ArgumentOutOfRangeException();
case PlayerActionData.EAppliesToInputType.KbdMouseOnly:
case PlayerActionData.EAppliesToInputType.Both:
if (!actionUserData.doNotDisplay)
{
SortedDictionary<PlayerActionData.ActionGroup, List<PlayerAction>> sortedDictionary2;
if (sortedDictionary.ContainsKey(actionUserData.actionGroup.actionTab))
{
sortedDictionary2 = sortedDictionary[actionUserData.actionGroup.actionTab];
}
else
{
sortedDictionary2 = new SortedDictionary<PlayerActionData.ActionGroup, List<PlayerAction>>();
sortedDictionary.Add(actionUserData.actionGroup.actionTab, sortedDictionary2);
}
List<PlayerAction> list;
if (sortedDictionary2.ContainsKey(actionUserData.actionGroup))
{
list = sortedDictionary2[actionUserData.actionGroup];
}
else
{
list = new List<PlayerAction>();
sortedDictionary2.Add(actionUserData.actionGroup, list);
}
list.Add(action);
}
break;
case PlayerActionData.EAppliesToInputType.None:
case PlayerActionData.EAppliesToInputType.ControllerOnly:
break;
}
}
}
CustomPlayerActionManager.ResizeGrid(sortedDictionary);
}
internal static void InitControllerActionList(XUiC_OptionsController __instance)
{
PlayerActionsBase[] array = new PlayerActionsBase[]
{
__instance.xui.playerUI.playerInput,
__instance.xui.playerUI.playerInput.VehicleActions
};
Dictionary<string, List<PlayerAction>> dictionary = new Dictionary<string, List<PlayerAction>>();
dictionary.Add("inpTabPlayerOnFoot", new List<PlayerAction>());
PlayerActionsBase[] array2 = array;
for (int i = 0; i < array2.Length; i++)
{
foreach (PlayerAction playerAction in array2[i].ControllerRebindableActions)
{
PlayerActionData.ActionUserData actionUserData = playerAction.UserData as PlayerActionData.ActionUserData;
if (actionUserData != null)
{
if (actionUserData.actionGroup.actionTab.tabNameKey == "inpTabPlayerControl" || actionUserData.actionGroup.actionTab.tabNameKey == "inpTabToolbelt")
{
dictionary["inpTabPlayerOnFoot"].Add(playerAction);
}
else if (dictionary.ContainsKey(actionUserData.actionGroup.actionTab.tabNameKey))
{
dictionary[actionUserData.actionGroup.actionTab.tabNameKey].Add(playerAction);
}
else
{
dictionary.Add(actionUserData.actionGroup.actionTab.tabNameKey, new List<PlayerAction>());
dictionary[actionUserData.actionGroup.actionTab.tabNameKey].Add(playerAction);
}
}
}
}
dictionary["inpTabPlayerOnFoot"].Add(__instance.xui.playerUI.playerInput.PermanentActions.PushToTalk);
CustomPlayerActionManager.CreateControllerActions(dictionary);
CustomPlayerActionManager.ResizeControllerGrid(dictionary);
}
}

9
ModInfo.xml Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xml>
<Name value="CustomPlayerActionManager" />
<DisplayName value="Custom Player Action Manager" />
<Description value="allow modders to add custom bindable player control actions." />
<Author value="closer_ex" />
<Version value="1.1.2" />
</xml>

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 有关程序集的一般信息由以下
// 控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("CustomPlayerActionManager")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("CustomPlayerActionManager")]
[assembly: AssemblyCopyright("Copyright © 2022")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 将 ComVisible 设置为 false 会使此程序集中的类型
//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
//请将此类型的 ComVisible 特性设置为 true。
[assembly: ComVisible(false)]
// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
[assembly: Guid("a32d3424-8454-4b87-9554-ab0734819328")]
// 程序集的版本信息由下列四个值组成:
//
// 主版本
// 次版本
// 生成号
// 修订号
//
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
//通过使用 "*",如下所示:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

40
README.md Normal file
View File

@@ -0,0 +1,40 @@
# Custom Player Action Manager
This readme will give you a brief introduction on how to add rebindable key mappings to 7 days to die. I will assume you have adequate knowledge of C# and know exactly what you are doing and what you need to achieve, since you'll need them to make use of your custom controls anyway.
### How the game handles input
7 days to die manages player input by InControl, with 5 PlayerActionSet and an **ActionSetManager**. Those sets are: **PlayerActionsLocal**, **PlayerActionsGlobal**, **PlayerActionsGUI**, **PlayerActionsPermanent** and **PlayerActionsVehicle**, which are all implementation of another abstraction on PlayerActionSet: **PlayerActionsBase**.
**PlayerActionsBase** handles a lot listening options for you, so you only need to take care of the initialization of your action set. Basically deriving from it and override **CreateActions**, **CreateDefaultJoystickBindings** and **CreateDefaultKeyboardBindings** is all you need for add a set.
A PlayerActionSet consists of a bunch of PlayerAction. You can take above action sets for example on how to create actions. Note that each action comes with an **ActionGroup**, and each action group comes with an **ActionTab**. Vanilla tabs and groups can be found in **PlayerActionData**, you can add your own group with static fields, but adding new tabs will break the control option page header, as the column count is fixed and it's hard to dynamically add and remove with unknown count.
**ActionSetManager** disables all action sets pushed into its stack but the top one. You can manage action set enable states with it if you need exclusive input handling, but since this mostly happens with vehicle and GUI, it's not a necessary part. For example, **PlayerActionsPermanent** is never pushed into it, thus it can be handled with other action sets on the top.
You can add **-debuginput=verbose** to launch arguments to enable console debug info on **ActionSetManager**.
### Why is this mod needed
If a modder wants to add a custom PlayerActionSet, he will need to create it manually at some point after mods loaded, patch XUiC_OptionsControls to insert the actions into the panels, inject into pref save and handle control reset. When this happens in multiple mods, different ways of dealing with things might conflict with each other.
With this mod, you only need to create your actions with default key mapping, and it will get all the other chores done.
It introduces a derived type of **PlayerActionsBase** named **CustomPlayerActionVersionBase**, and searches for all the subclasses in loaded mod main assemblies, creating an instance and keep them in a dictionary.
Since XUiC_OptionsControls create the setting pages only when you open it for the first time, it is possible to insert all the custom action sets into it at once.
Then it saves all mod action sets in a save file, each one with its name and custom version, so that when one set is removed it won't interfere the data loading of other sets.
Moreover, it patches the log method of **ActionSetManager** to include enable states of custom action sets.
### How to make use of it
**VehicleWeapon** makes use of this mod, you can take that for reference.
1. Derive from **CustomPlayerActionVersionBase**, create your PlayerAction there.
2. In the constructor, set its **Name** and **Version**. It's also recommended to add a static Instance field and set the instance in constructor for convenient access.
3. Add conflicts with other action sets when necessary. this is done by `UserData = new PlayerActionData.ActionSetUserData(new PlayerActionsBase[] { other sets });`. Note this won't add your set to the conflict list of other sets, and the created user data is stored in a readonly collection, which does not support dynamic insertion and removal, so I add 2 extension methods to replace the user data of an action set with a new one that contains your set: `AddUniConflict` add the parameter to the caller's user data if it does not already exist, `AddBiConflict` add the param and the caller to each other's user data.
4. Adding conflicts with possibly loaded action sets if necessary. This is done by override `InitActionSetRelations`and call `CustomPlayerActionManager.TryGetCustomActionSetByName`, then add conflict with above extension methods if they exist. Note you should not access other custom action sets in constructor as their initialization order is undefined.
5. Always change the Version number when you remove actions. This will reset the key mapping without loading the save file, and update the save version on next save operation. I may be wrong, but this prevents potential BinaryReader exceptions during parsing obsolete data. This is not required for adding actions.

View File

@@ -0,0 +1,18 @@
using System;
public abstract class CustomPlayerActionVersionBase : PlayerActionsBase
{
public enum ControllerActionType
{
None,
OnFoot,
Vehicle
}
public virtual void InitActionSetRelations()
{
}
public int Version { get; protected set; } = 1;
public virtual ControllerActionType ControllerActionDisplay { get; } = ControllerActionType.None;
}

View File

@@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.IO;
using InControl;
public class CustomPlayerActionManager
{
private static Dictionary<string, CustomPlayerActionVersionBase> dict_action_sets = new Dictionary<string, CustomPlayerActionVersionBase>();
private static readonly string saveName = "ActionSetSaves.pref";
private static string saveFile;
private static bool inited;
internal static int[] arr_row_counts_control, arr_row_counts_controller;
internal static void ResizeGrid(SortedDictionary<PlayerActionData.ActionTab, SortedDictionary<PlayerActionData.ActionGroup, List<PlayerAction>>> sdict_all_action_sets)
{
Log.Out($"Recalculating options controls grid size...");
arr_row_counts_control = new int[sdict_all_action_sets.Count];
int i = 0;
foreach (var pair in sdict_all_action_sets)
{
var dict = pair.Value;
int rowCount = 0;
foreach (var list in dict.Values)
{
rowCount += (list.Count + 1) / 2 + 1;
}
arr_row_counts_control[i++] = rowCount;
Log.Out($"Size of tab {i} rows is set to {rowCount}");
}
}
internal static void ResizeControllerGrid(Dictionary<string, List<PlayerAction>> dictionary)
{
Log.Out($"Recalculating options controller grid size...");
arr_row_counts_controller = new int[dictionary.Count];
int i = 0;
foreach (var pair in dictionary)
{
arr_row_counts_controller[i] = (pair.Value.Count + 1) / 2;
Log.Out($"Size of tab {pair.Key} rows is set to {arr_row_counts_controller[i]}");
i++;
}
}
public static void InitCustomControls()
{
if (GameManager.IsDedicatedServer || inited)
return;
Log.Out("Initiating custom controls...");
InitFolderPath();
LoadCustomActionSets();
LoadCustomControlSaves();
SaveCustomControls();
inited = true;
}
private static void InitFolderPath()
{
saveFile = Path.Combine(GameIO.GetUserGameDataDir(), saveName);
Assembly assembly = Assembly.GetAssembly(typeof(CustomPlayerActionManager));
Mod mod = ModManager.GetModForAssembly(assembly);
string prevSaveFile = mod != null ? Path.Combine(mod.Path, saveName) : null;
if(!File.Exists(prevSaveFile))
{
mod = ModManager.GetMod("CustomPlayerActionManager");
if (mod != null)
{
prevSaveFile = Path.Combine(mod.Path, saveName);
}
}
if (File.Exists(prevSaveFile))
{
if (!File.Exists(saveFile))
{
File.Move(prevSaveFile, saveFile);
}
else
{
File.Delete(prevSaveFile);
}
}
}
private static void LoadCustomActionSets()
{
var assemblies = ModManager.GetLoadedAssemblies();
Type baseType = typeof(CustomPlayerActionVersionBase);
foreach(var assembly in assemblies)
{
var types = assembly.GetTypes();
foreach(var type in types)
{
if(type.IsSubclassOf(baseType))
{
var actionSet = Activator.CreateInstance(type) as CustomPlayerActionVersionBase;
dict_action_sets[actionSet.Name] = actionSet;
Log.Out("Found custom player action set: " + actionSet.Name);
}
}
}
foreach (var pair in dict_action_sets)
pair.Value.InitActionSetRelations();
}
private static void LoadCustomControlSaves()
{
if (!File.Exists(saveFile))
return;
string content = File.ReadAllText(saveFile);
string[] perModData = content.Split(';');
foreach(string data in perModData)
{
if (!LoadSaveData(Convert.FromBase64String(data), out string info))
Log.Warning(info);
}
}
private static bool LoadSaveData(byte[] data, out string info)
{
info = string.Empty;
if(data == null)
{
info = "No savedata to read!";
return false;
}
using (MemoryStream stream = new MemoryStream(data))
{
using (BinaryReader reader = new BinaryReader(stream))
{
string name = reader.ReadString();
int version = reader.ReadInt32();
if(dict_action_sets.TryGetValue(name, out var actionSet))
{
if (version == (actionSet as CustomPlayerActionVersionBase).Version)
actionSet.LoadData(reader.ReadBytes(reader.ReadInt32()));
else
{
info = "Action Set version changed: " + name + ", reset key mapping";
return false;
}
}else
{
info = "Action Set not found: " + name;
return false;
}
}
}
return true;
}
public static void SaveCustomControls()
{
if (dict_action_sets.Count <= 0)
return;
string[] data = new string[dict_action_sets.Count];
int i = 0;
foreach(var pair in dict_action_sets)
{
byte[] result = pair.Value.SaveData();
using (MemoryStream stream = new MemoryStream())
{
using (BinaryWriter writer = new BinaryWriter(stream))
{
writer.Write(pair.Key);
writer.Write(pair.Value.Version);
writer.Write(result.Length);
writer.Write(result);
}
result = stream.ToArray();
}
data[i++] = Convert.ToBase64String(result);
}
string saveData = string.Join(";", data);
File.WriteAllText(saveFile, saveData);
}
public static void ResetCustomControls()
{
foreach(var pair in dict_action_sets)
pair.Value.Reset();
SaveCustomControls();
}
public static PlayerActionsBase[] CreateActionArray(PlayerActionsBase[] origin)
{
Log.Out("Initializing custom option control panel");
List<PlayerActionsBase> result = new List<PlayerActionsBase>();
result.AddRange(origin);
result.AddRange(dict_action_sets.Values);
return result.ToArray();
}
public static void CreateControllerActions(Dictionary<string, List<PlayerAction>> dictionary)
{
foreach (var actionSet in dict_action_sets.Values)
{
switch (actionSet.ControllerActionDisplay)
{
case CustomPlayerActionVersionBase.ControllerActionType.OnFoot:
foreach (var action in actionSet.Actions)
{
dictionary["inpTabPlayerOnFoot"].Add(action);
}
break;
case CustomPlayerActionVersionBase.ControllerActionType.Vehicle:
foreach (var action in actionSet.Actions)
{
dictionary["inpTabVehicle"].Add(action);
}
break;
default:
break;
}
}
}
public static void StoreCurrentCustomBindings(List<string> origin)
{
foreach (var pair in dict_action_sets)
origin.Add(pair.Value.Save());
}
public static string CreateDebugInfo(string origin)
{
foreach(var pair in dict_action_sets)
origin += string.Format("{0} ({1}), ", pair.Value.GetType().Name, pair.Value.Enabled);
return origin;
}
public static bool TryGetCustomActionSetByName(string name, out CustomPlayerActionVersionBase value)
{
return dict_action_sets.TryGetValue(name, out value);
}
}

View File

@@ -0,0 +1,21 @@
using System.Collections.Generic;
public static class ActionSetUserDataExtension
{
public static void AddUniConflict(this PlayerActionsBase self, PlayerActionsBase other)
{
List<PlayerActionsBase> list = new List<PlayerActionsBase>((self.UserData as PlayerActionData.ActionSetUserData).bindingsConflictWithSet);
if(!list.Contains(other))
{
list.Add(other);
self.UserData = new PlayerActionData.ActionSetUserData(list.ToArray());
}
}
public static void AddBiConflict(this PlayerActionsBase self, PlayerActionsBase other)
{
self.AddUniConflict(other);
other.AddUniConflict(self);
}
}

View File

@@ -0,0 +1,21 @@
public class XUiC_OptionsControlsCLS : XUiC_OptionsControls
{
public override bool GetBindingValue(ref string _value, string _bindingName)
{
if (base.GetBindingValue(ref _value, _bindingName))
{
return true;
}
if (!string.IsNullOrEmpty(_bindingName) && _bindingName.StartsWith("keybindingEntryCount"))
{
if (CustomPlayerActionManager.arr_row_counts_control == null)
{
ReversePatches.InitPlayerActionList(this);
}
int index = int.Parse(_bindingName.Substring(_bindingName.Length - 1));
_value = CustomPlayerActionManager.arr_row_counts_control[index].ToString();
return true;
}
return false;
}
}

View File

@@ -0,0 +1,15 @@
<configs name="XUi_Menu/controls.xml">
<append xpath="/controls">
<custom_option_control_page>
<rect>
<scrollbar name="bar" width="20" height="550" pos="15,-320" pivot="center" foregroundname="handler" backgroundname="background" depth="10">
<button name="handler" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[white]" color="[white]" defaultcolor="[white]" hoverscale="1" />
<button name="background" sprite="menu_empty3px" pivot="center" pos="0,0" hovercolor="[black]" color="[black]" defaultcolor="[black]" hoverscale="1" />
</scrollbar>
<scrollview scroll_speed="0" scrollbar="bar" pos="0,-40" width="1408" height="550">
<grid name="controlsGrid" pos="8,-40" cols="2" cell_width="700" cell_height="45" repeat_content="false" arrangement="horizontal" />
</scrollview>
</rect>
</custom_option_control_page>
</append>
</configs>

View File

@@ -0,0 +1,14 @@
<configs name="XUi_Menu/windows.xml">
<setattribute xpath="/windows/window[@name='optionsControls']/rect[@name='tabs']/rect[@name='controlTemplate']" name="depth">3</setattribute>
<remove xpath="/windows/window[@name='optionsControls']/rect[@name='tabs']/rect[@name='tabsContents']/rect[not(@*)]"/>
<insertBefore xpath="/windows/window[@name='optionsControls']/rect[@name='tabs']/rect[@name='tabsContents']/rect[@tab_key='xuiOptionsControlsController']">
<custom_option_control_page/>
<custom_option_control_page/>
<custom_option_control_page/>
<custom_option_control_page/>
<custom_option_control_page/>
<custom_option_control_page/>
<custom_option_control_page/>
<custom_option_control_page/>
</insertBefore>
</configs>