diff --git a/MxValheim/EventSystem/Experience.cs b/MxValheim/EventSystem/Experience.cs index 850c0fc..d83d435 100644 --- a/MxValheim/EventSystem/Experience.cs +++ b/MxValheim/EventSystem/Experience.cs @@ -15,7 +15,7 @@ namespace MxValheim.EventSystem return (level * 100); } - public static float CalculateExpMultiplier(int level, Heightmap.Biome biome) + public static float CalculateExpMultiplier(Heightmap.Biome biome) { Dictionary biomeBase = new Dictionary() { diff --git a/MxValheim/EventSystem/Patch.cs b/MxValheim/EventSystem/Patch.cs index 888437d..7446973 100644 --- a/MxValheim/EventSystem/Patch.cs +++ b/MxValheim/EventSystem/Patch.cs @@ -45,8 +45,8 @@ namespace MxValheim.EventSystem { static void Prefix(RandEventSystem __instance) { - //__instance.m_eventIntervalMin = 10f; - //__instance.m_eventChance = 100f; + __instance.m_eventIntervalMin = 10f; + __instance.m_eventChance = 100f; } } @@ -56,16 +56,24 @@ namespace MxValheim.EventSystem private static readonly AccessTools.FieldRef ActiveEventField = AccessTools.FieldRefAccess("m_activeEvent"); static void Prefix(RandEventSystem __instance, string eventName) { - RandomEvent currentRaid = ActiveEventField(__instance); + RandomEvent currentRaid = ActiveEventField(__instance); if (string.IsNullOrEmpty(eventName) && currentRaid != null) { + int kill = 0; + ZPackage pkg = new ZPackage(); + pkg.Write(currentRaid.m_name); + pkg.Write($"{currentRaid.m_pos}"); + Debug.Log($"RPC_SetEvent: {currentRaid.m_pos}"); + kill = Watcher.eventKill; + ZRoutedRpc.instance.InvokeRoutedRPC(0, "RPC_ServerRemoveEvent", pkg); + Vector3 playerPos = Player.m_localPlayer.transform.position; float distance = Vector3.Distance(playerPos, currentRaid.m_pos); if (distance <= 96f) { Heightmap.Biome currentBiome = EnvMan.instance.GetCurrentBiome(); - Reward.GiveItemStatic("Coins", Reward.CalculateRewardCoin(Profiles.serverLevel, currentBiome)); + Reward.GiveItemStatic("Coins", Reward.CalculateRewardCoin(Profiles.serverLevel, currentBiome, kill)); System.Random rand = new System.Random(); Reward.GiveItemStatic("Feathers", rand.Next(5, 20)); if (rand.Next(100) < 50) @@ -102,10 +110,8 @@ namespace MxValheim.EventSystem } } - // Hiding the yellow persistent text at the top center if (RandEventSystem.instance != null && RandEventSystem.instance.GetActiveEvent() != null) { - // We set the text to an empty string so it doesn't render __instance.m_eventName.text = ""; } } @@ -116,66 +122,48 @@ namespace MxValheim.EventSystem { static void Postfix(Character __instance) { - // Check if the character is dead or just died if (__instance.GetHealth() <= 0f) { ZNetView nview = __instance.GetComponent(); - // This check should now pass because ApplyDamage happens before the object is invalidated if (nview == null || !nview.IsValid() || !nview.IsOwner()) return; - // 2. Check if a raid is active + MonsterAI mai = __instance.GetComponent(); + bool isEventCreature = false; + if (mai != null) + { + isEventCreature = Traverse.Create(mai).Field("m_eventCreature").Value; + } + + if (!isEventCreature) return; + RandomEvent activeEvent = RandEventSystem.instance.GetActiveEvent(); - if (activeEvent == null) return; - - Vector3 playerPos = Player.m_localPlayer.transform.position; - float distance = Vector3.Distance(playerPos, activeEvent.m_pos); - - MonsterAI mai = __instance.GetComponent(); - bool isEventCreature = false; - if (mai != null) + if (activeEvent != null) { - isEventCreature = Traverse.Create(mai).Field("m_eventCreature").Value; - } - Debug.Log($"EventSystem: Event Enemy {nview.GetZDO().GetBool(ZDOVars.s_eventCreature, isEventCreature)}"); + ZPackage pkg = new ZPackage(); + pkg.Write(activeEvent.m_name); + pkg.Write($"{activeEvent.m_pos}"); + ActiveEventData entry = MxValheimMod.ActiveEvents.Find(e => e.Position == activeEvent.m_pos.ToString()); + if (entry == null) + { + ZRoutedRpc.instance.InvokeRoutedRPC(0, "RPC_ServerRegisterEvent", pkg); + } else + { + ZRoutedRpc.instance.InvokeRoutedRPC(0, "RPC_ServerSetEventKill", pkg); + } + ZRoutedRpc.instance.InvokeRoutedRPC(0, "RPC_ServerGetEventKill", pkg); + } - if (distance <= 96f) - { - int level = __instance.GetLevel(); - int exp = - (level == 1) ? 2 : - (level == 2) ? 4 : - (level == 3) ? 6 : - 6; - ZRoutedRpc.instance.InvokeRoutedRPC(0, "RPC_RequestProfile", "Event"); - Heightmap.Biome currentBiome = EnvMan.instance.GetCurrentBiome(); - int expFinal = (int)(exp * Experience.CalculateExpMultiplier(Profiles.serverLevel, currentBiome)); - Experience.AddExperience(expFinal); - } - } - } - } - - [HarmonyPatch(typeof(Character), nameof(Character.ApplyDamage))] - public static class DeathNotifierEvent - { - static void Postfix(Character __instance) - { - // Check if the character is dead or just died - if (__instance.GetHealth() <= 0f) - { - ZNetView nview = __instance.GetComponent(); - - // This check should now pass because ApplyDamage happens before the object is invalidated - if (nview == null || !nview.IsValid() || !nview.IsOwner()) return; - - MonsterAI mai = __instance.GetComponent(); - bool isEventCreature = false; - if (mai != null) - { - isEventCreature = Traverse.Create(mai).Field("m_eventCreature").Value; - } - Debug.Log($"EventSystem: Event Enemy {nview.GetZDO().GetBool(ZDOVars.s_eventCreature, isEventCreature)}"); + int level = __instance.GetLevel(); + int exp = + (level == 1) ? 2 : + (level == 2) ? 4 : + (level == 3) ? 6 : + 6; + ZRoutedRpc.instance.InvokeRoutedRPC(0, "RPC_RequestProfile", "Event"); + Heightmap.Biome currentBiome = EnvMan.instance.GetCurrentBiome(); + int expFinal = ((int)(exp * Experience.CalculateExpMultiplier(currentBiome))) + Profiles.serverLevel; + Experience.AddExperience(expFinal); } } } diff --git a/MxValheim/EventSystem/Profiles.cs b/MxValheim/EventSystem/Profiles.cs index e5ffbbd..926e7b9 100644 --- a/MxValheim/EventSystem/Profiles.cs +++ b/MxValheim/EventSystem/Profiles.cs @@ -20,7 +20,6 @@ namespace MxValheim.EventSystem public static int serverLevel = 1; public static int serverExperience = 0; - public static void ServerUserExists(string playerid) { // Check to ensure this is actually the server running this diff --git a/MxValheim/EventSystem/RandomUnique/SpawnManager.cs b/MxValheim/EventSystem/RandomUnique/SpawnManager.cs new file mode 100644 index 0000000..18e14bd --- /dev/null +++ b/MxValheim/EventSystem/RandomUnique/SpawnManager.cs @@ -0,0 +1,158 @@ +using HarmonyLib; +using Jotunn.Configs; +using Jotunn.Entities; +using Jotunn.Managers; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using static DropTable; + +namespace MxValheim.EventSystem.RandomUnique +{ + internal class SpawnManager + { + public class SpawnConditionsData + { + public bool WorldSpawnEnabled; + public string Biome; + public float SpawnChance; + public int MaxSpawned; + public float SpawnInterval; + public float SpawnDistance; + public bool SpawnInForest; + public bool SpawnOutsideForest; + public float MinAltitude; + public float MaxAltitude; + public int MinGroupSize; + public int MaxGroupSize; + public bool SpawnAtDay; + public bool SpawnAtNight; + public int MinLevel; + } + + public class DropData + { + public string ItemName; + public int Min; + public int Max; + public float Chance; + public bool OnePerPlayer; + } + + public class EquipmentData + { + public string RightHand; + public string LeftHand; + public string Chest; + public string Legs; + public string Back; + } + + public class EnemyTemplate + { + public string PrefabToClone; + public string InternalName; + public string HoverName; + public string Faction; + public float Health; + public float Speed; + public float Scale; + public ColorData ColorTint; + + public SpawnConditionsData SpawnConditions; + + public List DropTable; + public EquipmentData Equipment; + } + + public class ColorData + { + public float r; + public float g; + public float b; + public float intensity; + } + + public static void RegisterWithWorldSpawn(GameObject customEnemy, EnemyTemplate data) + { + var spawnConfig = new SpawnConfig + { + Name = data.InternalName + "_Spawn", + WorldSpawnEnabled = data.SpawnConditions.WorldSpawnEnabled, + Biome = (Heightmap.Biome)Enum.Parse(typeof(Heightmap.Biome), data.SpawnConditions.Biome), + SpawnChance = data.SpawnConditions.SpawnChance, + MaxSpawned = data.SpawnConditions.MaxSpawned, + SpawnInterval = data.SpawnConditions.SpawnInterval, + SpawnDistance = data.SpawnConditions.SpawnDistance, + SpawnInForest = data.SpawnConditions.SpawnInForest, + SpawnOutsideForest = data.SpawnConditions.SpawnOutsideForest, + MinAltitude = data.SpawnConditions.MinAltitude, + MaxAltitude = data.SpawnConditions.MaxAltitude, + MinGroupSize = data.SpawnConditions.MinGroupSize, + MaxGroupSize = data.SpawnConditions.MaxGroupSize, + SpawnAtDay = !data.SpawnConditions.SpawnAtDay, + SpawnAtNight = data.SpawnConditions.SpawnAtNight, + MinLevel = data.SpawnConditions.MinLevel, + }; + + var creatureConfig = new CreatureConfig(); + creatureConfig.Name = data.HoverName; + + creatureConfig.AddSpawnConfig(spawnConfig); + + CreatureManager.Instance.AddCreature(new CustomCreature(customEnemy, true, creatureConfig)); + + Debug.Log($"[MxValheim] {data.InternalName} registered correctly with CreatureConfig!"); + } + + [HarmonyPatch(typeof(Hud), nameof(Hud.Update))] + public static class Hud_Update_SpawnManager + { + static void Postfix(Hud __instance) + { + if (Player.m_localPlayer == null || !Player.m_localPlayer.IsDead()) return; + + if (MxValheimMod._uniqueTimer > 0) + { + MxValheimMod._uniqueTimer -= Time.deltaTime; + } + + if (MxValheimMod._uniqueTimer <= 0) + { + int pb = GetPlayerBiomeIndex(Player.m_localPlayer); + System.Random rand = new System.Random(); + if (rand.Next(100) < 25) + { + switch (pb) + { + case 1: break; + } + } + MxValheimMod._uniqueTimer = 60.0f; + } + } + } + + public static int GetPlayerBiomeIndex(Player player) + { + int biomeIndex = 0; + Heightmap.Biome currentBiome = player.GetCurrentBiome(); + Debug.Log($"KillFeed: {currentBiome}"); + if (currentBiome == Heightmap.Biome.Meadows) biomeIndex = 1; + else if (currentBiome == Heightmap.Biome.BlackForest) biomeIndex = 2; + else if (currentBiome == Heightmap.Biome.Swamp) biomeIndex = 3; + else if (currentBiome == Heightmap.Biome.Mountain) biomeIndex = 4; + else if (currentBiome == Heightmap.Biome.Plains) biomeIndex = 5; + else if (currentBiome == Heightmap.Biome.Mistlands) biomeIndex = 6; + else if (currentBiome == Heightmap.Biome.DeepNorth) biomeIndex = 7; + else if (currentBiome == Heightmap.Biome.AshLands) biomeIndex = 8; + else if (currentBiome == Heightmap.Biome.Ocean) biomeIndex = 9; + + return biomeIndex; + } + } +} diff --git a/MxValheim/EventSystem/RandomUnique/UniqueManager.cs b/MxValheim/EventSystem/RandomUnique/UniqueManager.cs new file mode 100644 index 0000000..c0a6476 --- /dev/null +++ b/MxValheim/EventSystem/RandomUnique/UniqueManager.cs @@ -0,0 +1,265 @@ +using Jotunn.Managers; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using static MxValheim.EventSystem.RandomUnique.SpawnManager; + +namespace MxValheim.EventSystem.RandomUnique +{ + internal class UniqueManager + { + public static void LoadAllTemplates() + { + string uniqueFolder = Path.Combine(MxValheimMod.internalDataPath, "UniqueEnemy"); + + if (!Directory.Exists(uniqueFolder)) + { + Directory.CreateDirectory(uniqueFolder); + } + + string[] jsonFiles = Directory.GetFiles(uniqueFolder, "*.json"); + + foreach (string filePath in jsonFiles) + { + try + { + string jsonContent = File.ReadAllText(filePath); + LoadEnemyFromJson(jsonContent); + Debug.Log($"Loaded custom enemy from: {Path.GetFileName(filePath)}"); + } + catch (System.Exception ex) + { + Debug.LogError($"Failed to load {filePath}: {ex.Message}"); + } + } + } + + public static void LoadEnemyFromJson(string jsonContent) + { + var data = JsonConvert.DeserializeObject(jsonContent); + + GameObject customEnemy = PrefabManager.Instance.CreateClonedPrefab(data.InternalName, data.PrefabToClone); + + Character character = customEnemy.GetComponent(); + if (character != null) + { + character.m_name = data.HoverName; + character.m_health = data.Health; + character.m_speed = data.Speed; + character.m_faction = Character.Faction.Boss; + } + + customEnemy.transform.localScale = new Vector3(data.Scale, data.Scale, data.Scale); + + PrefabManager.Instance.AddPrefab(customEnemy); + + Debug.Log($"Successfully injected {data.InternalName} into Valheim!"); + } + + public static List LoadTemplatesFromFolder() + { + List loadedTemplates = new List(); + + string configFolder = Path.Combine(MxValheimMod.internalDataPath, "UniqueEnemy"); + + if (!Directory.Exists(configFolder)) + { + Directory.CreateDirectory(configFolder); + return loadedTemplates; + } + + string[] files = Directory.GetFiles(configFolder, "*.json"); + + foreach (string file in files) + { + try + { + string json = File.ReadAllText(file); + EnemyTemplate template = JsonConvert.DeserializeObject(json); + + if (template != null) + { + loadedTemplates.Add(template); + Debug.Log($"Loaded template: {template.InternalName} from {Path.GetFileName(file)}"); + } + } + catch (Exception ex) + { + Debug.Log($"Error loading JSON file {file}: {ex.Message}"); + } + } + + return loadedTemplates; + } + + public static void ApplyEquipment(GameObject customEnemy, EnemyTemplate data) + { + Humanoid humanoid = customEnemy.GetComponent(); + if (humanoid == null) return; + + humanoid.m_defaultItems = new GameObject[0]; + + List equipmentList = new List(); + + void AddToGear(string prefabName) + { + if (string.IsNullOrEmpty(prefabName)) return; + GameObject item = ObjectDB.instance.GetItemPrefab(prefabName); + if (item != null) equipmentList.Add(item); + } + + AddToGear(data.Equipment.RightHand); + AddToGear(data.Equipment.LeftHand); + AddToGear(data.Equipment.Chest); + AddToGear(data.Equipment.Legs); + AddToGear(data.Equipment.Back); + + humanoid.m_defaultItems = equipmentList.ToArray(); + } + + public static void ApplyDropTable(GameObject customEnemy, EnemyTemplate data) + { + CharacterDrop dropComp = customEnemy.GetComponent(); + if (dropComp == null) + { + dropComp = customEnemy.AddComponent(); + } + + dropComp.m_drops.Clear(); + + foreach (var dropInfo in data.DropTable) + { + GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(dropInfo.ItemName); + + if (itemPrefab != null) + { + CharacterDrop.Drop newDrop = new CharacterDrop.Drop + { + m_prefab = itemPrefab, + m_amountMin = dropInfo.Min, + m_amountMax = dropInfo.Max, + m_chance = dropInfo.Chance, + m_onePerPlayer = dropInfo.OnePerPlayer + }; + + dropComp.m_drops.Add(newDrop); + } + } + } + + public static void ApplyStats(GameObject customEnemy, EnemyTemplate data) + { + Character character = customEnemy.GetComponent(); + if (character != null) + { + character.m_name = data.HoverName; + + if (!string.IsNullOrEmpty(data.Faction) && + Enum.TryParse(data.Faction, out Character.Faction factionEnum)) + { + character.m_faction = factionEnum; + } + else + { + character.m_faction = Character.Faction.Undead; + } + + typeof(Character).GetField("m_maxHealth", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + ?.SetValue(character, data.Health); + character.m_health = data.Health; + + typeof(Character).GetField("m_tamed", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + ?.SetValue(character, false); + + var tameable = customEnemy.GetComponent(); + if (tameable != null) + { + UnityEngine.Object.DestroyImmediate(tameable); + } + + var procreation = customEnemy.GetComponent(); + if (procreation != null) UnityEngine.Object.DestroyImmediate(procreation); + + character.m_speed = data.Speed; + character.m_runSpeed = data.Speed * 1.5f; + } + + BaseAI ai = customEnemy.GetComponent(); + if (ai != null && character != null) + { + typeof(BaseAI).GetField("m_character", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.SetValue(ai, character); + + typeof(BaseAI).GetField("m_aggravated", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.SetValue(ai, true); + + ai.m_viewRange = 50f; + } + + Rigidbody rb = customEnemy.GetComponent(); + if (rb != null) + { + rb.isKinematic = false; + rb.useGravity = true; + } + + ZNetView nview = customEnemy.GetComponent(); + if (nview != null) + { + nview.m_type = ZDO.ObjectType.Default; + nview.m_syncInitialScale = true; + } + + UnityEngine.AI.NavMeshAgent agent = customEnemy.GetComponent(); + if (agent != null) + { + agent.enabled = true; + agent.updatePosition = true; + agent.updateRotation = true; + agent.stoppingDistance = 1.0f; + } + + nview.GetZDO()?.Set("m_eventCreature", true); + + customEnemy.transform.localScale = Vector3.one * data.Scale; + if (nview != null) + { + nview.SetLocalScale(customEnemy.transform.localScale); + } + } + + public static void ApplyColorTint(GameObject customEnemy, EnemyTemplate data) + { + if (data.ColorTint == null) return; + + // Convert JSON floats to a Unity Color object + Color customColor = new Color(data.ColorTint.r, data.ColorTint.g, data.ColorTint.b); + + // Find all renderers (body, head, etc.) + var renderers = customEnemy.GetComponentsInChildren(); + + foreach (var renderer in renderers) + { + // We use 'material' (singular) to create a unique instance for THIS enemy + foreach (var mat in renderer.materials) + { + // '_Color' is the standard main tint property in most Valheim shaders + if (mat.HasProperty("_Color")) + { + mat.SetColor("_Color", customColor); + } + + // Optional: If you want them to GLOW slightly, set the Emission + if (mat.HasProperty("_EmissionColor")) + { + mat.SetColor("_EmissionColor", customColor * 0.5f); + mat.EnableKeyword("_EMISSION"); + } + } + } + } + } +} diff --git a/MxValheim/EventSystem/Reward.cs b/MxValheim/EventSystem/Reward.cs index 48833b5..260caa9 100644 --- a/MxValheim/EventSystem/Reward.cs +++ b/MxValheim/EventSystem/Reward.cs @@ -9,7 +9,7 @@ namespace MxValheim.EventSystem { internal class Reward { - public static int CalculateRewardCoin(int level, Heightmap.Biome biome) + public static int CalculateRewardCoin(int level, Heightmap.Biome biome, int kill) { Dictionary biomeBase = new Dictionary() { @@ -22,7 +22,7 @@ namespace MxValheim.EventSystem { 32, 70 }, }; - int rewardCoin = biomeBase[((int)biome)] * level; + int rewardCoin = (biomeBase[((int)biome)] * level) + (biomeBase[((int)biome)] * kill); return rewardCoin; } diff --git a/MxValheim/EventSystem/Watcher.cs b/MxValheim/EventSystem/Watcher.cs new file mode 100644 index 0000000..bb0e1cf --- /dev/null +++ b/MxValheim/EventSystem/Watcher.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace MxValheim.EventSystem +{ + internal class Watcher + { + public static int eventKill = 0; + + public static void GetEventKill(long sender, ZPackage pkg) + { + string eventName = pkg.ReadString(); + string eventPos = pkg.ReadString(); + + int evindex = MxValheimMod.ActiveEvents.FindIndex(e => e.Position.ToString() == eventPos); + + eventKill = MxValheimMod.ActiveEvents[evindex].Kills; + } + + public static void SetEventKill(long sender, ZPackage pkg) + { + string eventName = pkg.ReadString(); + string eventPos = pkg.ReadString(); + + int evindex = MxValheimMod.ActiveEvents.FindIndex(e => e.Position.ToString() == eventPos); + int evKill = MxValheimMod.ActiveEvents[evindex].Kills; + + MxValheimMod.ActiveEvents.RemoveAt(evindex); + int newKill = evKill+1; + eventKill = newKill; + + MxValheimMod.ActiveEventData newData = new MxValheimMod.ActiveEventData(eventName, eventPos, newKill); + MxValheimMod.ActiveEvents.Add(newData); + } + + public static void RegisterEvent(long sender, ZPackage pkg) + { + string eventName = pkg.ReadString(); + string eventPos = pkg.ReadString(); + + MxValheimMod.ActiveEventData newData = new MxValheimMod.ActiveEventData(eventName, eventPos, 1); + MxValheimMod.ActiveEvents.Add(newData); + } + + public static void RemoveEvent(long sender, ZPackage pkg) + { + string eventName = pkg.ReadString(); + string eventPos = pkg.ReadString(); + + int evindex = MxValheimMod.ActiveEvents.FindIndex(e => e.Position.ToString() == eventPos); + + eventKill = 0; + + MxValheimMod.ActiveEvents.RemoveAt(evindex); + } + } +} diff --git a/MxValheim/MxValheim.cs b/MxValheim/MxValheim.cs index 6bcc89b..5870607 100644 --- a/MxValheim/MxValheim.cs +++ b/MxValheim/MxValheim.cs @@ -1,6 +1,7 @@ using BepInEx; using BepInEx.Configuration; using HarmonyLib; +using Jotunn.Managers; using MxValheim.EventSystem; using MxValheim.KillFeed; using MxValheim.Patch; @@ -16,10 +17,15 @@ using System.Threading; using System.Xml; using TMPro; using UnityEngine; +using UnityEngine.EventSystems; using UnityEngine.UI; +using static MxValheim.EventSystem.RandomUnique.SpawnManager; +using static MxValheim.EventSystem.RandomUnique.UniqueManager; +using static MxValheim.EventSystem.Watcher; using static MxValheim.Patch.Doors_Patch; [BepInPlugin(ModGUID, ModName, ModVersion)] +[BepInDependency(Jotunn.Main.ModGuid)] public class MxValheimMod : BaseUnityPlugin { public static MxValheimMod Instance; // Singleton reference @@ -28,7 +34,7 @@ public class MxValheimMod : BaseUnityPlugin private const string ModGUID = "ovh.mxdev.mxvalheim"; private const string ModName = "MxValheim"; - private const string ModVersion = "1.6.5"; + private const string ModVersion = "1.6.6"; public static ConfigEntry Config_Locked; public static ConfigEntry Config_OreMultiplier; @@ -52,7 +58,6 @@ public class MxValheimMod : BaseUnityPlugin public static bool initExpBar = true; - // Data structures public class KillData { public string attackerName; @@ -85,8 +90,25 @@ public class MxValheimMod : BaseUnityPlugin public static readonly Queue _doorQueueName = new Queue(); public static float _doorTimer; + public static float _uniqueTimer = 60.0f; public static string eventName; + public class ActiveEventData + { + public string Name; + public string Position; + public int Kills; + + public ActiveEventData(string name, string pos, int kills) + { + Name = name; + Position = pos; + Kills = kills; + } + } + + public static List ActiveEvents = new List(); + void Awake() { Instance = this; @@ -105,10 +127,31 @@ public class MxValheimMod : BaseUnityPlugin _doorTimer = MxValheimMod.Config_autoDoorClose.Value; + //PrefabManager.OnVanillaPrefabsAvailable += InitCustomEnemies; + Harmony harmony = new Harmony(ModGUID); harmony.PatchAll(); } + private void InitCustomEnemies() + { + List templates = LoadTemplatesFromFolder(); + + foreach (var data in templates) + { + GameObject customEnemy = PrefabManager.Instance.CreateClonedPrefab(data.InternalName, data.PrefabToClone); + + ApplyStats(customEnemy, data); + ApplyColorTint(customEnemy, data); + ApplyDropTable(customEnemy, data); + ApplyEquipment(customEnemy, data); + + RegisterWithWorldSpawn(customEnemy, data); + } + + PrefabManager.OnVanillaPrefabsAvailable -= InitCustomEnemies; + } + [HarmonyPatch(typeof(Localization), nameof(Localization.SetupLanguage))] public static class Localization_SetupLanguage_Patch { @@ -118,7 +161,6 @@ public class MxValheimMod : BaseUnityPlugin } } - // --- TEST COMMAND: Type 'testkill' in F5 console --- [HarmonyPatch(typeof(Terminal), nameof(Terminal.InputText))] public static class ConsoleInputPatch { @@ -139,10 +181,14 @@ public class MxValheimMod : BaseUnityPlugin { if (ZRoutedRpc.instance != null && kfp != null) { - // We use the explicit 'Method' delegate to avoid the ArgumentException ZRoutedRpc.instance.Register("RPC_MxKillMsg", new RoutedMethod.Method(kfp.OnReceiveKillMsg)); + ZRoutedRpc.instance.Register("RPC_ServerRegisterEvent", Watcher.RegisterEvent); + ZRoutedRpc.instance.Register("RPC_ServerGetEventKill", Watcher.GetEventKill); + ZRoutedRpc.instance.Register("RPC_ServerSetEventKill", Watcher.SetEventKill); + ZRoutedRpc.instance.Register("RPC_ServerRemoveEvent", Watcher.RemoveEvent); + ZRoutedRpc.instance.Register("RPC_ServerRegisterEventKill", Profiles.ServerSetLevel); ZRoutedRpc.instance.Register("RPC_ServerCreateUser", Profiles.ServerReceiveCreateUser); ZRoutedRpc.instance.Register("RPC_RequestProfile", Profiles.ServerHandleProfileRequest); ZRoutedRpc.instance.Register("RPC_ClientReceiveProfile", Profiles.LoadProfile); @@ -193,13 +239,13 @@ public class MxValheimMod : BaseUnityPlugin string timeString = clk.GetFormattedTime(); int day = EnvMan.instance.GetDay(); string pid = GetUserPlayerID(Player.m_localPlayer).ToString(); - RandomEvent activeEvent = RandEventSystem.instance.GetActiveEvent(); - if ((activeEvent == null)) + RandomEvent activeEvent = RandEventSystem.instance.GetActiveEvent(); + if (activeEvent == null) { Clock.m_textComponent.text = $"{Localization.instance.Localize("$clock_string_rank")} {Profiles.serverLevel} | {Localization.instance.Localize("$clock_string_day")} {day} | {timeString}"; } else { - Clock.m_textComponent.text = $"{eventName} | {Localization.instance.Localize("$clock_string_rank")} {Profiles.serverLevel} | {Localization.instance.Localize("$clock_string_day")} {day} | {timeString}"; + Clock.m_textComponent.text = $"{eventName} | {Localization.instance.Localize("$clock_string_kill")} {eventKill} | {Localization.instance.Localize("$clock_string_rank")} {Profiles.serverLevel} | {Localization.instance.Localize("$clock_string_day")} {day} | {timeString}"; } } } diff --git a/MxValheim/MxValheim.csproj b/MxValheim/MxValheim.csproj index 103b8e2..5889c55 100644 --- a/MxValheim/MxValheim.csproj +++ b/MxValheim/MxValheim.csproj @@ -55,6 +55,9 @@ E:\SteamLibrary\steamapps\common\Valheim\Valheim_Data\Managed\gui_framework.dll + + E:\SteamLibrary\steamapps\common\Valheim\BepInEx\plugins\Jotunn.dll + E:\SteamLibrary\steamapps\common\Valheim\BepInEx\plugins\Newtonsoft.Json.dll @@ -75,6 +78,10 @@ E:\SteamLibrary\steamapps\common\Valheim\Valheim_Data\Managed\UnityEngine.dll + + False + E:\SteamLibrary\steamapps\common\Valheim\Valheim_Data\Managed\UnityEngine.AIModule.dll + False E:\SteamLibrary\steamapps\common\Valheim\Valheim_Data\Managed\UnityEngine.AssetBundleModule.dll @@ -110,7 +117,10 @@ + + + diff --git a/MxValheim/Patch/HUD/Clock.cs b/MxValheim/Patch/HUD/Clock.cs index dbe84db..339ee99 100644 --- a/MxValheim/Patch/HUD/Clock.cs +++ b/MxValheim/Patch/HUD/Clock.cs @@ -15,13 +15,10 @@ namespace MxValheim.Patch.HUD public static Text m_textComponent; public static RectTransform m_barFill; - // This replaces the missing GetTimeString() method public string GetFormattedTime() { - // Get fractional day (0.0 to 1.0) float fraction = EnvMan.instance.GetDayFraction(); - // Convert to hours and minutes float totalHours = fraction * 24f; int hours = Mathf.FloorToInt(totalHours); int minutes = Mathf.FloorToInt((totalHours - hours) * 60f); @@ -32,25 +29,20 @@ namespace MxValheim.Patch.HUD [HarmonyPatch(typeof(MessageHud), nameof(MessageHud.ShowMessage))] public static class HideEventMessage_Patch { - // We use a Prefix to check the message before it is displayed static bool Prefix(MessageHud.MessageType type, string text) { - // Check if a raid is active if (RandEventSystem.instance != null) { RandomEvent activeEvent = RandEventSystem.instance.GetActiveEvent(); if (activeEvent != null) { - // If the message text matches the start or end message of the raid, block it if (text == activeEvent.GetHudText()) { - Debug.Log("Reached msg logic!!!"); - // Returning false tells Harmony to skip the original method (don't show text) return false; } } } - return true; // Show all other messages (health, items, etc.) normally + return true; } }