diff --git a/UsefulHints/Config.cs b/UsefulHints/Config.cs
new file mode 100644
index 0000000..370cbca
--- /dev/null
+++ b/UsefulHints/Config.cs
@@ -0,0 +1,48 @@
+using CustomPlayerEffects;
+using Exiled.API.Enums;
+using Exiled.API.Interfaces;
+using System.ComponentModel;
+
+namespace UsefulHints
+{
+ public class Config : IConfig
+ {
+ public bool IsEnabled { get; set; } = true;
+ public bool Debug { get; set; } = false;
+ [Description("[Module] Hints:")]
+ public bool EnableHints { get; set; } = true;
+ public string Scp096LookMessage { get; set; } = "You looked at SCP-096!";
+ public float Scp268Duration { get; set; } = 15f;
+ public string Scp268TimeLeftMessage { get; set; } = "Remaining: {0}s";
+ public string Scp2176TimeLeftMessage { get; set; } = "Remaining: {0}s";
+ public string JailbirdUseMessage { get; set; } = "Jailbird has been used {0}/5 times";
+ public string Scp207HintMessage { get; set; } = "You are on {0} SCP-207";
+ [Description("[Module] Kill Counter:")]
+ public bool EnableKillCounter { get; set; } = true;
+ public string KillCountMessage { get; set; } = "{0} kills";
+ [Description("[Module] Round Summary:")]
+ public bool EnableRoundSummary { get; set; } = true;
+ public ushort RoundSummaryMessageDuration { get; set; } = 10;
+ public string HumanKillMessage { get; set; } = "{0} had the most kills as Human: {1}";
+ public string ScpKillMessage { get; set; } = "{0} had the most kills as SCP: {1}";
+ public string TopDamageMessage { get; set; } = "{0} did the most damage: {1}";
+ public string FirstScpKillerMessage { get; set; } = "{0} was the first to kill SCP";
+ public string EscaperMessage { get; set; } = "{0} escaped first from the facility: {1}:{2}";
+ [Description("[Module] Teammates:")]
+ public bool EnableTeammates { get; set; } = true;
+ public float TeammateHintDelay { get; set; } = 4f;
+ public string TeammateHintMessage { get; set; } = "Your Teammates \n{0}";
+ public float TeammateMessageDuration { get; set; } = 8f;
+ public string AloneHintMessage { get; set; } = "You are playing Solo";
+ public float AloneMessageDuration { get; set; } = 4f;
+ [Description("[Module] Last Human Broadcast:")]
+ public bool EnableLastHumanBroadcast { get; set; } = true;
+ public string BroadcastForHuman { get; set; } = "You are the last human alive!";
+ public string BroadcastForScp { get; set; } = "{0} is the last human alive playing as {1} in {2}";
+ [Description("[Module] Jailbird Custom Settings:")]
+ public bool EnableCustomJailbirdSettings { get; set; } = false;
+ public EffectType JailbirdEffect { get; set; } = EffectType.Flashed;
+ public float JailbirdEffectDuration { get; set; } = 4f;
+ public byte JailbirdEffectIntensity { get; set; } = 1;
+ }
+}
\ No newline at end of file
diff --git a/UsefulHints/EventHandlers/Entities/SCP096.cs b/UsefulHints/EventHandlers/Entities/SCP096.cs
new file mode 100644
index 0000000..422223c
--- /dev/null
+++ b/UsefulHints/EventHandlers/Entities/SCP096.cs
@@ -0,0 +1,20 @@
+using Exiled.Events.EventArgs.Scp096;
+
+namespace UsefulHints.EventHandlers.Entities
+{
+ public static class SCP096
+ {
+ public static void RegisterEvents()
+ {
+ Exiled.Events.Handlers.Scp096.AddingTarget += OnScp096AddingTarget;
+ }
+ public static void UnregisterEvents()
+ {
+ Exiled.Events.Handlers.Scp096.AddingTarget -= OnScp096AddingTarget;
+ }
+ private static void OnScp096AddingTarget(AddingTargetEventArgs ev)
+ {
+ ev.Target.ShowHint(UsefulHints.Instance.Config.Scp096LookMessage, 5);
+ }
+ }
+}
\ No newline at end of file
diff --git a/UsefulHints/EventHandlers/Items/Hints.cs b/UsefulHints/EventHandlers/Items/Hints.cs
new file mode 100644
index 0000000..6cda0e8
--- /dev/null
+++ b/UsefulHints/EventHandlers/Items/Hints.cs
@@ -0,0 +1,149 @@
+using System.Linq;
+using System.Collections.Generic;
+using Exiled.API.Enums;
+using Exiled.API.Extensions;
+using Exiled.API.Features.Pickups;
+using Player = Exiled.API.Features.Player;
+using Exiled.Events.EventArgs.Map;
+using Exiled.Events.EventArgs.Player;
+using InventorySystem.Items.ThrowableProjectiles;
+using MEC;
+
+namespace UsefulHints.EventHandlers.Items
+{
+ public static class Hints
+ {
+ private static readonly Dictionary activeCoroutines = new Dictionary();
+ private static Dictionary activeItems = new Dictionary();
+ public static void RegisterEvents()
+ {
+ Exiled.Events.Handlers.Player.PickingUpItem += OnPickingUpSCP207;
+ Exiled.Events.Handlers.Player.UsedItem += OnSCP268Used;
+ Exiled.Events.Handlers.Player.InteractingDoor += OnSCP268Interacting;
+ Exiled.Events.Handlers.Player.ChangedItem += OnSCP268ChangedItem;
+ Exiled.Events.Handlers.Map.ExplodingGrenade += OnSCP2176Grenade;
+ Exiled.Events.Handlers.Server.WaitingForPlayers += OnWaitingForPlayers;
+ Exiled.Events.Handlers.Player.PickingUpItem += OnPickingUpJailbird;
+ }
+ public static void UnregisterEvents()
+ {
+ Exiled.Events.Handlers.Player.PickingUpItem -= OnPickingUpSCP207;
+ Exiled.Events.Handlers.Player.UsedItem -= OnSCP268Used;
+ Exiled.Events.Handlers.Player.InteractingDoor -= OnSCP268Interacting;
+ Exiled.Events.Handlers.Player.ChangedItem -= OnSCP268ChangedItem;
+ Exiled.Events.Handlers.Map.ExplodingGrenade -= OnSCP2176Grenade;
+ Exiled.Events.Handlers.Server.WaitingForPlayers -= OnWaitingForPlayers;
+ Exiled.Events.Handlers.Player.PickingUpItem -= OnPickingUpJailbird;
+ }
+ // SCP 207 Handler
+ private static void OnPickingUpSCP207(PickingUpItemEventArgs ev)
+ {
+ if (ev.Pickup.Type == ItemType.SCP207)
+ {
+ CustomPlayerEffects.StatusEffectBase scp207Effect = ev.Player.ActiveEffects.FirstOrDefault(effect => effect.GetEffectType() == EffectType.Scp207);
+
+ if (scp207Effect != null)
+ {
+ ev.Player.ShowHint($"{string.Format(UsefulHints.Instance.Config.Scp207HintMessage, scp207Effect.Intensity)}", 4);
+ }
+ }
+ }
+ // SCP 268 Handler
+ private static void OnSCP268Used(UsedItemEventArgs ev)
+ {
+ if (ev.Item.Type == ItemType.SCP268)
+ {
+ if (activeCoroutines.ContainsKey(ev.Player))
+ {
+ Timing.KillCoroutines(activeCoroutines[ev.Player]);
+ activeCoroutines.Remove(ev.Player);
+ }
+
+ var coroutine = Timing.RunCoroutine(Scp268Timer(ev.Player));
+ activeCoroutines.Add(ev.Player, coroutine);
+ activeItems.Add(ev.Player, ev.Item.Type);
+ }
+ }
+ private static void OnSCP268Interacting(InteractingDoorEventArgs ev)
+ {
+ if (activeCoroutines.ContainsKey(ev.Player) && activeItems.ContainsKey(ev.Player) && activeItems[ev.Player] == ItemType.SCP268)
+ {
+ Timing.KillCoroutines(activeCoroutines[ev.Player]);
+ activeCoroutines.Remove(ev.Player);
+ activeItems.Remove(ev.Player);
+ }
+ }
+ private static void OnSCP268ChangedItem(ChangedItemEventArgs ev)
+ {
+ if (activeCoroutines.ContainsKey(ev.Player) && activeItems.ContainsKey(ev.Player) && activeItems[ev.Player] == ItemType.SCP268)
+ {
+ Timing.KillCoroutines(activeCoroutines[ev.Player]);
+ activeCoroutines.Remove(ev.Player);
+ activeItems.Remove(ev.Player);
+ }
+ }
+ private static IEnumerator Scp268Timer(Player player)
+ {
+ float duration = UsefulHints.Instance.Config.Scp268Duration;
+
+ while (duration > 0)
+ {
+ player.ShowHint($"{new string('\n', 10)}{string.Format(UsefulHints.Instance.Config.Scp268TimeLeftMessage, (int)duration)}", 1.15f);
+ yield return Timing.WaitForSeconds(1f);
+ duration -= 1f;
+ }
+ activeCoroutines.Remove(player);
+ }
+ // SCP 2176 Handler
+ private static void OnSCP2176Grenade(ExplodingGrenadeEventArgs ev)
+ {
+ if (ev.Projectile.Base is Scp2176Projectile)
+ {
+ if (ev.Player != null)
+ {
+ if (activeCoroutines.ContainsKey(ev.Player))
+ {
+ Timing.KillCoroutines(activeCoroutines[ev.Player]);
+ activeCoroutines.Remove(ev.Player);
+ }
+
+ var coroutine = Timing.RunCoroutine(Scp2176Timer(ev.Player));
+ activeCoroutines.Add(ev.Player, coroutine);
+ }
+ }
+ }
+ private static IEnumerator Scp2176Timer(Player player)
+ {
+ float duration = 13f;
+
+ while (duration > 0)
+ {
+ player.ShowHint($"{new string('\n', 10)}{string.Format(UsefulHints.Instance.Config.Scp2176TimeLeftMessage, (int)duration)}", 1.15f);
+ yield return Timing.WaitForSeconds(1f);
+ duration -= 1f;
+ }
+ activeCoroutines.Remove(player);
+ }
+ // Reset Coroutines
+ private static void OnWaitingForPlayers()
+ {
+ activeCoroutines.Clear();
+ }
+ // Jailbird Handler
+ private static void OnPickingUpJailbird(PickingUpItemEventArgs ev)
+ {
+ if (ev.Pickup is JailbirdPickup jailbirdPickup)
+ {
+ int remainingCharges = jailbirdPickup.TotalCharges;
+ if (remainingCharges < 4)
+ {
+ ev.Player.ShowHint($"{string.Format(UsefulHints.Instance.Config.JailbirdUseMessage, remainingCharges)}", 4);
+ }
+ else
+ {
+ ev.Player.ShowHint($"{string.Format(UsefulHints.Instance.Config.JailbirdUseMessage, remainingCharges)}", 4);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/UsefulHints/EventHandlers/Modules/JailbirdPatchHandler.cs b/UsefulHints/EventHandlers/Modules/JailbirdPatchHandler.cs
new file mode 100644
index 0000000..ee6552b
--- /dev/null
+++ b/UsefulHints/EventHandlers/Modules/JailbirdPatchHandler.cs
@@ -0,0 +1,27 @@
+using HarmonyLib;
+using System;
+
+namespace UsefulHints.EventHandlers.Modules
+{
+ public static class JailbirdPatchHandler
+ {
+ public static Harmony Harmony { get; private set; }
+ public static string HarmonyName { get; private set; }
+ public static void RegisterEvents()
+ {
+ if (UsefulHints.Instance.Config.EnableCustomJailbirdSettings)
+ {
+ HarmonyName = $"com-vretu.uh-{DateTime.UtcNow.Ticks}";
+ Harmony = new Harmony(HarmonyName);
+ Harmony.PatchAll();
+ }
+ }
+ public static void UnregisterEvents()
+ {
+ if (UsefulHints.Instance.Config.EnableCustomJailbirdSettings)
+ {
+ Harmony.UnpatchAll(HarmonyName);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/UsefulHints/EventHandlers/Modules/KillCounter.cs b/UsefulHints/EventHandlers/Modules/KillCounter.cs
new file mode 100644
index 0000000..f62d013
--- /dev/null
+++ b/UsefulHints/EventHandlers/Modules/KillCounter.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using Player = Exiled.API.Features.Player;
+using Exiled.Events.EventArgs.Player;
+
+namespace UsefulHints.EventHandlers.Modules
+{
+ public static class KillCounter
+ {
+ private static readonly Dictionary playerKills = new Dictionary();
+ public static void RegisterEvents()
+ {
+ Exiled.Events.Handlers.Player.Died += OnPlayerDied;
+ }
+ public static void UnregisterEvents()
+ {
+ Exiled.Events.Handlers.Player.Died -= OnPlayerDied;
+ }
+ private static void OnPlayerDied(DiedEventArgs ev)
+ {
+ if (UsefulHints.Instance.Config.EnableKillCounter)
+ {
+ if (ev.Attacker != null && ev.Attacker != ev.Player)
+ {
+ Player killer = ev.Attacker;
+
+ if (playerKills.ContainsKey(killer))
+ {
+ playerKills[killer]++;
+ }
+ else
+ {
+ playerKills[killer] = 1;
+ }
+ killer.ShowHint(string.Format(UsefulHints.Instance.Config.KillCountMessage, playerKills[killer]), 4);
+ }
+ }
+ }
+ }
+}
diff --git a/UsefulHints/EventHandlers/Modules/LastHumanBroadcast.cs b/UsefulHints/EventHandlers/Modules/LastHumanBroadcast.cs
new file mode 100644
index 0000000..9ac3106
--- /dev/null
+++ b/UsefulHints/EventHandlers/Modules/LastHumanBroadcast.cs
@@ -0,0 +1,84 @@
+using System.Linq;
+using Exiled.API.Enums;
+using Exiled.API.Features;
+using Exiled.Events.EventArgs.Player;
+using PlayerRoles;
+
+namespace UsefulHints.EventHandlers.Modules
+{
+ public static class LastHumanBroadcast
+ {
+ public static void RegisterEvents()
+ {
+ Exiled.Events.Handlers.Player.Died += OnPlayerDied;
+ }
+ public static void UnregisterEvents()
+ {
+ Exiled.Events.Handlers.Player.Died -= OnPlayerDied;
+ }
+ private static void OnPlayerDied(DiedEventArgs ev)
+ {
+ if (UsefulHints.Instance.Config.EnableLastHumanBroadcast)
+ {
+ var aliveHumans = Player.List.Where(p => p.IsAlive && IsHuman(p));
+
+ if (aliveHumans.Count() == 1)
+ {
+ Player lastAlive = aliveHumans.First();
+
+ lastAlive.Broadcast(10, UsefulHints.Instance.Config.BroadcastForHuman);
+
+ var zone = GetZoneName(lastAlive);
+ var teamName = GetRoleTeamName(lastAlive);
+
+ string message = string.Format(UsefulHints.Instance.Config.BroadcastForScp, lastAlive.Nickname, teamName, zone);
+
+ foreach (var scp in Player.List.Where(p => p.Role.Team == Team.SCPs))
+ {
+ scp.Broadcast(10, message);
+ }
+ }
+ }
+ }
+ private static bool IsHuman(Player player)
+ {
+ return player.Role.Team == Team.FoundationForces || player.Role.Team == Team.ClassD || player.Role.Team == Team.Scientists || player.Role.Team == Team.ChaosInsurgency;
+ }
+ private static string GetZoneName(Player player)
+ {
+ ZoneType zone = player.CurrentRoom.Zone;
+
+ switch (zone)
+ {
+ case ZoneType.LightContainment:
+ return "Light Containment";
+ case ZoneType.HeavyContainment:
+ return "Heavy Containment";
+ case ZoneType.Entrance:
+ return "Entrance Zone";
+ case ZoneType.Surface:
+ return "Surface";
+ default:
+ return "Unknown Zone";
+ }
+ }
+ private static string GetRoleTeamName(Player player)
+ {
+ Team team = player.Role.Team;
+
+ switch (team)
+ {
+ case Team.FoundationForces:
+ return "Mobile Task Force";
+ case Team.ClassD:
+ return "Class D";
+ case Team.Scientists:
+ return "Scientist";
+ case Team.ChaosInsurgency:
+ return "Chaos Insurgency";
+ default:
+ return "Unknown Team";
+ }
+ }
+ }
+}
diff --git a/UsefulHints/EventHandlers/Modules/RoundSummary.cs b/UsefulHints/EventHandlers/Modules/RoundSummary.cs
new file mode 100644
index 0000000..f9f5f17
--- /dev/null
+++ b/UsefulHints/EventHandlers/Modules/RoundSummary.cs
@@ -0,0 +1,174 @@
+using System;
+using System.Collections.Generic;
+using Exiled.API.Features;
+using Player = Exiled.API.Features.Player;
+using Exiled.Events.EventArgs.Player;
+using Exiled.Events.EventArgs.Server;
+using PlayerRoles;
+using static Broadcast;
+
+namespace UsefulHints.EventHandlers.Modules
+{
+ public static class RoundSummary
+ {
+ private static Dictionary scpKills = new Dictionary();
+ private static Dictionary humanKills = new Dictionary();
+ private static Dictionary humanDamage = new Dictionary();
+
+ private static Player firstEscaper = null;
+ private static Player firstScpKiller = null;
+ private static DateTime roundStartTime;
+ private static TimeSpan escapeTime;
+
+ public static void RegisterEvents()
+ {
+ Exiled.Events.Handlers.Player.Died += OnPlayerDied;
+ Exiled.Events.Handlers.Player.Dying += OnPlayerDying;
+ Exiled.Events.Handlers.Player.Escaping += OnPlayerEscaping;
+ Exiled.Events.Handlers.Player.Hurting += OnPlayerHurting;
+ Exiled.Events.Handlers.Server.RoundEnded += OnRoundEnded;
+ Exiled.Events.Handlers.Server.RestartingRound += OnRestartingRound;
+ Exiled.Events.Handlers.Server.RoundStarted += OnRoundStarted;
+ }
+ public static void UnregisterEvents()
+ {
+ Exiled.Events.Handlers.Player.Died -= OnPlayerDied;
+ Exiled.Events.Handlers.Player.Dying -= OnPlayerDying;
+ Exiled.Events.Handlers.Player.Escaping -= OnPlayerEscaping;
+ Exiled.Events.Handlers.Player.Hurting -= OnPlayerHurting;
+ Exiled.Events.Handlers.Server.RoundEnded -= OnRoundEnded;
+ Exiled.Events.Handlers.Server.RestartingRound -= OnRestartingRound;
+ Exiled.Events.Handlers.Server.RoundStarted -= OnRoundStarted;
+ }
+ private static void OnRoundStarted()
+ {
+ roundStartTime = DateTime.Now;
+ }
+ // Handler for player hurting another player
+ private static void OnPlayerHurting(HurtingEventArgs ev)
+ {
+ Player attacker = ev.Attacker;
+ Player victim = ev.Player;
+
+ if (attacker != null && attacker != victim && attacker.Role.Team != Team.SCPs && victim.Role.Team == Team.SCPs)
+ {
+ if (!humanDamage.ContainsKey(attacker))
+ humanDamage[attacker] = 0;
+
+ humanDamage[attacker] += (int)Math.Round(ev.Amount);
+ }
+ }
+ // Handler for "When Player dying" (FirstScpKiller)
+ private static void OnPlayerDying(DyingEventArgs ev)
+ {
+ Player attacker = ev.Attacker;
+ Player victim = ev.Player;
+
+ if (victim.Role.Team == Team.SCPs && victim.Role.Type != RoleTypeId.Scp0492)
+ {
+ if (firstScpKiller == null)
+ {
+ firstScpKiller = attacker;
+ }
+ }
+ }
+ // Handler for "When Player died" (SCP and Human kill count)
+ private static void OnPlayerDied(DiedEventArgs ev)
+ {
+ Player attacker = ev.Attacker;
+
+ if (attacker != null && attacker != ev.Player)
+ {
+ if (attacker.Role.Team == Team.SCPs)
+ {
+ if (!scpKills.ContainsKey(attacker))
+ scpKills[attacker] = 0;
+
+ scpKills[attacker]++;
+ }
+ else
+ {
+ if (!humanKills.ContainsKey(attacker))
+ humanKills[attacker] = 0;
+
+ humanKills[attacker]++;
+ }
+ }
+ }
+ // Handler for Escaped Player
+ private static void OnPlayerEscaping(EscapingEventArgs ev)
+ {
+ if (firstEscaper == null)
+ {
+ firstEscaper = ev.Player;
+ escapeTime = DateTime.Now - roundStartTime;
+ }
+ }
+ // Handler for End Round messages
+ private static void OnRoundEnded(RoundEndedEventArgs ev)
+ {
+ if (UsefulHints.Instance.Config.EnableRoundSummary)
+ {
+ string text = "";
+
+ Player humanKiller = GetTopKiller(humanKills);
+ Player scpKiller = GetTopKiller(scpKills);
+ Player topDamageDealer = GetTopDamageDealer(humanDamage);
+
+ if (humanKiller != null)
+ text += string.Format(UsefulHints.Instance.Config.HumanKillMessage, humanKiller.Nickname, humanKills[humanKiller]) + "\n";
+ if (scpKiller != null)
+ text += string.Format(UsefulHints.Instance.Config.ScpKillMessage, scpKiller.Nickname, scpKills[scpKiller]) + "\n";
+ if (topDamageDealer != null)
+ text += string.Format(UsefulHints.Instance.Config.TopDamageMessage, topDamageDealer.Nickname, humanDamage[topDamageDealer]) + "\n";
+ if (firstEscaper != null)
+ text += string.Format(UsefulHints.Instance.Config.EscaperMessage, firstEscaper.Nickname, escapeTime.Minutes, escapeTime.Seconds) + "\n";
+ if (firstScpKiller != null)
+ text += string.Format(UsefulHints.Instance.Config.FirstScpKillerMessage, firstScpKiller.Nickname) + "\n";
+ if (!string.IsNullOrEmpty(text))
+ Map.Broadcast(UsefulHints.Instance.Config.RoundSummaryMessageDuration, text, BroadcastFlags.Normal, true);
+ }
+ }
+ // Reset Handlers
+ private static void OnRestartingRound()
+ {
+ scpKills.Clear();
+ humanKills.Clear();
+ humanDamage.Clear();
+ firstEscaper = null;
+ firstScpKiller = null;
+ }
+ // Helper to find player with most kills
+ private static Player GetTopKiller(Dictionary kills)
+ {
+ Player topKiller = null;
+ int maxKills = 0;
+
+ foreach (var entry in kills)
+ {
+ if (entry.Value > maxKills)
+ {
+ topKiller = entry.Key;
+ maxKills = entry.Value;
+ }
+ }
+ return topKiller;
+ }
+ // Helper to find player with most damage
+ private static Player GetTopDamageDealer(Dictionary damage)
+ {
+ Player topDealer = null;
+ float maxDamage = 0;
+
+ foreach (var entry in damage)
+ {
+ if (entry.Value > maxDamage)
+ {
+ topDealer = entry.Key;
+ maxDamage = entry.Value;
+ }
+ }
+ return topDealer;
+ }
+ }
+}
diff --git a/UsefulHints/EventHandlers/Modules/Teammates.cs b/UsefulHints/EventHandlers/Modules/Teammates.cs
new file mode 100644
index 0000000..67950db
--- /dev/null
+++ b/UsefulHints/EventHandlers/Modules/Teammates.cs
@@ -0,0 +1,50 @@
+using System.Linq;
+using System.Collections.Generic;
+using Player = Exiled.API.Features.Player;
+using MEC;
+
+namespace UsefulHints.EventHandlers.Modules
+{
+ public static class Teammates
+ {
+ public static void RegisterEvents()
+ {
+ Exiled.Events.Handlers.Server.RoundStarted += OnRoundStartedTeammates;
+ }
+ public static void UnregisterEvents()
+ {
+ Exiled.Events.Handlers.Server.RoundStarted -= OnRoundStartedTeammates;
+ }
+ private static IEnumerator DelayedDisplayTeammates()
+ {
+ yield return Timing.WaitForSeconds(UsefulHints.Instance.Config.TeammateHintDelay);
+ DisplayTeammates();
+ }
+ private static void OnRoundStartedTeammates()
+ {
+ Timing.RunCoroutine(DelayedDisplayTeammates());
+ }
+ private static void DisplayTeammates()
+ {
+ if (UsefulHints.Instance.Config.EnableTeammates)
+ {
+ foreach (var player in Player.List)
+ {
+ List teammates = Player.List
+ .Where(p => p.Role.Team == player.Role.Team && p != player)
+ .Select(p => p.Nickname)
+ .ToList();
+
+ if (teammates.Count > 0)
+ {
+ player.ShowHint(string.Format(UsefulHints.Instance.Config.TeammateHintMessage, string.Join("\n", teammates)), UsefulHints.Instance.Config.TeammateMessageDuration);
+ }
+ else
+ {
+ player.ShowHint(string.Format(UsefulHints.Instance.Config.AloneHintMessage), UsefulHints.Instance.Config.AloneMessageDuration);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/UsefulHints/Patches/JailbirdPatch.cs b/UsefulHints/Patches/JailbirdPatch.cs
new file mode 100644
index 0000000..c90279e
--- /dev/null
+++ b/UsefulHints/Patches/JailbirdPatch.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Emit;
+using Exiled.API.Features;
+using Exiled.API.Features.Pools;
+using HarmonyLib;
+using InventorySystem.Items.Jailbird;
+
+namespace UsefulHints.Patches
+{
+ [HarmonyPatch(typeof(JailbirdHitreg), "ServerAttack")]
+ public static class JailbirdPatch
+ {
+ public static void EnableEffect(HitboxIdentity hitboxIdentity)
+ {
+ Player val = Player.Get(hitboxIdentity.TargetHub);
+ val.EnableEffect(UsefulHints.Instance.Config.JailbirdEffect, UsefulHints.Instance.Config.JailbirdEffectIntensity, UsefulHints.Instance.Config.JailbirdEffectDuration, true);
+ Log.Debug("Effect Activated");
+ }
+
+ [HarmonyTranspiler]
+ public static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
+ {
+ List newInstructions = ListPool.Pool.Get(instructions);
+ int index = newInstructions.FindLastIndex((CodeInstruction code) => code.opcode == OpCodes.Ldfld && (FieldInfo)code.operand == AccessTools.Field(typeof(ReferenceHub), "playerEffectsController")) + 3;
+ newInstructions.InsertRange(index, new CodeInstruction[2]
+ {
+ new CodeInstruction(OpCodes.Ldloc_S, 12),
+ new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(JailbirdPatch), nameof(EnableEffect), new Type[1] { typeof(HitboxIdentity) }))
+ });
+ for (int z = 0; z < newInstructions.Count; z++)
+ {
+ yield return newInstructions[z];
+ }
+ ListPool.Pool.Return(newInstructions);
+ }
+ }
+}
\ No newline at end of file
diff --git a/UsefulHints/Properties/AssemblyInfo.cs b/UsefulHints/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..59e4bd6
--- /dev/null
+++ b/UsefulHints/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// Ogólne informacje o zestawie są kontrolowane poprzez następujący
+// zestaw atrybutów. Zmień wartości tych atrybutów, aby zmodyfikować informacje
+// powiązane z zestawem.
+[assembly: AssemblyTitle("UsefulHints")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("UsefulHints")]
+[assembly: AssemblyCopyright("Copyright © 2024")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Ustawienie elementu ComVisible na wartość false sprawia, że typy w tym zestawie są niewidoczne
+// dla składników COM. Jeśli potrzebny jest dostęp do typu w tym zestawie z
+// COM, ustaw wartość true dla atrybutu ComVisible tego typu.
+[assembly: ComVisible(false)]
+
+// Następujący identyfikator GUID jest identyfikatorem biblioteki typów w przypadku udostępnienia tego projektu w modelu COM
+[assembly: Guid("8e4e5067-3ada-48dd-a6c4-57b8aecd24cd")]
+
+// Informacje o wersji zestawu zawierają następujące cztery wartości:
+//
+// Wersja główna
+// Wersja pomocnicza
+// Numer kompilacji
+// Poprawka
+//
+// Możesz określić wszystkie wartości lub użyć domyślnych numerów kompilacji i poprawki
+// przy użyciu symbolu „*”, tak jak pokazano poniżej:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/UsefulHints/UsefulHints.cs b/UsefulHints/UsefulHints.cs
new file mode 100644
index 0000000..e1f8a13
--- /dev/null
+++ b/UsefulHints/UsefulHints.cs
@@ -0,0 +1,39 @@
+using System;
+using Exiled.API.Features;
+
+namespace UsefulHints
+{
+ public class UsefulHints : Plugin
+ {
+ public override string Name => "Useful Hints";
+ public override string Author => "Vretu";
+ public override string Prefix { get; } = "UH";
+ public override Version Version => new Version(1, 4, 0);
+ public override Version RequiredExiledVersion { get; } = new Version(8, 9, 8);
+ public static UsefulHints Instance { get; private set; }
+ public override void OnEnabled()
+ {
+ Instance = this;
+ if(Config.EnableHints){ EventHandlers.Entities.SCP096.RegisterEvents(); }
+ if(Config.EnableHints){ EventHandlers.Items.Hints.RegisterEvents(); }
+ EventHandlers.Modules.JailbirdPatchHandler.RegisterEvents();
+ EventHandlers.Modules.KillCounter.RegisterEvents();
+ EventHandlers.Modules.LastHumanBroadcast.RegisterEvents();
+ EventHandlers.Modules.RoundSummary.RegisterEvents();
+ EventHandlers.Modules.Teammates.RegisterEvents();
+ base.OnEnabled();
+ }
+ public override void OnDisabled()
+ {
+ Instance = null;
+ if(Config.EnableHints){ EventHandlers.Entities.SCP096.UnregisterEvents(); }
+ if(Config.EnableHints){ EventHandlers.Items.Hints.UnregisterEvents(); }
+ EventHandlers.Modules.JailbirdPatchHandler.UnregisterEvents();
+ EventHandlers.Modules.KillCounter.UnregisterEvents();
+ EventHandlers.Modules.LastHumanBroadcast.UnregisterEvents();
+ EventHandlers.Modules.RoundSummary.UnregisterEvents();
+ EventHandlers.Modules.Teammates.UnregisterEvents();
+ base.OnDisabled();
+ }
+ }
+}
\ No newline at end of file
diff --git a/UsefulHints/UsefulHints.csproj b/UsefulHints/UsefulHints.csproj
new file mode 100644
index 0000000..98d63b8
--- /dev/null
+++ b/UsefulHints/UsefulHints.csproj
@@ -0,0 +1,81 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {8E4E5067-3ADA-48DD-A6C4-57B8AECD24CD}
+ Library
+ Properties
+ UsefulHints
+ UsefulHints
+ v4.8
+ 512
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\..\Exiled References\Assembly-CSharp-firstpass.dll
+
+
+ ..\..\Exiled References\Mirror.dll
+
+
+
+
+
+
+
+
+
+
+ ..\..\Exiled References\UnityEngine.dll
+
+
+ ..\..\Exiled References\UnityEngine.CoreModule.dll
+
+
+ ..\..\Exiled References\UnityEngine.PhysicsModule.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 8.12.2
+
+
+ 2.3.3
+
+
+
+
+
\ No newline at end of file