9b35f6b23f
* Update bug-reports.yml * Fix modifyChatMessage hook * Add LuaCsSetup.Lua back for compatibility * Fix Game.AssignOnExecute having command arguments be passed as varargs instead of a table * Actually use the PackageId const everywhere we need to refer to our content package * Load languages files even if the package is disabled * Fix Hook.Remove not being implemented properly * - Changed event aliases to be case insensitive. * - Fixed assembly logging style. - Fixed double logging during execution. * Fix garbage network data being read by the game when reading LuaCs network messages * PackageId -> PackageName * Added caching toggle to PluginManagementService * Fix LuaCs initializing too late for singleplayer campaigns and rework the C# prompt to only show when enabling mods/joining server * Oops, fix NRE crash * Fix hide username in logs config not doing anything * Fix Cs prompt showing up more than one between rounds * Fix server host being prompted twice with the C# popup * Ignore our workshop packages from the game's dependency thing since it doesn't really make sense * Load console commands after executing and possible fix for the not console command permitted * Added fallback friendly name resolution for ModConfig assembly contents. * Register Voronoi2 stuff * Added configinfo null check to SettingBase.cs * Add safety check so this stops crashing when we look at it the wrong way * Fixed "Folder" attribute files not being found. * Keep the LuaCsConfig class laying around for compatibility, not sure anywhere in our code base (and shouldn't be) * Added fallback compilation for UseInternalsAwareAssembly if the publicized script compilation fails. * Added legacy overload of AddCommand for mod compat. * Added LoggerService to Lua env. Made ILoggerService compliant with LuaCsLogger API. * Changed csharp script compilation algorithm to be best effort. * Added "RunUnrestricted" mode for lua scripts that need to run outside of sandbox. * - Fixed networking sync vars failing to sync initially. - Fixed lua failing to differentiate overloads ISettingBase. * Add alias for human.CPRSuccess and human.CPRFailed * - Fixed up the settings menu. - Made SettingEntry throw an error if "Value" attribute is not found in XML. - Fixed saved values for settings sometimes not reloading after disabling and re-enabling a package. * Fix LuaCs net messages received during connection initialization to be read incorrectly, happened because we would reset the BitPosition in our harmony patch which would cause the message to be read incorrectly later * Allow reloadlua to force the state to running * New icon for settings and make the top left text more user friendly * Fix client.packages hook sending normal packages * Fixed OnUpdate() not passing in deltaTime instead of totalTime. * Missing diffs frombb21a09244* Added networking tests for configs. * Added missing diffs forf61f852a25. * Some tweaks to the text * Remove missing Value error, it should just use the default value if it's not specified * Fix UseInternalAccessName * Always purge cashes for plugin content on unloading. * Fix texture not multiple of 4 * v1.12.7.0 (Spring Update 2026 Hotfix 1) --------- Co-authored-by: Joonas Rikkonen <poe.regalis@gmail.com> Co-authored-by: Evil Factory <36804725+evilfactory@users.noreply.github.com> Co-authored-by: MapleWheels <njainanan@hotmail.com>
417 lines
16 KiB
C#
417 lines
16 KiB
C#
using Barotrauma.Items.Components;
|
|
using Barotrauma.LuaCs;
|
|
using Barotrauma.LuaCs.Events;
|
|
using Barotrauma.Networking;
|
|
using Barotrauma.Steam;
|
|
using HarmonyLib;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using static Barotrauma.ContentPackageManager;
|
|
|
|
namespace Barotrauma.LuaCs;
|
|
|
|
[HarmonyPatch]
|
|
internal class HarmonyEventPatchesService : ISystem
|
|
{
|
|
public bool IsDisposed { get; private set; }
|
|
public FluentResults.Result Reset()
|
|
{
|
|
Unpatch();
|
|
Patch();
|
|
return FluentResults.Result.Ok();
|
|
}
|
|
|
|
private static IEventService _eventService;
|
|
private static ILoggerService _loggerService;
|
|
private readonly Harmony Harmony;
|
|
|
|
public HarmonyEventPatchesService(IEventService eventService, ILoggerService loggerService)
|
|
{
|
|
_eventService = eventService;
|
|
_loggerService = loggerService;
|
|
Harmony = new Harmony("LuaCsForBarotrauma.Events");
|
|
Patch();
|
|
}
|
|
|
|
private void Patch()
|
|
{
|
|
this.Harmony?.PatchAll(typeof(HarmonyEventPatchesService));
|
|
#if SERVER
|
|
this.Harmony?.PatchAll(typeof(HarmonyEventPatchesService.Patch_StartGame_End));
|
|
#endif
|
|
}
|
|
|
|
private void Unpatch()
|
|
{
|
|
this.Harmony?.UnpatchSelf();
|
|
}
|
|
|
|
|
|
[HarmonyPatch(typeof(CoroutineManager), nameof(CoroutineManager.Update)), HarmonyPostfix]
|
|
public static void CoroutineManager_Update_Post()
|
|
{
|
|
_eventService.PublishEvent<IEventUpdate>(x => x.OnUpdate(CoroutineManager.DeltaTime));
|
|
_loggerService.ProcessLogs();
|
|
}
|
|
|
|
#if CLIENT
|
|
[HarmonyPatch(typeof(GameSession), nameof(GameSession.StartRound), new Type[]
|
|
{
|
|
typeof(LevelData), typeof(bool), typeof(SubmarineInfo), typeof(SubmarineInfo)
|
|
}), HarmonyPostfix]
|
|
public static void GameSession_StartRound_Post()
|
|
{
|
|
_eventService.PublishEvent<IEventRoundStarted>(x => x.OnRoundStart());
|
|
}
|
|
#endif
|
|
|
|
[HarmonyPatch(typeof(GameSession), nameof(GameSession.EndRound)), HarmonyPrefix]
|
|
public static void GameSession_EndRound_Pre()
|
|
{
|
|
_eventService.PublishEvent<IEventRoundEnded>(x => x.OnRoundEnd());
|
|
}
|
|
|
|
[HarmonyPatch(typeof(GameSession), nameof(GameSession.LoadPreviousSave)), HarmonyPrefix]
|
|
public static void GameSession_LoadPreviousSave_Pre()
|
|
{
|
|
_eventService.PublishEvent<IEventRoundEnded>(x => x.OnRoundEnd());
|
|
}
|
|
|
|
[HarmonyPatch(typeof(GameSession), nameof(GameSession.EndMissions)), HarmonyPostfix]
|
|
public static void GameSession_EndMission_Post(GameSession __instance)
|
|
{
|
|
_eventService.PublishEvent<IEventMissionsEnded>(x => x.OnMissionsEnded(__instance.Missions.ToList()));
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Screen), nameof(Screen.Select)), HarmonyPostfix]
|
|
public static void Screen_Selected_Post(Screen __instance)
|
|
{
|
|
_eventService.PublishEvent<IEventScreenSelected>(x => x.OnScreenSelected(__instance));
|
|
}
|
|
|
|
#if CLIENT
|
|
[HarmonyPatch(typeof(MainMenuScreen), "StartGame"), HarmonyPostfix]
|
|
public static void MainMenuScreen_StartGame_Pre(Screen __instance)
|
|
{
|
|
LuaCsSetup.Instance.SetRunState(RunState.Running);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(MainMenuScreen), "LoadGame"), HarmonyPostfix]
|
|
public static void MainMenuScreen_LoadGame_Pre(Screen __instance)
|
|
{
|
|
LuaCsSetup.Instance.SetRunState(RunState.Running);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(MutableWorkshopMenu), nameof(MutableWorkshopMenu.Apply)), HarmonyPostfix]
|
|
public static void MutableWorkshopMenu_Apply_Post(Screen __instance)
|
|
{
|
|
LuaCsSetup.Instance.PromptCSharpMods(selection => { }, joiningServer: false);
|
|
}
|
|
|
|
#endif
|
|
|
|
[HarmonyPatch(typeof(ContentPackageManager.PackageSource), nameof(ContentPackageManager.PackageSource.Refresh)), HarmonyPostfix]
|
|
public static void PackageSource_Refresh_Post()
|
|
{
|
|
_eventService.PublishEvent<IEventAllPackageListChanged>(x => x.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages));
|
|
}
|
|
|
|
[HarmonyPatch(typeof(ContentPackageManager), nameof(ContentPackageManager.Init)), HarmonyPostfix]
|
|
public static void ContentPackageManager_Init_Post()
|
|
{
|
|
_eventService.PublishEvent<IEventAllPackageListChanged>(x => x.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages));
|
|
_eventService.PublishEvent<IEventEnabledPackageListChanged>(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular));
|
|
}
|
|
|
|
[HarmonyPatch(typeof(ContentPackageManager.EnabledPackages), nameof(ContentPackageManager.EnabledPackages.SetCore)), HarmonyPostfix]
|
|
public static void EnabledPackages_SetCore_Post()
|
|
{
|
|
_eventService.PublishEvent<IEventEnabledPackageListChanged>(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular));
|
|
}
|
|
|
|
[HarmonyPatch(typeof(ContentPackageManager.EnabledPackages), nameof(ContentPackageManager.EnabledPackages.SetRegular)), HarmonyPostfix]
|
|
public static void EnabledPackages_SetRegular_Post()
|
|
{
|
|
_eventService.PublishEvent<IEventEnabledPackageListChanged>(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular));
|
|
}
|
|
|
|
#if CLIENT
|
|
[HarmonyPatch(typeof(GameClient), "ReadDataMessage"), HarmonyPrefix]
|
|
public static bool GameClient_ReadDataMessage_Pre(IReadMessage inc)
|
|
{
|
|
int prevBitPosition = inc.BitPosition;
|
|
ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte();
|
|
bool? skip = null;
|
|
_eventService.PublishEvent<IEventServerRawNetMessageReceived>(x => skip = x.OnReceivedServerNetMessage(inc, header) ?? skip);
|
|
|
|
if (skip == true)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
inc.BitPosition = prevBitPosition; // rewind so the game can read the message
|
|
return true;
|
|
}
|
|
|
|
[HarmonyPatch(typeof(SubEditorScreen), nameof(SubEditorScreen.Select), new Type[] { }), HarmonyPostfix]
|
|
public static void SubEditorScreen_Selected_Post(Screen __instance)
|
|
{
|
|
_eventService.PublishEvent<IEventScreenSelected>(x => x.OnScreenSelected(__instance));
|
|
}
|
|
|
|
[HarmonyPatch(typeof(PlayerInput), nameof(PlayerInput.Update)), HarmonyPrefix]
|
|
public static void PlayerInput_Update_Pre(double deltaTime)
|
|
{
|
|
_eventService.PublishEvent<IEventKeyUpdate>(x => x.OnKeyUpdate(deltaTime));
|
|
}
|
|
|
|
[HarmonyPatch(typeof(DebugConsole), "IsCommandPermitted"), HarmonyPrefix]
|
|
public static bool DebugConsole_IsCommandPermitted(Identifier command, ref bool __result)
|
|
{
|
|
DebugConsole.Command c = DebugConsole.FindCommand(command.Value);
|
|
|
|
if (DebugConsole.Commands.IndexOf(c) >= LuaCsSetup.DebugConsoleCommandVanillaIndex)
|
|
{
|
|
__result = true;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
#elif SERVER
|
|
[HarmonyPatch(typeof(GameServer), "ReadDataMessage"), HarmonyPrefix]
|
|
public static bool GameServer_ReadDataMessage_Pre(NetworkConnection sender, IReadMessage inc)
|
|
{
|
|
int prevBitPosition = inc.BitPosition;
|
|
ClientPacketHeader header = (ClientPacketHeader)inc.ReadByte();
|
|
|
|
bool? skip = null;
|
|
_eventService.PublishEvent<IEventClientRawNetMessageReceived>(x => skip = x.OnReceivedClientNetMessage(inc, header, sender) ?? skip);
|
|
|
|
if (skip == true)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
inc.BitPosition = prevBitPosition; // rewind so the game can read the message
|
|
return true;
|
|
}
|
|
|
|
[HarmonyPatch(typeof(GameServer), "OnInitializationComplete"), HarmonyPostfix]
|
|
public static void GameServer_OnInitializationComplete_Post(GameServer __instance)
|
|
{
|
|
Client client = __instance.ConnectedClients.LastOrDefault();
|
|
if (client == null) { return; }
|
|
_eventService.PublishEvent<IEventClientConnected>(x => x.OnClientConnected(client));
|
|
}
|
|
|
|
[HarmonyPatch(typeof(GameServer), nameof(GameServer.DisconnectClient), new Type[] { typeof(Client), typeof(PeerDisconnectPacket) }), HarmonyPrefix]
|
|
public static void GameServer_DisconnectClient_Pre(Client client, PeerDisconnectPacket peerDisconnectPacket)
|
|
{
|
|
if (client == null) { return; }
|
|
|
|
_eventService.PublishEvent<IEventClientDisconnected>(x => x.OnClientDisconnected(client));
|
|
}
|
|
|
|
[HarmonyPatch(typeof(GameServer), nameof(GameServer.AssignJobs)), HarmonyPostfix]
|
|
public static void GameServer_AssignJobs_Post(List<Client> unassigned)
|
|
{
|
|
_eventService.PublishEvent<IEventJobsAssigned>(x => x.OnJobsAssigned(unassigned));
|
|
}
|
|
#endif
|
|
|
|
[HarmonyPatch(typeof(Character), nameof(Character.Create), new[] {
|
|
typeof(CharacterPrefab),
|
|
typeof(Vector2),
|
|
typeof(string),
|
|
typeof(CharacterInfo),
|
|
typeof(ushort),
|
|
typeof(bool),
|
|
typeof(bool),
|
|
typeof(bool),
|
|
typeof(RagdollParams),
|
|
typeof(bool)
|
|
}), HarmonyPostfix]
|
|
public static void Character_Create_Post(Character __result)
|
|
{
|
|
_eventService.PublishEvent<IEventCharacterCreated>(x => x.OnCharacterCreated(__result));
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Character), "KillProjSpecific"), HarmonyPostfix]
|
|
public static void Character_Kill_Post(Character __instance, Affliction causeOfDeathAffliction, CauseOfDeathType causeOfDeath)
|
|
{
|
|
_eventService.PublishEvent<IEventCharacterDeath>(x => x.OnCharacterDeath(__instance, causeOfDeathAffliction, causeOfDeath));
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Character), nameof(Character.GiveJobItems)), HarmonyPostfix]
|
|
public static void Character_GiveJobItems_Post(Character __instance, WayPoint spawnPoint, bool isPvPMode)
|
|
{
|
|
_eventService.PublishEvent<IEventGiveCharacterJobItems>(x => x.OnGiveCharacterJobItems(__instance, spawnPoint, isPvPMode));
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Character), nameof(Character.DamageLimb)), HarmonyPrefix]
|
|
public static bool Character_DamageLimb_Pre(AttackResult __result, Character __instance, Vector2 worldPosition, Limb hitLimb, IEnumerable<Affliction> afflictions, float stun, bool playSound, Vector2 attackImpulse, Character attacker, float damageMultiplier, bool allowStacking, float penetration, bool shouldImplode, bool ignoreDamageOverlay, bool recalculateVitality)
|
|
{
|
|
AttackResult? result = null;
|
|
_eventService.PublishEvent<IEventCharacterDamageLimb>(x => result = x.OnCharacterDamageLimb(__instance, worldPosition, hitLimb, afflictions, stun, playSound, attackImpulse, attacker, damageMultiplier, allowStacking, penetration, shouldImplode));
|
|
if (result != null)
|
|
{
|
|
__result = (AttackResult)result;
|
|
return false; // skip
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Affliction), nameof(Affliction.Update)), HarmonyPostfix]
|
|
public static void Affliction_Update_Post(Affliction __instance, CharacterHealth characterHealth, Limb targetLimb, float deltaTime)
|
|
{
|
|
_eventService.PublishEvent<IEventAfflictionUpdate>(x => x.OnAfflictionUpdate(__instance, characterHealth, targetLimb, deltaTime));
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Connection), nameof(Connection.SendSignal)), HarmonyPostfix]
|
|
public static void Connection_SendSignal_Post(Connection __instance, Signal signal)
|
|
{
|
|
foreach (var wire in __instance.Wires)
|
|
{
|
|
Connection recipient = wire.OtherConnection(__instance);
|
|
if (recipient == null) { continue; }
|
|
|
|
_eventService.PublishEvent<IEventSignalReceived>(x => x.OnSignalReceived(signal, recipient));
|
|
_eventService.Call("signalReceived." + recipient.Item.Prefab.Identifier, signal, recipient);
|
|
}
|
|
|
|
foreach (CircuitBoxConnection connection in __instance.CircuitBoxConnections)
|
|
{
|
|
_eventService.PublishEvent<IEventSignalReceived>(x => x.OnSignalReceived(signal, connection.Connection));
|
|
_eventService.Call("signalReceived." + connection.Connection.Item.Prefab.Identifier, signal, connection.Connection);
|
|
}
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Item), MethodType.Constructor, new Type[] { typeof(Rectangle), typeof(ItemPrefab), typeof(Submarine), typeof(bool), typeof(ushort) }), HarmonyPostfix]
|
|
public static void Item_Ctor_Post(Item __instance)
|
|
{
|
|
_eventService.PublishEvent<IEventItemCreated>(x => x.OnItemCreated(__instance));
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Item), nameof(Item.Remove)), HarmonyPostfix]
|
|
public static void Item_Remove_Post(Item __instance)
|
|
{
|
|
_eventService.PublishEvent<IEventItemRemoved>(x => x.OnItemRemoved(__instance));
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Item), nameof(Item.Remove)), HarmonyPostfix]
|
|
public static void Item_ShallowRemove_Post(Item __instance)
|
|
{
|
|
_eventService.PublishEvent<IEventItemRemoved>(x => x.OnItemRemoved(__instance));
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Item), nameof(Item.Use)), HarmonyPrefix]
|
|
public static bool Item_Use_Pre(Item __instance, Character user, Limb targetLimb, Entity useTarget)
|
|
{
|
|
if (__instance.RequireAimToUse && (user == null || !user.IsKeyDown(InputType.Aim)))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (__instance.Condition <= 0.0f) { return true; }
|
|
|
|
bool? result = null;
|
|
_eventService.PublishEvent<IEventItemUse>(x => result = x.OnItemUsed(__instance, user, targetLimb, useTarget));
|
|
if (result == true)
|
|
{
|
|
return false; // skip
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Item), nameof(Item.SecondaryUse)), HarmonyPrefix]
|
|
public static bool Item_SecondaryUse_Pre(Item __instance, Character character)
|
|
{
|
|
if (__instance.Condition <= 0.0f) { return true; }
|
|
|
|
bool? result = null;
|
|
_eventService.PublishEvent<IEventItemSecondaryUse>(x => result = x.OnItemSecondaryUsed(__instance, character));
|
|
if (result == true)
|
|
{
|
|
return false; // skip
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Inventory), "PutItem"), HarmonyPrefix]
|
|
public static bool Inventory_PutItem_Prefix(Inventory __instance, Item item, int i, Character user, bool removeItem)
|
|
{
|
|
bool? result = null;
|
|
_eventService.PublishEvent<IEventInventoryPutItem>(x => result = x.OnInventoryPutItem(__instance, item, user, i, removeItem));
|
|
if (result == true)
|
|
{
|
|
return false; // skip
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Inventory), "TrySwapping"), HarmonyPrefix]
|
|
public static bool Inventory_TrySwapping_Prefix(Inventory __instance, Item item, int index, Character user, bool swapWholeStack, ref bool __result)
|
|
{
|
|
// uncomment when we are plugin
|
|
// if (item?.ParentInventory == null || !__instance.slots[index].Any()) { return false; }
|
|
// if (__instance.slots[index].Items.Any(it => !it.IsInteractable(user))) { return false; }
|
|
if (!__instance.AllowSwappingContainedItems) { return false; }
|
|
|
|
bool? result = null;
|
|
_eventService.PublishEvent<IEventInventoryItemSwap>(x => result = x.OnInventoryItemSwap(__instance, item, user, index, swapWholeStack));
|
|
if (result != null)
|
|
{
|
|
__result = (bool)result;
|
|
return false; // skip
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
IsDisposed = true;
|
|
this.Harmony?.UnpatchSelf();
|
|
}
|
|
|
|
#if SERVER
|
|
[HarmonyPatch]
|
|
class Patch_StartGame_End
|
|
{
|
|
static MethodBase TargetMethod()
|
|
{
|
|
var original = AccessTools.Method(
|
|
typeof(GameServer),
|
|
"StartGame"
|
|
);
|
|
|
|
return AccessTools.EnumeratorMoveNext(original);
|
|
}
|
|
|
|
[HarmonyPostfix]
|
|
static void Postfix(object __instance, bool __result)
|
|
{
|
|
if (!__result) { return; }
|
|
|
|
var enumerator = __instance as IEnumerator<CoroutineStatus>;
|
|
if (enumerator == null) { return; }
|
|
|
|
if (enumerator.Current == CoroutineStatus.Success)
|
|
{
|
|
_eventService.PublishEvent<IEventRoundStarted>(x => x.OnRoundStart());
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|