diff --git a/MiraAPI.Example/ExampleRole.cs b/MiraAPI.Example/ExampleRole.cs new file mode 100644 index 0000000..6de70f2 --- /dev/null +++ b/MiraAPI.Example/ExampleRole.cs @@ -0,0 +1,16 @@ +using MiraAPI.Roles; +using MiraAPI.Utilities.Assets; +using UnityEngine; + +namespace MiraAPI.Example; + +[RegisterCustomRole] +public class ExampleRole : ImpostorRole, ICustomRole +{ + public string RoleName => "fortnite killer"; + public string RoleLongDescription => "ok so your objective is to eliminate everyone \nwith your fortnite tactical shotgun"; + public string RoleDescription => RoleLongDescription; + public Color RoleColor => Palette.Orange; + public ModdedRoleTeams Team => ModdedRoleTeams.Impostor; + public LoadableAsset OptionsScreenshot => MiraAssets.Empty; +} \ No newline at end of file diff --git a/MiraAPI.Example/MiraDebugWindow.cs b/MiraAPI.Example/MiraDebugWindow.cs index 34bbb64..5fa9a08 100644 --- a/MiraAPI.Example/MiraDebugWindow.cs +++ b/MiraAPI.Example/MiraDebugWindow.cs @@ -15,11 +15,11 @@ public class MiraDebugWindow : MonoBehaviour { if (GUILayout.Button("Test modifier")) { - PlayerControl.LocalPlayer.AddModifier(); + PlayerControl.LocalPlayer.AddModifier(); } if (GUILayout.Button("Remove modifier")) { - PlayerControl.LocalPlayer.RemoveModifier(); + PlayerControl.LocalPlayer.RemoveModifier(); } }); diff --git a/MiraAPI.Example/ModifierExample.cs b/MiraAPI.Example/ModifierExample.cs index bb2ecb7..aea6b77 100644 --- a/MiraAPI.Example/ModifierExample.cs +++ b/MiraAPI.Example/ModifierExample.cs @@ -5,7 +5,7 @@ namespace MiraAPI.Example; [RegisterModifier] public class ModifierExample : GameModifier { - public override string ModifierName => "Example"; + public override string ModifierName => "Example Modifier"; public override bool CanVent() { diff --git a/MiraAPI.Example/ModifierTimerExample.cs b/MiraAPI.Example/ModifierTimerExample.cs new file mode 100644 index 0000000..04d810b --- /dev/null +++ b/MiraAPI.Example/ModifierTimerExample.cs @@ -0,0 +1,20 @@ +using MiraAPI.Modifiers.Types; + +namespace MiraAPI.Example; + +[RegisterModifier] +public class ModifierTimerExample : TimedModifier +{ + public override string ModifierName => "Color Changer"; + public override bool HideOnUi => true; + public override float Duration => 10f; + public override bool AutoStart => true; + + private System.Random _rand = new(); + + public override void OnTimerComplete() + { + Player.SetColor((byte)_rand.Next(0, Palette.PlayerColors.Count)); + StartTimer(); + } +} diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs index 61f27bb..2e2f107 100644 --- a/MiraAPI/Modifiers/BaseModifier.cs +++ b/MiraAPI/Modifiers/BaseModifier.cs @@ -2,11 +2,13 @@ public abstract class BaseModifier { public abstract string ModifierName { get; } + public virtual bool HideOnUi => false; public PlayerControl Player { get; internal set; } public uint ModifierId { get; internal set; } public virtual void OnActivate() { } public virtual void OnDeactivate() { } public virtual void Update() { } + public virtual void FixedUpdate() { } public virtual void OnDeath() { } public virtual bool CanVent() => Player.Data.Role.CanVent; } \ No newline at end of file diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index 32e20ce..f226057 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -6,6 +6,9 @@ using Reactor.Utilities; using Reactor.Utilities.Attributes; using System; using System.Collections.Generic; +using System.Linq; +using System.Text; +using TMPro; using UnityEngine; namespace MiraAPI.Modifiers; @@ -15,12 +18,27 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr) { public List ActiveModifiers { get; private set; } - public PlayerControl player; + public PlayerControl Player; + + private TextMeshPro _modifierText; public void Start() { - player = GetComponent(); + Player = GetComponent(); ActiveModifiers = new List(); + + if (!Player.AmOwner) return; + + _modifierText = Helpers.CreateTextLabel("ModifierText", HudManager.Instance.transform, AspectPosition.EdgeAlignments.RightTop, new Vector3(10.1f, 3.5f, 0), textAlignment: TextAlignmentOptions.Right); + _modifierText.verticalAlignment = VerticalAlignmentOptions.Top; + } + + public void FixedUpdate() + { + foreach (var modifier in ActiveModifiers) + { + modifier.FixedUpdate(); + } } public void Update() @@ -29,6 +47,26 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr) { modifier.Update(); } + + var filteredModifiers = ActiveModifiers.Where(mod => !mod.HideOnUi); + + if (Player.AmOwner && filteredModifiers.Count() > 0) + { + var stringBuild = new StringBuilder(); + foreach (var mod in filteredModifiers) + { + stringBuild.Append($"\n{mod.ModifierName}"); + if (mod is TimedModifier timer) + { + stringBuild.Append($" ({Math.Round(timer.Duration - timer.TimeRemaining, 0)}s/{timer.Duration}s)"); + } + } + _modifierText.text = $"Modifiers:{stringBuild}"; + } + else if (Player.AmOwner && filteredModifiers.Count() == 0 && _modifierText.text != string.Empty) + { + _modifierText.text = string.Empty; + } } public static void RemoveModifier(PlayerControl target, uint modifierId) @@ -66,6 +104,12 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr) return; } + if (target.HasModifier(modifierId)) + { + Logger.Error($"Player already has modifier with id {modifierId}!"); + return; + } + var modifier = (BaseModifier)Activator.CreateInstance(type); var modifierComponent = target.GetModifierComponent(); @@ -77,7 +121,7 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr) } modifierComponent.ActiveModifiers.Add(modifier); - modifier.Player = modifierComponent.player; + modifier.Player = modifierComponent.Player; modifier.ModifierId = modifierId; modifier.OnActivate(); diff --git a/MiraAPI/Modifiers/Types/TimedModifier.cs b/MiraAPI/Modifiers/Types/TimedModifier.cs index 1725ed0..8e8fda3 100644 --- a/MiraAPI/Modifiers/Types/TimedModifier.cs +++ b/MiraAPI/Modifiers/Types/TimedModifier.cs @@ -11,13 +11,13 @@ public abstract class TimedModifier : BaseModifier public bool TimerActive = false; public float TimeRemaining; - public override void Update() + public override void FixedUpdate() { if (!Player.AmOwner) return; if (TimeRemaining > 0 && TimerActive) { - TimeRemaining -= Time.deltaTime; + TimeRemaining -= Time.fixedDeltaTime; } else { diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index 5e922cc..4444b12 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -1,6 +1,7 @@ using MiraAPI.Roles; using System.Linq; using System.Text; +using TMPro; using UnityEngine; namespace MiraAPI.Utilities; @@ -13,6 +14,33 @@ public static class Helpers return ShipStatus.Instance.AllRooms.ToList().Find(room => room.roomArea.OverlapPoint(pos)); } + public static TextMeshPro CreateTextLabel(string name, Transform parent, +AspectPosition.EdgeAlignments alignment, Vector3 distance, float fontSize = 2f, +TextAlignmentOptions textAlignment = TextAlignmentOptions.Center) + { + var textObj = new GameObject(name) + { + transform = + { + parent = parent + }, + layer = LayerMask.NameToLayer("UI") + }; + + var textMeshPro = textObj.AddComponent(); + textMeshPro.fontSize = fontSize; + textMeshPro.alignment = textAlignment; + textMeshPro.font = HudManager.Instance.TaskPanel.taskText.font; + textMeshPro.fontMaterial = HudManager.Instance.TaskPanel.taskText.fontMaterial; + + var aspectPosition = textObj.AddComponent(); + aspectPosition.Alignment = alignment; + aspectPosition.DistanceFromEdge = distance; + aspectPosition.AdjustPosition(); + + return textMeshPro; + } + public static DeadBody GetBodyById(byte id) { return Object.FindObjectsOfType().FirstOrDefault(body => body.ParentId == id);