Upload from upload_mods.ps1

This commit is contained in:
Nathaniel Cosford
2025-06-04 16:13:32 +09:30
commit 7345f42201
470 changed files with 51966 additions and 0 deletions

View File

@@ -0,0 +1,600 @@
using KFCommonUtilityLib.Scripts.ConsoleCmd;
using KFCommonUtilityLib.Scripts.Utilities;
using System;
using System.Collections.Generic;
using UniLinq;
using UnityEngine;
namespace KFCommonUtilityLib.Scripts.StaticManagers
{
//concept: maintain an entityID-AltActionIndice mapping on both server and client
//and get the correct action before calling ItemAction.*
//always set MinEventParams.itemActionData
//done: set meta and ammoindex on switching mode, keep current mode in metadata
//should take care of accuracy updating
//partially done: should support shared meta
//alt actions should be considered primary, redirect index == 0 to custom method
//redirect ItemClass.Actions[0] to custom method
//however, player input handling is redirected to action0 so that alternative module can dispatch it to correct action.
//patch GameManager.updateSendClientPlayerPositionToServer to sync data, so that mode change always happens after holding item change
public struct MultiActionIndice
{
public const int MAX_ACTION_COUNT = 3;
public unsafe fixed sbyte indices[MAX_ACTION_COUNT];
public unsafe fixed sbyte metaIndice[MAX_ACTION_COUNT];
public readonly byte modeCount;
public unsafe MultiActionIndice(ItemClass item)
{
ItemAction[] actions = item.Actions;
indices[0] = 0;
metaIndice[0] = 0;
byte last = 1;
for (sbyte i = 3; i < actions.Length && last < MAX_ACTION_COUNT; i++)
{
if (actions[i] != null)
{
indices[last] = i;
if (actions[i].Properties.Values.TryGetValue("ShareMetaWith", out string str) && sbyte.TryParse(str, out sbyte shareWith))
{
metaIndice[last] = shareWith;
}
else
{
metaIndice[last] = i;
}
last++;
}
}
modeCount = last;
for (; last < MAX_ACTION_COUNT; last++)
{
indices[last] = -1;
metaIndice[last] = -1;
}
}
public unsafe int GetActionIndexForMode(int mode)
{
return indices[mode];
}
public unsafe int GetMetaIndexForMode(int mode)
{
return metaIndice[mode];
}
public unsafe int GetMetaIndexForActionIndex(int actionIndex)
{
return metaIndice[GetModeForAction(actionIndex)];
}
public int GetModeForAction(int actionIndex)
{
int mode = -1;
for (int i = 0; i < MultiActionIndice.MAX_ACTION_COUNT; i++)
{
unsafe
{
if (indices[i] == actionIndex)
{
mode = i;
break;
}
}
}
return mode;
}
}
//MultiActionMapping instance should be changed on ItemAction.StartHolding, so we only need to send curIndex.
public class MultiActionMapping
{
public const string STR_MULTI_ACTION_INDEX = "MultiActionIndex";
public readonly MultiActionIndice indices;
private int slotIndex;
private int curIndex;
private int lastDisplayMode = -1;
private readonly bool[] unlocked;
private ActionModuleAlternative.AlternativeData altData;
public EntityAlive entity;
public string toggleSound;
public ItemValue ItemValue
{
get
{
var res = entity.inventory.GetItem(slotIndex).itemValue;
if (res.IsEmpty())
{
return null;
}
return res;
}
}
public int SlotIndex => slotIndex;
/// <summary>
/// when set CurIndex from local input, also set manager to dirty to update the index on other clients
/// </summary>
public int CurMode
{
get => curIndex;
set
{
unsafe
{
if (value < 0)
value = 0;
else
{
while (value < MultiActionIndice.MAX_ACTION_COUNT)
{
if (unlocked[value])
break;
value++;
}
//mostly for CurIndex++, cycle through available indices
if (value >= MultiActionIndice.MAX_ACTION_COUNT || indices.indices[value] == -1)
value = 0;
}
if (curIndex == value)
return;
SaveMeta();
//load current meta and ammo index from metadata
curIndex = value;
ReadMeta();
entity.emodel?.avatarController?.UpdateInt(MultiActionUtils.ExecutingActionIndexHash, CurActionIndex, false);
//altData.OverrideMuzzleTransform(curIndex);
}
}
}
//for ItemClass.Actions access
public int CurActionIndex => indices.GetActionIndexForMode(curIndex);
//for meta saving on mode switch only?
public int CurMetaIndex => indices.GetMetaIndexForMode(curIndex);
public int ModeCount => indices.modeCount;
//mapping object is created on StartHolding
//we set the curIndex field instead of the property, according to following situations:
//1. it's a newly created ItemValue, meta and ammo index belongs to action0, no saving is needed;
//2. it's an existing ItemValue, meta and ammo index is set to its action index, still saving is unnecessary.
internal MultiActionMapping(ActionModuleAlternative.AlternativeData altData, MultiActionIndice indices, EntityAlive entity, ItemValue itemValueTemp, string toggleSound, int slotIndex, bool[] unlocked)
{
this.altData = altData;
this.indices = indices;
this.entity = entity;
this.slotIndex = slotIndex;
this.unlocked = unlocked;
object res = itemValueTemp.GetMetadata(STR_MULTI_ACTION_INDEX);
if (res is false || res is null)
{
itemValueTemp.SetMetadata(STR_MULTI_ACTION_INDEX, 0, TypedMetadataValue.TypeTag.Integer);
curIndex = 0;
}
else
{
curIndex = (int)res;
ReadMeta();
}
unsafe
{
for (int i = 0; i < MultiActionIndice.MAX_ACTION_COUNT; i++)
{
int metaIndex = indices.metaIndice[i];
if (metaIndex < 0)
break;
if (!itemValueTemp.HasMetadata(MultiActionUtils.ActionMetaNames[metaIndex]))
{
itemValueTemp.SetMetadata(MultiActionUtils.ActionMetaNames[metaIndex], 0, TypedMetadataValue.TypeTag.Integer);
}
#if DEBUG
else
{
Log.Out($"{MultiActionUtils.ActionMetaNames[metaIndex]}: {itemValueTemp.GetMetadata(MultiActionUtils.ActionMetaNames[metaIndex]).ToString()}");
}
#endif
if (!itemValueTemp.HasMetadata(MultiActionUtils.ActionSelectedAmmoNames[metaIndex]))
{
itemValueTemp.SetMetadata(MultiActionUtils.ActionSelectedAmmoNames[metaIndex], 0, TypedMetadataValue.TypeTag.Integer);
}
#if DEBUG
else
{
Log.Out($"{MultiActionUtils.ActionSelectedAmmoNames[metaIndex]}: {itemValueTemp.GetMetadata(MultiActionUtils.ActionSelectedAmmoNames[metaIndex]).ToString()}");
}
#endif
}
}
this.toggleSound = toggleSound;
entity.emodel?.avatarController?.UpdateInt(MultiActionUtils.ExecutingActionIndexHash, CurActionIndex, false);
#if DEBUG
Log.Out($"MultiAction mode {curIndex}, meta {itemValueTemp.Meta}, ammo index {itemValueTemp.SelectedAmmoTypeIndex}\n {StackTraceUtility.ExtractStackTrace()}");
#endif
}
public void SaveMeta(ItemValue _itemValue = null)
{
//save previous meta and ammo index to metadata
int curMetaIndex = CurMetaIndex;
ItemValue itemValue = _itemValue ?? ItemValue;
if (itemValue == null)
return;
ItemAction[] actions = itemValue.ItemClass.Actions;
if (CurActionIndex < 0 || CurActionIndex >= actions.Length)
return;
ItemActionAttack itemActionAttack = actions[CurActionIndex] as ItemActionAttack;
if (itemActionAttack == null)
return;
if (ConsoleCmdReloadLog.LogInfo)
{
Log.Out($"Saving meta for item {itemValue.ItemClass.Name}");
}
itemValue.SetMetadata(MultiActionUtils.ActionMetaNames[curMetaIndex], itemValue.Meta, TypedMetadataValue.TypeTag.Integer);
itemValue.SetMetadata(MultiActionUtils.ActionSelectedAmmoNames[curMetaIndex], (int)itemValue.SelectedAmmoTypeIndex, TypedMetadataValue.TypeTag.Integer);
if (itemValue.SelectedAmmoTypeIndex > itemActionAttack.MagazineItemNames.Length)
{
Log.Error($"SAVING META ERROR: AMMO INDEX LARGER THAN AMMO ITEM COUNT!\n{StackTraceUtility.ExtractStackTrace()}");
}
if (ConsoleCmdReloadLog.LogInfo)
{
ConsoleCmdMultiActionItemValueDebug.LogMeta(itemValue);
Log.Out($"Save Meta stacktrace:\n{StackTraceUtility.ExtractStackTrace()}");
}
}
public void ReadMeta(ItemValue _itemValue = null)
{
int curMetaIndex = CurMetaIndex;
ItemValue itemValue = _itemValue ?? ItemValue;
if (itemValue == null)
return;
itemValue.SetMetadata(STR_MULTI_ACTION_INDEX, curIndex, TypedMetadataValue.TypeTag.Integer);
object res = itemValue.GetMetadata(MultiActionUtils.ActionMetaNames[curMetaIndex]);
if (res is false || res is null)
{
itemValue.SetMetadata(MultiActionUtils.ActionMetaNames[curMetaIndex], 0, TypedMetadataValue.TypeTag.Integer);
itemValue.Meta = 0;
}
else
{
itemValue.Meta = (int)res;
}
res = itemValue.GetMetadata(MultiActionUtils.ActionSelectedAmmoNames[curMetaIndex]);
if (res is false || res is null)
{
itemValue.SetMetadata(MultiActionUtils.ActionSelectedAmmoNames[curMetaIndex], 0, TypedMetadataValue.TypeTag.Integer);
itemValue.SelectedAmmoTypeIndex = 0;
}
else
{
itemValue.SelectedAmmoTypeIndex = (byte)(int)res;
}
if (ConsoleCmdReloadLog.LogInfo)
{
ConsoleCmdMultiActionItemValueDebug.LogMeta(itemValue);
Log.Out($"Read Meta stacktrace:\n{StackTraceUtility.ExtractStackTrace()}");
}
}
public int SetupRadial(XUiC_Radial _xuiRadialWindow, EntityPlayerLocal _epl)
{
_xuiRadialWindow.ResetRadialEntries();
int preSelectedIndex = -1;
string[] magazineItemNames = ((ItemActionAttack)_epl.inventory.holdingItem.Actions[CurActionIndex]).MagazineItemNames;
bool[] disableStates = CommonUtilityPatch.GetUnusableItemEntries(magazineItemNames, _epl, CurActionIndex);
for (int i = 0; i < magazineItemNames.Length; i++)
{
ItemClass ammoClass = ItemClass.GetItemClass(magazineItemNames[i], false);
if (ammoClass != null && (!_epl.isHeadUnderwater || ammoClass.UsableUnderwater) && !disableStates[i])
{
int ammoCount = _xuiRadialWindow.xui.PlayerInventory.GetItemCount(ammoClass.Id);
bool isCurrentUsing = _epl.inventory.holdingItemItemValue.SelectedAmmoTypeIndex == i;
_xuiRadialWindow.CreateRadialEntry(i, ammoClass.GetIconName(), (ammoCount > 0) ? "ItemIconAtlas" : "ItemIconAtlasGreyscale", ammoCount.ToString(), ammoClass.GetLocalizedItemName(), isCurrentUsing);
if (isCurrentUsing)
{
preSelectedIndex = i;
}
}
}
return preSelectedIndex;
}
public bool CheckDisplayMode()
{
if (lastDisplayMode == CurMode)
{
return false;
}
else
{
lastDisplayMode = CurMode;
return true;
}
}
public bool IsActionUnlocked(int actionIndex)
{
if (actionIndex >= MultiActionIndice.MAX_ACTION_COUNT || actionIndex < 0)
return false;
return unlocked[actionIndex];
}
}
public static class MultiActionManager
{
//clear on game load
private static readonly Dictionary<int, MultiActionMapping> dict_mappings = new Dictionary<int, MultiActionMapping>();
private static readonly Dictionary<int, MultiActionIndice> dict_indice = new Dictionary<int, MultiActionIndice>();
private static readonly Dictionary<int, FastTags<TagGroup.Global>[]> dict_item_action_exclude_tags = new Dictionary<int, FastTags<TagGroup.Global>[]>();
private static readonly Dictionary<int, int[][]> dict_item_action_exclude_mod_property = new Dictionary<int, int[][]>();
private static readonly Dictionary<int, int[][]> dict_item_action_exclude_mod_passive = new Dictionary<int, int[][]>();
private static readonly Dictionary<int, int[][]> dict_item_action_exclude_mod_trigger = new Dictionary<int, int[][]>();
//should set to true when:
//mode switch input received;
//start holding new multi action weapon.?
//if true, send local curIndex to other clients in updateSendClientPlayerPositionToServer.
public static bool LocalModeChanged { get; set; }
public static void PostloadCleanup()
{
dict_mappings.Clear();
dict_indice.Clear();
}
public static void PreloadCleanup()
{
dict_item_action_exclude_tags.Clear();
dict_item_action_exclude_mod_property.Clear();
dict_item_action_exclude_mod_passive.Clear();
dict_item_action_exclude_mod_trigger.Clear();
}
public static void ParseItemActionExcludeTagsAndModifiers(ItemClass item)
{
if (item == null)
return;
FastTags<TagGroup.Global>[] tags = null;
int[][] properties = null, passives = null, triggers = null;
for (int i = 0; i < item.Actions.Length; i++)
{
if (item.Actions[i] != null)
{
if (item.Actions[i].Properties.Values.TryGetValue("ExcludeTags", out string str))
{
if (tags == null)
{
tags = new FastTags<TagGroup.Global>[ItemClass.cMaxActionNames];
dict_item_action_exclude_tags.Add(item.Id, tags);
}
tags[i] = FastTags<TagGroup.Global>.Parse(str);
}
if (item.Actions[i].Properties.Values.TryGetValue("ExcludeMods", out str))
{
if (properties == null)
{
properties = new int[ItemClass.cMaxActionNames][];
dict_item_action_exclude_mod_property.Add(item.Id, properties);
}
properties[i] = str.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Where(s => !string.IsNullOrEmpty(s))
.Select(s => ItemClass.GetItemClass(s, false))
.Where(_item => _item != null)
.Select(_item => _item.Id)
.ToArray();
//Log.Out($"EXCLUDE PROPERTIES FROM ITEM {item.Name} ITEMID {item.Id} ACTION {i} : {string.Join(" ", properties[i])}");
}
if (item.Actions[i].Properties.Values.TryGetValue("ExcludePassives", out str))
{
if (passives == null)
{
passives = new int[ItemClass.cMaxActionNames][];
dict_item_action_exclude_mod_passive.Add(item.Id, passives);
}
passives[i] = str.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Where(s => !string.IsNullOrEmpty(s))
.Select(s => ItemClass.GetItemClass(s, false))
.Where(_item => _item != null)
.Select(_item => _item.Id)
.ToArray();
//Log.Out($"EXCLUDE PASSIVES FROM ITEM {item.Name} ITEMID {item.Id} ACTION {i} : {string.Join(" ", passives[i])}");
}
if (item.Actions[i].Properties.Values.TryGetValue("ExcludeTriggers", out str))
{
if (triggers == null)
{
triggers = new int[ItemClass.cMaxActionNames][];
dict_item_action_exclude_mod_trigger.Add(item.Id, triggers);
}
triggers[i] = str.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Where(s => !string.IsNullOrEmpty(s))
.Select(s => ItemClass.GetItemClass(s, false))
.Where(_item => _item != null)
.Select(_item => _item.Id)
.ToArray();
//Log.Out($"EXCLUDE TRIGGERS FROM ITEM {item.Name} ITEMID {item.Id} ACTION {i} : {string.Join(" ", triggers[i])}");
}
}
}
}
public static void ModifyItemTags(ItemValue itemValue, ItemActionData actionData, ref FastTags<TagGroup.Global> tags)
{
if (itemValue == null || actionData == null || !dict_item_action_exclude_tags.TryGetValue(itemValue.type, out var arr_tags))
{
return;
}
tags = tags.Remove(arr_tags[actionData.indexInEntityOfAction]);
}
public static bool ShouldExcludeProperty(int itemId, int modId, int actionIndex)
{
return dict_item_action_exclude_mod_property.TryGetValue(itemId, out var arr_exclude) && arr_exclude[actionIndex] != null && Array.IndexOf(arr_exclude[actionIndex], modId) >= 0;
}
public static bool ShouldExcludePassive(int itemId, int modId, int actionIndex)
{
return dict_item_action_exclude_mod_passive.TryGetValue(itemId, out var arr_exclude) && arr_exclude[actionIndex] != null && Array.IndexOf(arr_exclude[actionIndex], modId) >= 0;
}
public static bool ShouldExcludeTrigger(int itemId, int modId, int actionIndex)
{
return dict_item_action_exclude_mod_trigger.TryGetValue(itemId, out var arr_exclude) && arr_exclude[actionIndex] != null && Array.IndexOf(arr_exclude[actionIndex], modId) >= 0;
}
public static void UpdateLocalMetaSave(int playerID)
{
if (dict_mappings.TryGetValue(playerID, out MultiActionMapping mapping))
{
mapping?.SaveMeta();
}
}
public static MultiActionIndice GetActionIndiceForItemID(int itemID)
{
if (dict_indice.TryGetValue(itemID, out MultiActionIndice indice))
return indice;
ItemClass item = ItemClass.GetForId(itemID);
if (item == null)
return indice;
indice = new MultiActionIndice(item);
dict_indice[itemID] = indice;
return indice;
}
public static void ToggleLocalActionIndex(EntityPlayerLocal player)
{
if (player == null || !dict_mappings.TryGetValue(player.entityId, out MultiActionMapping mapping))
return;
if (mapping.ModeCount <= 1 || player.inventory.IsHoldingItemActionRunning())
return;
int prevMode = mapping.CurMode;
mapping.CurMode++;
if (prevMode != mapping.CurMode)
{
FireToggleModeEvent(player, mapping);
player.inventory.CallOnToolbeltChangedInternal();
}
}
public static void FireToggleModeEvent(EntityPlayerLocal player, MultiActionMapping mapping)
{
player.PlayOneShot(mapping.toggleSound);
LocalModeChanged = true;
player.MinEventContext.ItemActionData = player.inventory.holdingItemData.actionData[mapping.CurActionIndex];
player.FireEvent(CustomEnums.onSelfItemSwitchMode);
}
public static void SetMappingForEntity(int entityID, MultiActionMapping mapping)
{
dict_mappings[entityID] = mapping;
//Log.Out($"current item index mapping: {((mapping == null || mapping.itemValue == null) ? "null" : mapping.itemValue.ItemClass.Name)}");
}
public static bool SetModeForEntity(int entityID, int mode)
{
if (dict_mappings.TryGetValue(entityID, out MultiActionMapping mapping) && mapping != null)
{
int prevMode = mapping.CurMode;
mapping.CurMode = mode;
return prevMode != mapping.CurMode;
}
return false;
}
public static int GetModeForEntity(int entityID)
{
if (!dict_mappings.TryGetValue(entityID, out MultiActionMapping mapping) || mapping == null)
return 0;
return mapping.CurMode;
}
public static int GetActionIndexForEntity(EntityAlive entity)
{
if (entity == null || !dict_mappings.TryGetValue(entity.entityId, out var mapping) || mapping == null)
return 0;
return mapping.CurActionIndex;
}
public static int GetMetaIndexForEntity(int entityID)
{
if (!dict_mappings.TryGetValue(entityID, out var mapping) || mapping == null)
return 0;
return mapping.CurMetaIndex;
}
public static int GetMetaIndexForActionIndex(int entityID, int actionIndex)
{
if (!dict_mappings.TryGetValue(entityID, out var mapping) || mapping == null)
{
return actionIndex;
}
int mode = mapping.indices.GetModeForAction(actionIndex);
if (mode > 0)
{
unsafe
{
return mapping.indices.metaIndice[mode];
}
}
return actionIndex;
}
public static MultiActionMapping GetMappingForEntity(int entityID)
{
dict_mappings.TryGetValue(entityID, out var mapping);
return mapping;
}
internal static float inputCD = 0;
internal static void UpdateLocalInput(EntityPlayerLocal player, PlayerActionsLocal localActions, bool isUIOpen, float _dt)
{
if (inputCD > 0)
{
inputCD = Math.Max(0, inputCD - _dt);
}
if (isUIOpen || inputCD > 0 || player.emodel.IsRagdollActive || player.IsDead() || player.AttachedToEntity != null)
{
return;
}
if (PlayerActionKFLib.Instance.ToggleActionMode && PlayerActionKFLib.Instance.ToggleActionMode.WasPressed)
{
var mapping = GetMappingForEntity(player.entityId);
if (mapping == null)
{
return;
}
if (player.inventory.IsHoldingItemActionRunning())
{
return;
}
if (localActions.Reload.WasPressed || localActions.PermanentActions.Reload.WasPressed)
{
inputCD = 0.1f;
return;
}
player.inventory.Execute(mapping.CurActionIndex, true, localActions);
localActions.Primary.ClearInputState();
ToggleLocalActionIndex(player);
}
}
}
}