252 lines
9.7 KiB
C#
252 lines
9.7 KiB
C#
using BepInEx;
|
|
using BepInEx.Configuration;
|
|
using HarmonyLib;
|
|
using MxValheim.KillFeed;
|
|
using Newtonsoft.Json;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Threading;
|
|
using System.Xml;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
[BepInPlugin(ModGUID, ModName, ModVersion)]
|
|
public class MxValheimMod : BaseUnityPlugin
|
|
{
|
|
public static MxValheimMod Instance; // Singleton reference
|
|
public static KillFeed_Patch kfp = new KillFeed_Patch();
|
|
|
|
private const string ModGUID = "ovh.mxdev.mxvalheim";
|
|
private const string ModName = "MxValheim";
|
|
private const string ModVersion = "1.5.5";
|
|
|
|
public static ConfigEntry<bool> Config_Locked;
|
|
public static ConfigEntry<int> Config_OreMultiplier;
|
|
public static ConfigEntry<float> Config_rangeMultiplier;
|
|
public static ConfigEntry<float> Config_bowDrawSpeedBonusPerLevel;
|
|
public static ConfigEntry<bool> Config_rainDamage;
|
|
public static ConfigEntry<float> Config_boatSpeed;
|
|
public static ConfigEntry<bool> Config_autoDoorCloseEnabled;
|
|
public static ConfigEntry<float> Config_autoDoorClose;
|
|
|
|
public static string modPath = Path.Combine(Paths.PluginPath, "MxValheim");
|
|
public static string internalConfigsPath = Path.Combine(modPath, "Configs");
|
|
private static string WeightConfigPath => Path.Combine(internalConfigsPath, "items_weight.json");
|
|
public static Dictionary<string, float> WeightSettings = new Dictionary<string, float>();
|
|
|
|
// Data structures
|
|
public class KillData
|
|
{
|
|
public string attackerName;
|
|
public string weaponPrefabName;
|
|
public int victimLevel;
|
|
public bool isBoss;
|
|
}
|
|
|
|
public static GameObject _hudRoot;
|
|
public static Text _killText;
|
|
public static Image _weaponIconSlot;
|
|
public static Image _victimIconSlot;
|
|
public static Image _panelImage;
|
|
public static Outline _border;
|
|
public static Color borderColor = Color.white;
|
|
public static CanvasGroup _canvasGroup;
|
|
public static float _fadeDuration = 0.5f;
|
|
public static float _displayTimer;
|
|
|
|
public static readonly Queue<string> _msgQueue = new Queue<string>();
|
|
public static readonly Queue<Color> _borderColQueue = new Queue<Color>();
|
|
public static readonly Queue<Sprite> _iconQueue = new Queue<Sprite>();
|
|
public static readonly Queue<Sprite> _victimIconQueue = new Queue<Sprite>();
|
|
public static readonly Dictionary<Character, KillData> _activeTrackers = new Dictionary<Character, KillData>();
|
|
|
|
void Awake()
|
|
{
|
|
Instance = this;
|
|
|
|
Config_OreMultiplier = Config.Bind("General","OreMultiplier",3,"How many items should drop for every 1 ore/scrap found.");
|
|
Config_rangeMultiplier = Config.Bind("General", "CraftingRangeMultiplier",2.0f,"Multiplier for the workbench build/crafting range. Default is 2x.");
|
|
Config_bowDrawSpeedBonusPerLevel = Config.Bind("General", "BowDrawSpeedBonusPercentPerLevel", 1.0f, "Shorten the bow draw speed by this percent for every bow upgrade level.");
|
|
Config_rainDamage = Config.Bind("General", "RainDamage", true, "Set to true to stop rain damage, false to return to vanilla behavior.");
|
|
Config_boatSpeed = Config.Bind("General", "BoatSpeedMultiplier", 2.0f, "Your boat/raft will move without wind at a speed multiplied by this value.");
|
|
Config_autoDoorCloseEnabled = Config.Bind("General", "AutoDoorCloseEnabled", true, "Your doors will auto close if enabled. See AutoDoorCloseTimer for the desired time.");
|
|
Config_autoDoorClose = Config.Bind("General", "AutoDoorCloseTimer", 5.0f, "Your doors will auto close after the specified timer duration.");
|
|
|
|
LoadLocalization();
|
|
LoadJsonConfig();
|
|
|
|
Harmony harmony = new Harmony(ModGUID);
|
|
harmony.PatchAll();
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Localization), nameof(Localization.SetupLanguage))]
|
|
public static class Localization_SetupLanguage_Patch
|
|
{
|
|
public static void Postfix()
|
|
{
|
|
LoadLocalization();
|
|
}
|
|
}
|
|
|
|
// --- TEST COMMAND: Type 'testkill' in F5 console ---
|
|
[HarmonyPatch(typeof(Terminal), nameof(Terminal.InputText))]
|
|
public static class ConsoleInputPatch
|
|
{
|
|
static void Postfix(Terminal __instance)
|
|
{
|
|
string text = __instance.m_input.text;
|
|
if (text.ToLower() == "listicons")
|
|
{
|
|
var spriteAsset = Resources.FindObjectsOfTypeAll<TMP_SpriteAsset>().FirstOrDefault(x => x.name == "icons"); ;
|
|
|
|
if (spriteAsset != null)
|
|
{
|
|
Debug.Log($"--- Listing all sprites in {spriteAsset.name} ---");
|
|
for (int i = 0; i < spriteAsset.spriteCharacterTable.Count; i++)
|
|
{
|
|
var sprite = spriteAsset.spriteCharacterTable[i];
|
|
Debug.Log($"Index: {i} | Name: {sprite.name}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Game), nameof(Game.Start))]
|
|
public static class GameStartPatch
|
|
{
|
|
static void Postfix()
|
|
{
|
|
if (ZRoutedRpc.instance != null && kfp != null)
|
|
{
|
|
// We use the explicit 'Method' delegate to avoid the ArgumentException
|
|
ZRoutedRpc.instance.Register<string, string, string, string, int>("RPC_MxKillMsg",
|
|
new RoutedMethod<string, string, string, string, int>.Method(kfp.OnReceiveKillMsg));
|
|
|
|
Debug.Log("MxValheimMod: RPC Registered successfully with explicit delegate.");
|
|
}
|
|
}
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
// Use the game's native check for dedicated servers
|
|
ZNet zn = new ZNet();
|
|
if (zn.IsDedicated() || Player.m_localPlayer == null) return;
|
|
|
|
if (_msgQueue == null || _msgQueue.Count == 0 && _displayTimer <= 0) return;
|
|
|
|
// Logic to trigger next message
|
|
if (_msgQueue.Count > 0 && _displayTimer <= 0)
|
|
{
|
|
borderColor = new Color(0.141f, 0.141f, 0.153f);
|
|
kfp.ShowNextMessage(_msgQueue.Dequeue(), _iconQueue.Dequeue(), _victimIconQueue.Dequeue(), _borderColQueue.Dequeue());
|
|
}
|
|
|
|
if (_displayTimer > 0)
|
|
{
|
|
_displayTimer -= Time.deltaTime;
|
|
|
|
if (_canvasGroup != null && _hudRoot != null)
|
|
{
|
|
RectTransform pRect = _panelImage.GetComponent<RectTransform>();
|
|
|
|
// FADE AND SLIDE LOGIC
|
|
if (_displayTimer > 4.5f)
|
|
{ // Fading in (first 0.5s)
|
|
_canvasGroup.alpha = (5f - _displayTimer) * 2;
|
|
}
|
|
else if (_displayTimer < 1f)
|
|
{ // Fading out (last 1s)
|
|
_canvasGroup.alpha = _displayTimer;
|
|
}
|
|
else
|
|
{
|
|
_canvasGroup.alpha = 1f;
|
|
pRect.anchoredPosition = new Vector2(0, -40);
|
|
}
|
|
}
|
|
|
|
if (_displayTimer <= 0 && _hudRoot != null) _hudRoot.SetActive(false);
|
|
}
|
|
|
|
}
|
|
|
|
public static void LoadLocalization()
|
|
{
|
|
if (Localization.instance == null) return;
|
|
|
|
string modPath = Path.Combine(Paths.PluginPath, "MxValheim");
|
|
string translationsPath = Path.Combine(modPath, "Translations");
|
|
string lang = Localization.instance.GetSelectedLanguage();
|
|
string filePath = Path.Combine(translationsPath, $"{lang}.json");
|
|
|
|
if (!File.Exists(filePath))
|
|
{
|
|
filePath = Path.Combine(translationsPath, "English.json");
|
|
}
|
|
|
|
if (File.Exists(filePath))
|
|
{
|
|
try
|
|
{
|
|
string json = File.ReadAllText(filePath);
|
|
var dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
|
|
|
|
// Get the method via Reflection to bypass "Inaccessible" errors
|
|
MethodInfo addWordMethod = typeof(Localization).GetMethod("AddWord",
|
|
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
|
|
if (addWordMethod != null)
|
|
{
|
|
foreach (var entry in dict)
|
|
{
|
|
// Parameters: (instance to run on, array of arguments)
|
|
addWordMethod.Invoke(Localization.instance, new object[] { entry.Key, entry.Value });
|
|
}
|
|
Debug.Log($"[MxValheim] Successfully injected {dict.Count} strings for {lang}.");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("[MxValheim] Critical Error: Could not find AddWord method in game code.");
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"[MxValheim] Error loading JSON: {e.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool LoadJsonConfig()
|
|
{
|
|
try
|
|
{
|
|
if (!File.Exists(WeightConfigPath))
|
|
{
|
|
WeightSettings = new Dictionary<string, float> { { "Wood", 1.0f }, { "Stone", 1.0f } };
|
|
File.WriteAllText(WeightConfigPath, JsonConvert.SerializeObject(WeightSettings, Newtonsoft.Json.Formatting.Indented));
|
|
return true;
|
|
}
|
|
|
|
string json = File.ReadAllText(WeightConfigPath);
|
|
WeightSettings = JsonConvert.DeserializeObject<Dictionary<string, float>>(json);
|
|
Logger.LogInfo($"Successfully parsed {WeightSettings.Count} items.");
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogWarning($"Could not read JSON (might be busy): {ex.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal static void ShowKillMessage(string v)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
} |