using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.InteropServices; using HarmonyLib; using UnityEngine; namespace Harmony.PlayerFeatures { /** * SCorePlayerMoveController_Update * * This class includes a Harmony patches to the EntityPlayer Local to allow skipping of buffs that contain the name "buffcutscene" by pressing space or escape. * * This was used in the Winter Project 2019 to skip the opening cutscene, which was applied through a buff. */ [HarmonyPatch(typeof(PlayerMoveController))] [HarmonyPatch("Update")] public class PlayerMoveControllerUpdate { // Returns true for the default PlaceBlock code to execute. If it returns false, it won't execute it at all. private static bool Prefix(PlayerMoveController __instance, EntityPlayerLocal ___entityPlayerLocal) { if (__instance.playerInput.Jump.IsPressed || __instance.playerInput.Menu.IsPressed) foreach (var buff in ___entityPlayerLocal.Buffs.ActiveBuffs) if (buff.BuffName.ToLower().Contains("buffcutscene")) ___entityPlayerLocal.Buffs.RemoveBuff(buff.BuffName); return true; } private static readonly string AdvancedFeatureClass = "AdvancedNPCFeatures"; private static readonly string AdvancedEnemyNPCsFeature = "AdvancedEnemyNPCs"; private static bool _initialized = false; private static bool _enabled = false; /// /// Disables the "Press E to interact..." prompt if the NPC is an enemy. /// This is only needed if some enemy NPCs use EntityAliveSDX. /// /// /// public static void Postfix( EntityPlayerLocal ___entityPlayerLocal, ref string ___strTextLabelPointingTo) { if (!_initialized) Initialize(); if (!_enabled) return; if (!___entityPlayerLocal.IsAlive()) return; var hitInfo = ___entityPlayerLocal.HitInfo; // This is how the code determines if it's an entity that can be interacted with if (!hitInfo.bHitValid || !hitInfo.tag.StartsWith("E_") || hitInfo.hit.distanceSq >= Constants.cCollectItemDistance * Constants.cCollectItemDistance) return; var rootTransform = GameUtils.GetHitRootTransform(hitInfo.tag, hitInfo.transform); if (rootTransform == null) return; var entity = rootTransform.GetComponent(); if (entity is not EntityNPC npc || !npc.IsAlive()) return; // This is how the code determines if it's a trader/EntityAliveSDX (vs. a drone) if (GameManager.Instance.World.GetTileEntity(npc.entityId) is not TileEntityTrader) return; // At this point we know it's a trader or EntityAliveSDX and that the "Press E..." // prompt is to be shown; now determine if it's an enemy, and if so, hide the prompt if (ShouldTalk(___entityPlayerLocal, npc)) return; ___strTextLabelPointingTo = string.Empty; XUiC_InteractionPrompt.SetText(___entityPlayerLocal.PlayerUI, null); } private static void Initialize() { _initialized = true; _enabled = Configuration.CheckFeatureStatus( AdvancedFeatureClass, AdvancedEnemyNPCsFeature); AdvLogging.DisplayLog( AdvancedFeatureClass, $"{AdvancedEnemyNPCsFeature} {(_enabled ? "en" : "dis")}abled"); } /// /// This is a specialized method, which basically returns false if the NPC is an enemy /// of the player. We already know the entity types, that neither entity is dead or null, /// and that a player can't have a leader, so we don't need to do all of the checks in /// . /// /// /// private static bool ShouldTalk(EntityPlayerLocal player, EntityNPC npc) { if (EntityTargetingUtilities.IsAlly(npc, player)) return true; if (EntityTargetingUtilities.IsFightingFollowers(player, npc)) return false; return !EntityTargetingUtilities.IsEnemyByFaction(npc, player); } } }