diff --git a/valheimmod/valheimmod1.cs b/valheimmod/valheimmod1.cs index faa5445..cb229b1 100644 --- a/valheimmod/valheimmod1.cs +++ b/valheimmod/valheimmod1.cs @@ -197,6 +197,64 @@ private void Awake() AddLocs(); AddInputs(); PrefabManager.OnPrefabsRegistered += ModAbilities.Effects.Register; + + // Register RPC methods for ValhallaDome networking (delayed to ensure ZNet is ready) + PrefabManager.OnPrefabsRegistered += RegisterRPCMethods; + } + + /// + /// Register RPC methods for networking between clients and server + /// + private void RegisterRPCMethods() + { + try + { + if (ZRoutedRpc.instance != null) + { + // Register the RPC handler for dome creation requests + ZRoutedRpc.instance.Register("ValhallaDome_RequestCreation", new System.Action(OnDomeCreationRequested)); + Jotunn.Logger.LogInfo("ValhallaDome: Registered RPC methods successfully"); + } + else + { + Jotunn.Logger.LogWarning("ValhallaDome: ZRoutedRpc.instance is null, cannot register RPC methods"); + } + } + catch (System.Exception ex) + { + Jotunn.Logger.LogError($"ValhallaDome: Failed to register RPC methods: {ex.Message}"); + } + } + + /// + /// Handle dome creation requests from clients (server-side) + /// + private void OnDomeCreationRequested(long senderUID, float x, float y, float z) + { + try + { + Jotunn.Logger.LogInfo($"ValhallaDome: Received dome creation request from client {senderUID} at position ({x}, {y}, {z})"); + + if (ZNet.instance == null || !ZNet.instance.IsServer()) + { + Jotunn.Logger.LogWarning("ValhallaDome: Received RPC on client or ZNet not ready, ignoring"); + return; + } + + Vector3 position = new Vector3(x, y, z); + if (ModAbilities.ValhallaDome.Instance != null) + { + ModAbilities.ValhallaDome.Instance.CreateDomeAtPosition(position); + } + else + { + Jotunn.Logger.LogError("ValhallaDome: Instance is null, cannot create dome"); + } + } + catch (System.Exception ex) + { + Jotunn.Logger.LogError($"ValhallaDome: Error handling dome creation request: {ex.Message}"); + } } private void Update() @@ -661,7 +719,17 @@ class Hud_UpdateStatusEffects_Patch { static void Postfix(Hud __instance, List statusEffects) { - ModAbilities.Effects.UpdateStatusEffect(__instance, statusEffects); + try + { + if (__instance != null && statusEffects != null) + { + ModAbilities.Effects.UpdateStatusEffect(__instance, statusEffects); + } + } + catch (System.Exception ex) + { + Jotunn.Logger.LogError($"Error in Hud_UpdateStatusEffects_Patch: {ex.Message}"); + } } } @@ -780,5 +848,127 @@ static void Prefix() Jotunn.Logger.LogInfo("Transitioning to main scene, resetting LoggedIn to false."); } } + + // Patch to detect and setup Valhalla Domes on all clients + [HarmonyPatch(typeof(ShieldGenerator), "Start")] + public static class ShieldGenerator_Start_ValhallaDome_Patch + { + static void Postfix(ShieldGenerator __instance) + { + try + { + if (__instance == null) return; + + var znetView = __instance.GetComponent(); + if (znetView != null && znetView.IsValid()) + { + // Check if this is marked as a Valhalla Dome + if (znetView.GetZDO().GetBool("valhalla_dome_setup", false)) + { + Jotunn.Logger.LogInfo($"ValhallaDome: Detected Valhalla Dome on client, setting up..."); + + // Start coroutine to setup the dome on the next frame + if (Player.m_localPlayer != null) + { + Player.m_localPlayer.StartCoroutine(SetupValhallaDomeCoroutine(__instance.gameObject)); + } + } + } + } + catch (System.Exception ex) + { + Jotunn.Logger.LogError($"ValhallaDome: Error in ShieldGenerator patch: {ex.Message}"); + } + } + + private static System.Collections.IEnumerator SetupValhallaDomeCoroutine(GameObject dome) + { + yield return null; // Wait one frame + + if (dome == null) + { + Jotunn.Logger.LogError("ValhallaDome: Dome GameObject is null in coroutine"); + yield break; + } + + var shieldGen = dome.GetComponent(); + if (shieldGen != null) + { + Jotunn.Logger.LogInfo("ValhallaDome: Setting up shield generator for client"); + + shieldGen.m_offWhenNoFuel = false; + shieldGen.m_minShieldRadius = 4f; + shieldGen.m_maxShieldRadius = 4f; + shieldGen.SetFuel(shieldGen.m_maxFuel); + shieldGen.UpdateShield(); + + // Wait another frame for the shield dome to be created + yield return null; + + // Hide all MeshRenderers except the dome + foreach (var renderer in dome.GetComponentsInChildren(true)) + { + if (shieldGen.m_shieldDome == null || !renderer.transform.IsChildOf(shieldGen.m_shieldDome.transform)) + { + renderer.enabled = false; + Jotunn.Logger.LogInfo($"ValhallaDome: Hiding renderer on {renderer.name}"); + } + } + + // Setup the dome's mob-repelling functionality + var domeObj = shieldGen.m_shieldDome; + if (domeObj != null) + { + Jotunn.Logger.LogInfo("ValhallaDome: Setting up dome collider and mob repelling"); + + // Remove existing colliders except the dome's own + foreach (var col in dome.GetComponentsInChildren()) + { + if (col.transform != domeObj.transform) + { + UnityEngine.Object.Destroy(col); + } + } + + // Add the sphere collider for mob detection + var collider = domeObj.GetComponent(); + if (collider == null) + { + collider = domeObj.AddComponent(); + collider.isTrigger = true; + float visualRadius = shieldGen.m_maxShieldRadius; + float scale = domeObj.transform.lossyScale.x; + float fudge = 0.3f; + collider.radius = (visualRadius / scale) * fudge; + + // Add the mob-only shield component + var mobShield = domeObj.GetComponent(); + if (mobShield == null) + { + domeObj.AddComponent(); + } + } + + domeObj.layer = LayerMask.NameToLayer("character"); + } + + // Setup timed destruction + var timedDestruction = dome.GetComponent(); + if (timedDestruction == null) + { + timedDestruction = dome.AddComponent(); + } + timedDestruction.m_forceTakeOwnershipAndDestroy = true; + timedDestruction.m_timeout = ModAbilities.ValhallaDome.Instance.ttl; + timedDestruction.Trigger(); + + Jotunn.Logger.LogInfo("ValhallaDome: Client setup complete"); + } + else + { + Jotunn.Logger.LogError("ValhallaDome: ShieldGenerator component not found on dome"); + } + } + } } } \ No newline at end of file diff --git a/valheimmod/valheimmod3.cs b/valheimmod/valheimmod3.cs index afe00c1..b7657e8 100644 --- a/valheimmod/valheimmod3.cs +++ b/valheimmod/valheimmod3.cs @@ -252,14 +252,29 @@ public static void Load(bool onDeath = false) public static void UpdateStatusEffect(Hud __instance, List statusEffectsList, Dictionary durationDict = null) { + // Add null checks to prevent NullReferenceException + if (__instance == null || statusEffectsList == null || specialAbilities == null) + return; + // Update the textures for each status effect in the list { foreach (var ability in specialAbilities) { + if (ability == null) continue; + for (int j = 0; j < statusEffectsList.Count; j++) { StatusEffect statusEffect = statusEffectsList[j]; - ability?.updateTexture(__instance, statusEffect, j); + if (statusEffect == null) continue; + + try + { + ability.updateTexture(__instance, statusEffect, j); + } + catch (System.Exception ex) + { + Jotunn.Logger.LogError($"Error updating texture for ability {ability.GetType().Name}: {ex.Message}"); + } } } } @@ -762,19 +777,39 @@ public override void updateDuration(Dictionary statusEffectDict) } public override void updateTexture(Hud __instance, StatusEffect statusEffect, int index) { + // Add null checks to prevent NullReferenceException + if (statusEffect?.m_name == null || SpecialEffect?.StatusEffect?.m_name == null) + return; + + if (Player.m_localPlayer == null) + return; + if (statusEffect.m_name == SpecialEffect.StatusEffect.m_name) { // Find the correct icon for the current arrow count int arrowsLeft = 3 - (ShotsFired.ContainsKey(Player.m_localPlayer) ? ShotsFired[Player.m_localPlayer] : 0); if (arrowsLeft > 0 && arrowsLeft <= 3) { + // Add null checks for HUD components + if (__instance?.m_statusEffects == null || index < 0 || index >= __instance.m_statusEffects.Count) + return; + // Update the icon in the HUD RectTransform val2 = __instance.m_statusEffects[index]; - Image component = ((UnityEngine.Component)((Transform)val2).Find("Icon")).GetComponent(); - component.sprite = textures[arrowsLeft - 1]; + if (val2 == null) + return; + + Transform iconTransform = ((Transform)val2).Find("Icon"); + if (iconTransform == null) + return; + + Image component = iconTransform.GetComponent(); + if (component != null && textures != null && arrowsLeft - 1 < textures.Length) + { + component.sprite = textures[arrowsLeft - 1]; + } } } - } } @@ -872,29 +907,47 @@ public override void updateDuration(Dictionary statusEffectDict) public class MobOnlyShield : MonoBehaviour { private float repelForce = 30f; // Adjust this value to change the force applied to mobs + private void OnTriggerEnter(Collider other) { Jotunn.Logger.LogInfo($"MobOnlyShield OnTriggerEnter called for {other.name}"); - Character character = other.GetComponent(); - if (character != null && character.IsMonsterFaction(0f)) - { - Jotunn.Logger.LogInfo($"Repelling mob: {character.name}"); - Vector3 repelDir = (character.transform.position - transform.position).normalized; - character.m_body?.AddForce(repelDir * repelForce, ForceMode.VelocityChange); - - } - else if (character != null) - { - Jotunn.Logger.LogInfo($"Ignoring non-monster character: {character.name}"); - } + HandleMobRepelling(other); } + private void OnTriggerStay(Collider other) + { + HandleMobRepelling(other, true); + } + + private void HandleMobRepelling(Collider other, bool isStay = false) { Character character = other.GetComponent(); - if (character != null && character.IsMonsterFaction(0f)) + if (character == null) return; + + // Check if it's a monster/hostile creature + if (character.IsMonsterFaction(0f)) { - Vector3 repelDir = (character.transform.position - transform.position).normalized; - character.m_body?.AddForce(repelDir * repelForce, ForceMode.VelocityChange); // Use Force for continuous push + if (!isStay) + Jotunn.Logger.LogInfo($"Repelling mob: {character.name}"); + + // Only apply force if we can (either local player or have authority) + if (character.m_nview != null && (character.m_nview.IsOwner() || Player.m_localPlayer != null)) + { + Vector3 repelDir = (character.transform.position - transform.position).normalized; + if (character.m_body != null) + { + character.m_body.AddForce(repelDir * repelForce, ForceMode.VelocityChange); + } + else + { + // Fallback: directly modify position if no rigidbody + character.transform.position += repelDir * 0.1f; + } + } + } + else if (character != null && !isStay) + { + Jotunn.Logger.LogInfo($"Ignoring non-monster character: {character.name}"); } } } @@ -972,9 +1025,205 @@ public override void CallPending(valheimmod instance = null) { return; } - Player.m_localPlayer.m_seman.AddStatusEffect(SpecialEffect.StatusEffect, true); - ValhallaDome.Instance.abilityUsed = true; // Reset the active dome - CallManual(); + + // Try to create the dome first, only add status effect if successful + bool domeCreated = TryCreateDome(); + if (domeCreated) + { + // Add the status effect only after successful dome creation + Player.m_localPlayer.m_seman.AddStatusEffect(SpecialEffect.StatusEffect, true); + ValhallaDome.Instance.abilityUsed = true; + } + else + { + // If dome creation failed, show a message but don't add cooldown + Player.m_localPlayer.Message(MessageHud.MessageType.Center, "Failed to create dome"); + } + } + } + + /// + /// Try to create a dome, return true if successful + /// + private bool TryCreateDome() + { + try + { + Jotunn.Logger.LogInfo("ValhallaDome: TryCreateDome called"); + Vector3 position = Player.m_localPlayer.transform.position; + + // For now, use the simple approach like the original CallManual + // The networking can be added back later once this works + if (Player.m_localPlayer == null) + { + Jotunn.Logger.LogError("ValhallaDome: Player.m_localPlayer is null"); + return false; + } + + if (ZNetScene.instance == null) + { + Jotunn.Logger.LogError("ValhallaDome: ZNetScene.instance is null"); + return false; + } + + GameObject domePrefab = ZNetScene.instance.GetPrefab("piece_shieldgenerator"); + if (domePrefab != null) + { + Jotunn.Logger.LogInfo("ValhallaDome: Found shield generator prefab, creating dome"); + Quaternion rot = Quaternion.identity; + ActiveDome = UnityEngine.Object.Instantiate(domePrefab, position, rot); + Jotunn.Logger.LogInfo($"ValhallaDome: Instantiated dome at position {position}"); + + var znetView = ActiveDome.GetComponent(); + if (znetView != null && znetView.IsValid()) + { + Jotunn.Logger.LogInfo("ValhallaDome: ZNetView is valid, setting up dome"); + string uniqueId = System.Guid.NewGuid().ToString(); + znetView.GetZDO().Set(dome_uid, uniqueId); + znetView.GetZDO().Set("valhalla_dome_setup", true); // Mark this as a valhalla dome + + Instance.LastDomeUID = uniqueId; + PlayerPrefs.SetString("Dome_LastDomeUID", uniqueId); + PlayerPrefs.Save(); + + Jotunn.Logger.LogInfo($"ValhallaDome: Dome created with UID: {uniqueId}"); + + // Start a coroutine to set up the shield after one frame + Player.m_localPlayer.StartCoroutine(SetupShieldNextFrame(ActiveDome)); + return true; + } + else + { + Jotunn.Logger.LogError("ValhallaDome: ZNetView is null or invalid"); + if (znetView == null) + Jotunn.Logger.LogError("ValhallaDome: ZNetView component not found"); + else + Jotunn.Logger.LogError("ValhallaDome: ZNetView is not valid"); + return false; + } + } + else + { + Jotunn.Logger.LogError("ValhallaDome: Could not find piece_shieldgenerator prefab"); + return false; + } + } + catch (System.Exception ex) + { + Jotunn.Logger.LogError($"ValhallaDome: Error creating dome: {ex.Message}"); + return false; + } + } + + /// + /// Request dome creation at the specified position. Handles both server and client cases. + /// + public void RequestDomeCreation(Vector3 position) + { + try + { + if (ZNet.instance == null) + { + Jotunn.Logger.LogError("ValhallaDome: ZNet.instance is null, cannot create dome"); + return; + } + + if (ZNet.instance.IsServer()) + { + // If we're the server, create the dome directly + Jotunn.Logger.LogInfo("ValhallaDome: Server creating dome directly"); + CreateDomeAtPosition(position); + } + else + { + // If we're a client, send an RPC request to the server + Jotunn.Logger.LogInfo("ValhallaDome: Client requesting dome creation from server"); + if (ZRoutedRpc.instance != null && ZNet.instance.GetServerPeer() != null) + { + ZRoutedRpc.instance.InvokeRoutedRPC(ZNet.instance.GetServerPeer().m_uid, "ValhallaDome_RequestCreation", + position.x, position.y, position.z); + } + else + { + Jotunn.Logger.LogError("ValhallaDome: Cannot send RPC - ZRoutedRpc or server peer is null"); + } + } + } + catch (System.Exception ex) + { + Jotunn.Logger.LogError($"ValhallaDome: Error in RequestDomeCreation: {ex.Message}"); + } + } + + /// + /// Actually creates the dome at the specified position. Only called on server. + /// + public void CreateDomeAtPosition(Vector3 position) + { + try + { + if (ZNet.instance == null || !ZNet.instance.IsServer()) + { + Jotunn.Logger.LogWarning("ValhallaDome: CreateDomeAtPosition called on client, ignoring"); + return; + } + + GameObject domePrefab = ZNetScene.instance?.GetPrefab("piece_shieldgenerator"); + if (domePrefab != null) + { + Quaternion rot = Quaternion.identity; + + // Create the dome through the network spawning system + GameObject spawnedDome = UnityEngine.Object.Instantiate(domePrefab, position, rot); + var znetView = spawnedDome.GetComponent(); + + if (znetView != null) + { + // Take ownership of the networked object + znetView.ClaimOwnership(); + + if (znetView.IsValid()) + { + string uniqueId = System.Guid.NewGuid().ToString(); + znetView.GetZDO().Set(dome_uid, uniqueId); + znetView.GetZDO().Set("valhalla_dome_setup", true); // Mark this as a valhalla dome + + Instance.LastDomeUID = uniqueId; + PlayerPrefs.SetString("Dome_LastDomeUID", uniqueId); + PlayerPrefs.Save(); + + Jotunn.Logger.LogInfo($"ValhallaDome: Server created dome with UID: {uniqueId} at position {position}"); + + // Store reference locally for the server + if (ActiveDome == null) // Only set if we don't have an active dome + { + ActiveDome = spawnedDome; + } + + // Setup the dome immediately for the server/host + if (Player.m_localPlayer != null) + { + Player.m_localPlayer.StartCoroutine(SetupShieldNextFrame(spawnedDome)); + } + } + else + { + Jotunn.Logger.LogError("ValhallaDome: ZNetView is not valid, cannot create dome"); + } + } + else + { + Jotunn.Logger.LogError("ValhallaDome: No ZNetView component found on dome prefab"); + } + } + else + { + Jotunn.Logger.LogError("ValhallaDome: Could not find piece_shieldgenerator prefab"); + } + } + catch (System.Exception ex) + { + Jotunn.Logger.LogError($"ValhallaDome: Error in CreateDomeAtPosition: {ex.Message}"); } }