397 lines
15 KiB
C#
397 lines
15 KiB
C#
using HarmonyLib;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Controls;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using static MxValheimMod;
|
|
using Canvas = UnityEngine.Canvas;
|
|
using Image = UnityEngine.UI.Image;
|
|
|
|
namespace MxValheim.EventSystem
|
|
{
|
|
internal class EventSystem_Patch
|
|
{
|
|
public static GameObject _eventRoot;
|
|
public static Text _eventTitleText;
|
|
public static Text _eventProgressText;
|
|
public static Image _progressBar;
|
|
public static CanvasGroup _canvasGroup;
|
|
|
|
// --- STATE DATA ---
|
|
public enum RaidPhase
|
|
{
|
|
None,
|
|
Scouts,
|
|
EliteGuard,
|
|
BossInbound,
|
|
Completed
|
|
}
|
|
public static RaidPhase CurrentPhase = RaidPhase.None;
|
|
public static int KillsInCurrentPhase = 0;
|
|
// --- 1. HUD CREATION (Your Style) ---
|
|
public static void CreateEventHud()
|
|
{
|
|
if (_eventRoot != null) return;
|
|
|
|
_eventRoot = new GameObject("MxEventHUD_Root");
|
|
UnityEngine.Object.DontDestroyOnLoad(_eventRoot);
|
|
|
|
Canvas c = _eventRoot.AddComponent<Canvas>();
|
|
c.renderMode = RenderMode.ScreenSpaceOverlay;
|
|
c.sortingOrder = 9999;
|
|
|
|
CanvasScaler scaler = _eventRoot.AddComponent<CanvasScaler>();
|
|
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
|
scaler.referenceResolution = new Vector2(1920, 1080);
|
|
|
|
_canvasGroup = _eventRoot.AddComponent<CanvasGroup>();
|
|
_canvasGroup.alpha = 0f;
|
|
|
|
// Background Panel (Positioned -90 to sit below KillFeed at -30)
|
|
GameObject panel = new GameObject("Background", typeof(RectTransform), typeof(Image));
|
|
panel.transform.SetParent(_eventRoot.transform, false);
|
|
Image panelImage = panel.GetComponent<Image>();
|
|
panelImage.color = new Color(0, 0, 0, 0.7f);
|
|
|
|
// Styling with Valheim Assets
|
|
foreach (Sprite s in Resources.FindObjectsOfTypeAll<Sprite>())
|
|
{
|
|
if (s.name == "item_background")
|
|
{
|
|
panelImage.sprite = s;
|
|
panelImage.type = Image.Type.Sliced;
|
|
break;
|
|
}
|
|
}
|
|
|
|
RectTransform pRect = panel.GetComponent<RectTransform>();
|
|
pRect.anchorMin = pRect.anchorMax = pRect.pivot = new Vector2(0.5f, 1f);
|
|
pRect.anchoredPosition = new Vector2(0, -90); // Stacks below your KillFeed
|
|
pRect.sizeDelta = new Vector2(400, 55);
|
|
|
|
VerticalLayoutGroup layout = panel.AddComponent<VerticalLayoutGroup>();
|
|
layout.childAlignment = TextAnchor.MiddleCenter;
|
|
layout.spacing = 2f;
|
|
layout.padding = new RectOffset(10, 10, 5, 5);
|
|
|
|
// Title
|
|
_eventTitleText = CreateText(panel.transform, "RAID STATUS", 14, Color.yellow);
|
|
|
|
// Progress Bar
|
|
GameObject barBg = new GameObject("BarBG", typeof(RectTransform), typeof(Image));
|
|
barBg.transform.SetParent(panel.transform, false);
|
|
barBg.GetComponent<Image>().color = new Color(0.1f, 0.1f, 0.1f, 0.9f);
|
|
barBg.GetComponent<RectTransform>().sizeDelta = new Vector2(350, 8);
|
|
|
|
GameObject fillObj = new GameObject("Fill", typeof(RectTransform), typeof(Image));
|
|
fillObj.transform.SetParent(barBg.transform, false);
|
|
_progressBar = fillObj.GetComponent<Image>();
|
|
_progressBar.color = Color.red;
|
|
_progressBar.type = Image.Type.Filled;
|
|
_progressBar.fillMethod = Image.FillMethod.Horizontal;
|
|
_progressBar.GetComponent<RectTransform>().anchorMin = Vector2.zero;
|
|
_progressBar.GetComponent<RectTransform>().anchorMax = Vector2.one;
|
|
_progressBar.GetComponent<RectTransform>().sizeDelta = Vector2.zero;
|
|
|
|
// Progress Text
|
|
_eventProgressText = CreateText(panel.transform, "0 / 0", 11, Color.white);
|
|
}
|
|
|
|
private static Text CreateText(Transform parent, string content, int size, Color col)
|
|
{
|
|
GameObject txtObj = new GameObject("Text", typeof(Text));
|
|
txtObj.transform.SetParent(parent, false);
|
|
Text t = txtObj.GetComponent<Text>();
|
|
t.text = content;
|
|
t.fontSize = size;
|
|
t.color = col;
|
|
t.alignment = TextAnchor.MiddleCenter;
|
|
|
|
Font valFont = null;
|
|
foreach (Font f in Resources.FindObjectsOfTypeAll<Font>())
|
|
if (f.name == "AveriaSerifLibre-Bold") valFont = f;
|
|
t.font = valFont ?? Resources.GetBuiltinResource<Font>("Arial.ttf");
|
|
|
|
return t;
|
|
}
|
|
|
|
// --- 2. WAVE LOGIC ---
|
|
public static void UpdateWave(bool reset = false)
|
|
{
|
|
Debug.Log($"MxEvent:UpdateWave Seen a wave start.");
|
|
if (reset)
|
|
{
|
|
CurrentPhase = RaidPhase.Scouts;
|
|
KillsInCurrentPhase = 0;
|
|
}
|
|
|
|
int target = GetTargetForPhase(CurrentPhase);
|
|
|
|
Color barColor = (CurrentPhase == RaidPhase.Scouts) ? Color.magenta : Color.red;
|
|
|
|
UpdateDisplay($"PHASE: {CurrentPhase}", KillsInCurrentPhase, target, barColor);
|
|
}
|
|
|
|
public static int GetTargetForPhase(RaidPhase phase)
|
|
{
|
|
switch (phase)
|
|
{
|
|
case RaidPhase.Scouts: return 5; // 5 kills to clear scouts
|
|
case RaidPhase.EliteGuard: return 10; // 10 kills for elite guard
|
|
case RaidPhase.BossInbound: return 1; // Just the boss
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
public static void AdvancePhase()
|
|
{
|
|
KillsInCurrentPhase = 0; // Reset counter for the new phase
|
|
|
|
if (CurrentPhase == RaidPhase.Scouts) CurrentPhase = RaidPhase.EliteGuard;
|
|
else if (CurrentPhase == RaidPhase.EliteGuard) CurrentPhase = RaidPhase.BossInbound;
|
|
else CurrentPhase = RaidPhase.Completed;
|
|
|
|
if (CurrentPhase == RaidPhase.Scouts)
|
|
{
|
|
// Spawn Eikthyr at the player's location
|
|
GameObject boarPrefab = ZNetScene.instance.GetPrefab("Boar");
|
|
if (boarPrefab != null)
|
|
{
|
|
UnityEngine.Object.Instantiate(boarPrefab, Player.m_localPlayer.transform.position + Vector3.forward * 20f, Quaternion.identity);
|
|
}
|
|
}
|
|
|
|
if (CurrentPhase == RaidPhase.BossInbound)
|
|
{
|
|
// Spawn Eikthyr at the player's location
|
|
GameObject bossPrefab = ZNetScene.instance.GetPrefab("Eikthyr");
|
|
if (bossPrefab != null)
|
|
{
|
|
UnityEngine.Object.Instantiate(bossPrefab, Player.m_localPlayer.transform.position + Vector3.forward * 10f, Quaternion.identity);
|
|
}
|
|
}
|
|
|
|
Debug.Log($"MxEvent: Advancing to {CurrentPhase}");
|
|
|
|
// Play a sound to notify the player (The 'Quest Update' sound)
|
|
Player.m_localPlayer?.Message(MessageHud.MessageType.Center, $"$inventory_newphase: {CurrentPhase}");
|
|
|
|
if (CurrentPhase == RaidPhase.Completed)
|
|
{
|
|
Debug.Log("MxEvent: All phases complete. Ending vanilla raid.");
|
|
EndRaidManually();
|
|
}
|
|
}
|
|
|
|
public static void UpdateDisplay(string title, int curr, int total, Color col)
|
|
{
|
|
Debug.Log($"MxEvent:UpdateDisplay Something reached my display update logic.");
|
|
if (_eventRoot == null) CreateEventHud();
|
|
_eventRoot.SetActive(true);
|
|
_canvasGroup.alpha = 1f;
|
|
|
|
_eventTitleText.text = title;
|
|
_eventProgressText.text = $"{curr} / {total}";
|
|
_progressBar.fillAmount = (float)curr / total;
|
|
_progressBar.color = col;
|
|
}
|
|
|
|
public static void EndRaidManually()
|
|
{
|
|
if (RandEventSystem.instance != null)
|
|
{
|
|
// This tells Valheim the time is up
|
|
RandomEvent activeEvent = RandEventSystem.instance.GetActiveEvent();
|
|
if (activeEvent != null)
|
|
{
|
|
activeEvent.m_time = 0;
|
|
}
|
|
}
|
|
FadeOutHud(); // Start your fade out
|
|
}
|
|
|
|
// --- 3. HARMONY PATCHES ---
|
|
|
|
[HarmonyPatch(typeof(RandEventSystem), nameof(RandEventSystem.FixedUpdate))]
|
|
public static class DebugRaidFrequency
|
|
{
|
|
static void Prefix(RandEventSystem __instance)
|
|
{
|
|
// Use a small timer check so we don't spam the logic every single frame
|
|
// though for debugging, forcing these values is fine.
|
|
__instance.m_eventIntervalMin = 10f;
|
|
__instance.m_eventChance = 100f;
|
|
}
|
|
}
|
|
|
|
[HarmonyPatch(typeof(RandEventSystem), nameof(RandEventSystem.FixedUpdate))]
|
|
public static class TimerFreezePatch
|
|
{
|
|
static void Prefix(RandEventSystem __instance)
|
|
{
|
|
RandomEvent activeEvent = __instance.GetActiveEvent();
|
|
|
|
// If we are in our custom phases and haven't reached "Completed" yet
|
|
if (activeEvent != null && CurrentPhase != RaidPhase.Completed)
|
|
{
|
|
// Valheim subtracts time in FixedUpdate.
|
|
// We add it back using the engine's global fixedDeltaTime.
|
|
activeEvent.m_time += Time.fixedDeltaTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure HUD is ready when the game UI loads
|
|
[HarmonyPatch(typeof(Hud), nameof(Hud.Awake))]
|
|
[HarmonyPostfix]
|
|
static void InitHud() => CreateEventHud();
|
|
|
|
[HarmonyPatch(typeof(Hud), nameof(Hud.Update))]
|
|
[HarmonyPostfix]
|
|
static void CheckForExistingRaid()
|
|
{
|
|
// If the HUD isn't visible but a raid IS active, force it to show
|
|
if (CurrentPhase == RaidPhase.None && RandEventSystem.instance.GetActiveEvent() != null)
|
|
{
|
|
Debug.Log("MxEvent: Detected existing raid on HUD update. Synchronizing...");
|
|
UpdateWave(true);
|
|
}
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Hud), nameof(Hud.Update))]
|
|
public static class Hud_MonitorPatch
|
|
{
|
|
private static RandomEvent _lastKnownEvent = null;
|
|
|
|
static void Postfix()
|
|
{
|
|
// 1. Safety check: Ensure the system exists
|
|
if (RandEventSystem.instance == null) return;
|
|
|
|
RandomEvent activeEvent = RandEventSystem.instance.GetActiveEvent();
|
|
|
|
// 2. Logic: Detection
|
|
if (activeEvent != null && _lastKnownEvent == null)
|
|
{
|
|
Debug.Log($"MxEvent: HUD Heartbeat detected Raid Start: {activeEvent.m_name}");
|
|
_lastKnownEvent = activeEvent;
|
|
|
|
// Force HUD Creation and Display
|
|
UpdateWave(true);
|
|
}
|
|
else if (activeEvent == null && _lastKnownEvent != null)
|
|
{
|
|
Debug.Log("MxEvent: HUD Heartbeat detected Raid End.");
|
|
_lastKnownEvent = null;
|
|
FadeOutHud();
|
|
}
|
|
}
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Character), nameof(Character.ApplyDamage))]
|
|
public static class DeathNotifier
|
|
{
|
|
static void Postfix(Character __instance)
|
|
{
|
|
// Check if the character is dead or just died
|
|
if (__instance.GetHealth() <= 0f)
|
|
{
|
|
ZNetView nview = __instance.GetComponent<ZNetView>();
|
|
|
|
// 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
|
|
RandomEvent activeEvent = RandEventSystem.instance.GetActiveEvent();
|
|
if (activeEvent == null) return;
|
|
Debug.Log("MxEvent: Confirmed Active Event!");
|
|
|
|
Vector3 playerPos = Player.m_localPlayer.transform.position;
|
|
float distance = Vector3.Distance(playerPos, activeEvent.m_pos);
|
|
Debug.Log($"MxEvent: Distance: {distance}");
|
|
|
|
if (distance <= 96f)
|
|
{
|
|
KillsInCurrentPhase++;
|
|
int target = GetTargetForPhase(CurrentPhase);
|
|
|
|
if (KillsInCurrentPhase >= target && CurrentPhase != RaidPhase.Completed)
|
|
{
|
|
AdvancePhase();
|
|
}
|
|
|
|
// Send the update
|
|
ZRoutedRpc.instance.InvokeRoutedRPC(
|
|
ZRoutedRpc.Everybody,
|
|
"RPC_UpdateRaidHUD",
|
|
$"PHASE: {CurrentPhase}",
|
|
KillsInCurrentPhase,
|
|
GetTargetForPhase(CurrentPhase)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static bool IsEventCreature(Character character)
|
|
{
|
|
ZNetView nview = character.GetComponent<ZNetView>();
|
|
if (nview == null || nview.GetZDO() == null) return false;
|
|
|
|
// Layer 1: Check the networked boolean (The standard way)
|
|
if (nview.GetZDO().GetBool("eventcreature")) return true;
|
|
|
|
// Layer 2: Check the BaseAI component (The local way)
|
|
BaseAI ai = character.GetComponent<BaseAI>();
|
|
if (ai != null)
|
|
{
|
|
bool isRaidMob = (bool)AccessTools.Field(typeof(BaseAI), "m_eventCreature").GetValue(ai);
|
|
if (isRaidMob) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Hide HUD when raid ends
|
|
[HarmonyPatch(typeof(RandEventSystem), nameof(RandEventSystem.ResetRandomEvent))]
|
|
[HarmonyPostfix]
|
|
static void OnRaidEnd()
|
|
{
|
|
FadeOutHud();
|
|
CurrentPhase = RaidPhase.None;
|
|
}
|
|
|
|
private static IEnumerator FadeOutHud()
|
|
{
|
|
Debug.Log("MxEvent: Starting Fade Out...");
|
|
|
|
// Safety check for the component
|
|
if (_canvasGroup == null)
|
|
{
|
|
_eventRoot.SetActive(false);
|
|
yield break;
|
|
}
|
|
|
|
float startAlpha = _canvasGroup.alpha;
|
|
float rate = 1.0f / 1.5f; // Fade over 1.5 seconds
|
|
float progress = 0.0f;
|
|
|
|
while (progress < 1.0f)
|
|
{
|
|
progress += Time.deltaTime * rate;
|
|
_canvasGroup.alpha = Mathf.Lerp(startAlpha, 0, progress);
|
|
yield return null;
|
|
}
|
|
|
|
_canvasGroup.alpha = 0;
|
|
_eventRoot.SetActive(false); // Fully disable to ensure it's gone
|
|
Debug.Log("MxEvent: HUD Hidden and Deactivated.");
|
|
}
|
|
}
|
|
}
|