Skip to content

Commit 36b9f83

Browse files
feat: add Console to get notified on certain game events
* feat: enable and improve ConsoleUI * feat: add new toggles for notifications * feat: add patches for logging vent events * feat: add patches for logging kills and shapeshift events * docs: add Console tab to changelist in README --------- Co-authored-by: Astral✨ <90265231+astra1dev@users.noreply.github.com>
1 parent 2360642 commit 36b9f83

9 files changed

Lines changed: 214 additions & 52 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ Make sure you are only having one version of MalumMenu installed at a time, as h
7878
- **ESP**: Show Player Info, More Lobby Info, Show Task Arrows
7979
- **Roles**: Do Tasks as Impostor, Tasks Menu (to complete individual tasks and see other players' tasks), Track Reach, Interrogate Reach
8080
- **Ship**: Call Meeting, Open Sabotage Map, Trigger Spores ([#40](https://github.com/scp222thj/MalumMenu/pull/40)), Auto-Open Doors On Use, Doors Menu (to close / open individual doors)
81+
- **Console** (NEW!): Show Console, Log Deaths, Log Shapeshifts, Log Vents
8182
- **Host-Only**: No Options Limits, Protect Player Menu, Force Role
8283
- **Meetings** (NEW!): Skip Meeting, VoteImmune, Eject Player
8384
- **Game State** (NEW!) ([#49](https://github.com/scp222thj/MalumMenu/pull/49)): Force Start Game, No Game End

src/MalumMenu.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public partial class MalumMenu : BasePlugin
2020
public static string malumVersion = "2.6.1";
2121
public static List<string> supportedAU = ["2025.9.9", "2025.10.14", "2025.11.18"];
2222
public static MenuUI menuUI;
23-
// public static ConsoleUI consoleUI;
23+
public static ConsoleUI consoleUI;
2424
public static RolesUI rolesUI;
2525
public static DoorsUI doorsUI;
2626
public static TasksUI tasksUI;
@@ -100,7 +100,7 @@ public override void Load()
100100
Harmony.PatchAll();
101101

102102
menuUI = AddComponent<MenuUI>();
103-
// consoleUI = AddComponent<ConsoleUI>();
103+
consoleUI = AddComponent<ConsoleUI>();
104104
rolesUI = AddComponent<RolesUI>();
105105
doorsUI = AddComponent<DoorsUI>();
106106
tasksUI = AddComponent <TasksUI>();

src/Patches/OtherPatches.cs

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -298,29 +298,6 @@ public static void Prefix(Console __instance)
298298
}
299299
}
300300

301-
[HarmonyPatch(typeof(Vent), nameof(Vent.CanUse))]
302-
public static class Vent_CanUse
303-
{
304-
// Prefix patch of Vent.CanUse to allow venting for cheaters
305-
// Basically does what the original method did with the required modifications
306-
public static void Postfix(Vent __instance, NetworkedPlayerInfo pc, ref bool canUse, ref bool couldUse, ref float __result)
307-
{
308-
if (!PlayerControl.LocalPlayer || !PlayerControl.LocalPlayer.Data) return;
309-
if (PlayerControl.LocalPlayer.Data.Role.CanVent || PlayerControl.LocalPlayer.Data.IsDead) return;
310-
if (!CheatToggles.useVents) return;
311-
var @object = pc.Object;
312-
313-
var center = @object.Collider.bounds.center;
314-
var position = __instance.transform.position;
315-
var num = Vector2.Distance(center, position);
316-
317-
// Allow usage of vents unless the vent is too far or there are objects blocking the player's path
318-
canUse = num <= __instance.UsableDistance && !PhysicsHelpers.AnythingBetween(@object.Collider, center, position, Constants.ShipOnlyMask, false);
319-
couldUse = true;
320-
__result = num;
321-
}
322-
}
323-
324301
[HarmonyPatch(typeof(IntroCutscene), "CoBegin")]
325302
public static class IntroCutscene_CoBegin
326303
{

src/Patches/PlayerControlPatches.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using HarmonyLib;
2+
using UnityEngine;
23

34
namespace MalumMenu;
45

@@ -56,6 +57,37 @@ public static bool Prefix(PlayerControl __instance, PlayerControl target)
5657
}
5758
}
5859

60+
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.MurderPlayer))]
61+
public static class PlayerControl_MurderPlayer
62+
{
63+
/// <summary>
64+
/// Prefix patch of PlayerControl.MurderPlayer to log when a player tries to kill another player, who the killer and target are,
65+
/// and where the kill happened. Also logs when a kill gets saved by a guardian angel.
66+
/// </summary>
67+
/// <param name="__instance">The <c>PlayerControl</c> instance.</param>
68+
/// <param name="target">The player being killed.</param>
69+
public static void Prefix(PlayerControl __instance, PlayerControl target)
70+
{
71+
if (!CheatToggles.logDeaths || target == null) return;
72+
73+
var (realKillerName, displayKillerName, isDisguised) = Utils.GetPlayerIdentity(__instance);
74+
var targetName = $"<color=#{ColorUtility.ToHtmlStringRGB(target.Data.Color)}>{target.CurrentOutfit.PlayerName}</color>";
75+
var room = Utils.GetRoomFromPosition(target.GetTruePosition());
76+
var roomName = room != null ? room.RoomId.ToString() : "an unknown location";
77+
78+
if (target.protectedByGuardianId != -1)
79+
{
80+
ConsoleUI.Log(isDisguised ? $"{realKillerName} (as {displayKillerName}) tried to kill {targetName} in {roomName} (Saved)"
81+
: $"{realKillerName} tried to kill {targetName} in {roomName} (Saved)");
82+
}
83+
else
84+
{
85+
ConsoleUI.Log(isDisguised ? $"{realKillerName} (as {displayKillerName}) killed {targetName} in {roomName}"
86+
: $"{realKillerName} killed {targetName} in {roomName}");
87+
}
88+
}
89+
}
90+
5991
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.TurnOnProtection))]
6092
public static class PlayerControl_TurnOnProtection
6193
{
@@ -100,6 +132,37 @@ public static void Prefix(ref bool shouldAnimate){
100132
}
101133
}
102134

135+
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Shapeshift))]
136+
public static class PlayerControl_Shapeshift
137+
{
138+
/// <summary>
139+
/// Postfix patch of PlayerControl.Shapeshift to log when a player shapeshifts into another player,
140+
/// and who they shapeshifted into. Also logs when a shapeshift gets reverted.
141+
/// </summary>
142+
/// <param name="__instance">The <c>PlayerControl</c> instance.</param>
143+
/// <param name="targetPlayer">The player that is being shapeshifted into.</param>
144+
/// <param name="animate">Used in the original method to determine whether the shapeshift animation should play.</param>
145+
public static void Postfix(PlayerControl __instance, PlayerControl targetPlayer, bool animate)
146+
{
147+
if (!CheatToggles.logShapeshifts) return;
148+
149+
if (__instance.CurrentOutfitType == PlayerOutfitType.MushroomMixup) return;
150+
var targetPlayerInfo = targetPlayer.Data;
151+
if (targetPlayerInfo.PlayerId == __instance.Data.PlayerId)
152+
{
153+
ConsoleUI.Log($"<color=#{ColorUtility.ToHtmlStringRGB(GameData.Instance.GetPlayerById(__instance.PlayerId).Color)}>" +
154+
$"{GameData.Instance.GetPlayerById(__instance.PlayerId)._object.Data.PlayerName}</color> Shapeshift was reverted");
155+
}
156+
else
157+
{
158+
ConsoleUI.Log($"<color=#{ColorUtility.ToHtmlStringRGB(GameData.Instance.GetPlayerById(__instance.PlayerId).Color)}>" +
159+
$"{GameData.Instance.GetPlayerById(__instance.PlayerId)._object.Data.PlayerName}</color> shapeshifted into " +
160+
$"<color=#{ColorUtility.ToHtmlStringRGB(GameData.Instance.GetPlayerById(targetPlayerInfo.PlayerId).Color)}>" +
161+
$"{GameData.Instance.GetPlayerById(targetPlayerInfo.PlayerId)._object.Data.PlayerName}</color>");
162+
}
163+
}
164+
}
165+
103166
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSyncSettings))]
104167
public static class PlayerControl_RpcSyncSettings
105168
{

src/Patches/VentPatches.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using HarmonyLib;
2+
using UnityEngine;
3+
4+
namespace MalumMenu;
5+
6+
[HarmonyPatch(typeof(Vent), nameof(Vent.CanUse))]
7+
public static class Vent_CanUse
8+
{
9+
/// <summary>
10+
/// Postfix patch of Vent.CanUse to allow venting.
11+
/// </summary>
12+
/// <param name="__instance">The <c>Vent</c> instance.</param>
13+
/// <param name="pc">The <c>PlayerControl</c> of the player trying to use the vent.</param>
14+
/// <param name="canUse">Whether the player can currently use the vent, accounting for distance and physics obstacles.</param>
15+
/// <param name="couldUse">Whether the player's role and game state theoretically allow vent usage.</param>
16+
/// <param name="__result">The distance from the player to the vent, or -1 if the vent cannot be used.</param>
17+
public static void Postfix(Vent __instance, NetworkedPlayerInfo pc, ref bool canUse, ref bool couldUse, ref float __result)
18+
{
19+
if (!PlayerControl.LocalPlayer || !PlayerControl.LocalPlayer.Data) return;
20+
if (PlayerControl.LocalPlayer.Data.Role.CanVent || PlayerControl.LocalPlayer.Data.IsDead) return;
21+
if (!CheatToggles.useVents) return;
22+
var @object = pc.Object;
23+
24+
var center = @object.Collider.bounds.center;
25+
var position = __instance.transform.position;
26+
var num = Vector2.Distance(center, position);
27+
28+
// Allow usage of vents unless the vent is too far or there are objects blocking the player's path
29+
canUse = num <= __instance.UsableDistance && !PhysicsHelpers.AnythingBetween(@object.Collider, center, position, Constants.ShipOnlyMask, false);
30+
couldUse = true;
31+
__result = num;
32+
}
33+
}
34+
35+
[HarmonyPatch(typeof(Vent), nameof(Vent.EnterVent))]
36+
public static class Vent_EnterVent
37+
{
38+
/// <summary>
39+
/// Postfix patch of Vent.EnterVent to log when a player enters a vent, along with the room they entered it in.
40+
/// </summary>
41+
/// <param name="__instance">The <c>Vent</c> instance.</param>
42+
/// <param name="pc">The <c>PlayerControl</c> of the player entering the vent.</param>
43+
public static void Postfix(Vent __instance, PlayerControl pc)
44+
{
45+
if (!CheatToggles.logVents || !Utils.isShip) return;
46+
47+
var (realPlayerName, displayPlayerName, isDisguised) = Utils.GetPlayerIdentity(pc);
48+
var room = Utils.GetRoomFromPosition(__instance.transform.position - (Vector3) pc.Collider.offset);
49+
var roomName = room != null ? room.RoomId.ToString() : "an unknown location";
50+
ConsoleUI.Log(isDisguised
51+
? $"{realPlayerName} (as {displayPlayerName}) entered a vent in {roomName}"
52+
: $"{realPlayerName} entered a vent in {roomName}");
53+
}
54+
}
55+
56+
[HarmonyPatch(typeof(Vent), nameof(Vent.ExitVent))]
57+
public static class Vent_ExitVent
58+
{
59+
/// <summary>
60+
/// Postfix patch of Vent.ExitVent to log when a player exits a vent, along with the room they exited it in.
61+
/// </summary>
62+
/// <param name="__instance">The <c>Vent</c> instance.</param>
63+
/// <param name="pc">The <c>PlayerControl</c> of the player exiting the vent.</param>
64+
public static void Postfix(Vent __instance, PlayerControl pc)
65+
{
66+
if (!CheatToggles.logVents || !Utils.isShip) return;
67+
68+
var (realPlayerName, displayPlayerName, isDisguised) = Utils.GetPlayerIdentity(pc);
69+
var room = Utils.GetRoomFromPosition(__instance.transform.position - (Vector3) pc.Collider.offset);
70+
var roomName = room != null ? room.RoomId.ToString() : "an unknown location";
71+
ConsoleUI.Log(isDisguised
72+
? $"{realPlayerName} (as {displayPlayerName}) exited a vent in {roomName}"
73+
: $"{realPlayerName} exited a vent in {roomName}");
74+
}
75+
}

src/UI/CheatToggles.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ public struct CheatToggles
8080
public static bool alwaysChat;
8181
public static bool chatJailbreak;
8282

83+
// Console
84+
public static bool showConsole;
85+
public static bool logDeaths;
86+
public static bool logShapeshifts;
87+
public static bool logVents;
88+
8389
//Ship
8490
public static bool closeMeeting;
8591
public static bool sabotageMap;

src/UI/ConsoleUI.cs

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,70 @@
1+
using Il2CppSystem;
12
using UnityEngine;
2-
using Il2CppSystem.Collections.Generic;
3+
using System.Collections.Generic;
34

45
namespace MalumMenu;
56

67
public class ConsoleUI : MonoBehaviour
78
{
8-
public bool isVisible = false;
9-
private Vector2 scrollPosition = Vector2.zero;
10-
private static List<string> logEntries = new();
11-
private const int MaxLogEntries = 100;
12-
private Rect windowRect = new Rect(320, 10, 500, 300); // Adjust size and position as needed
13-
private GUIStyle logStyle;
14-
15-
public void Log(string message)
9+
private static Vector2 _scrollPosition = Vector2.zero;
10+
private static List<string> _logEntries = new();
11+
private const int MaxLogEntries = 300;
12+
private Rect _windowRect = new(320, 10, 500, 300);
13+
private GUIStyle _logStyle;
14+
15+
public static void Log(string message)
1616
{
17-
if (logEntries.Count >= MaxLogEntries) // Limit the number of logs to keep memory usage in check
17+
if (_logEntries.Count >= MaxLogEntries) // Limit the number of logs to keep memory usage in check
1818
{
19-
logEntries.RemoveAt(0); // Remove the oldest log entry
19+
_logEntries.RemoveAt(0); // Remove the oldest log entry
2020
}
2121

22-
logEntries.Add(message);
22+
_logEntries.Add(message);
2323

2424
// Scroll to the bottom
25-
scrollPosition.y = float.MaxValue;
25+
_scrollPosition.y = float.MaxValue;
2626
}
2727

2828
private void OnGUI()
2929
{
30+
if (!CheatToggles.showConsole) return;
3031

31-
if (!isVisible) return;
32-
33-
logStyle ??= new GUIStyle(GUI.skin.label)
32+
_logStyle ??= new GUIStyle(GUI.skin.label)
3433
{
35-
fontSize = 20
34+
fontSize = 16
3635
};
3736

38-
if(ColorUtility.TryParseHtmlString(MalumMenu.menuHtmlColor.Value, out var configUIColor)){
37+
if(ColorUtility.TryParseHtmlString(MalumMenu.menuHtmlColor.Value, out var configUIColor))
38+
{
3939
GUI.backgroundColor = configUIColor;
4040
}
4141

42-
windowRect = GUI.Window(1, windowRect, (GUI.WindowFunction)ConsoleWindow, "MalumConsole");
42+
_windowRect = GUI.Window(1, _windowRect, (GUI.WindowFunction)ConsoleWindow, "MalumConsole");
4343
}
4444

4545
private void ConsoleWindow(int windowID)
4646
{
4747
GUILayout.BeginVertical();
48-
scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, true);
48+
_scrollPosition = GUILayout.BeginScrollView(_scrollPosition, false, true);
4949

50-
foreach (var log in logEntries)
50+
foreach (var log in _logEntries)
5151
{
52-
GUILayout.Label(log, logStyle); // Use the custom GUIStyle with the specified font size
52+
GUILayout.Label(log, _logStyle);
5353
}
5454

5555
GUILayout.EndScrollView();
56+
57+
GUILayout.BeginHorizontal();
58+
if (GUILayout.Button("Clear Log", GUILayout.Width(235)))
59+
{
60+
_logEntries.Clear();
61+
}
62+
if (GUILayout.Button("Copy Log to clipboard"))
63+
{
64+
GUIUtility.systemCopyBuffer = String.Join("\n", _logEntries.ToArray());
65+
}
66+
GUILayout.EndHorizontal();
67+
5668
GUILayout.EndVertical();
5769

5870
GUI.DragWindow();

src/UI/MenuUI.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,12 @@ private void Start()
142142
new ToggleInfo(" Unlock Textbox", () => CheatToggles.chatJailbreak, x => CheatToggles.chatJailbreak = x)
143143
], []));
144144

145-
// Console is temporarly disabled until we implement some features for it
146-
147-
//groups.Add(new GroupInfo("Console", false, new List<ToggleInfo>() {
148-
// new ToggleInfo(" ConsoleUI", () => MalumMenu.consoleUI.isVisible, x => MalumMenu.consoleUI.isVisible = x),
149-
//}, new List<SubmenuInfo>()));
145+
groups.Add(new GroupInfo("Console", false, [
146+
new ToggleInfo(" Show Console", () => CheatToggles.showConsole, x => CheatToggles.showConsole = x),
147+
new ToggleInfo(" Log Deaths", () => CheatToggles.logDeaths, x => CheatToggles.logDeaths = x),
148+
new ToggleInfo(" Log Shapeshifts", () => CheatToggles.logShapeshifts, x => CheatToggles.logShapeshifts = x),
149+
new ToggleInfo(" Log Vents", () => CheatToggles.logVents, x => CheatToggles.logVents = x),
150+
], []));
150151

151152
groups.Add(new GroupInfo("Host-Only", false,
152153
[

src/Utilities/Utils.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,22 @@ public static int getClientIdByPlayer(PlayerControl player)
9898
return client == null ? -1 : client.Id;
9999
}
100100

101+
/// <summary>
102+
/// Get a player's real name, display name, and whether they are disguised or not.
103+
/// </summary>
104+
/// <param name="player">The PlayerControl of the player to get the identity of.</param>
105+
/// <returns>A tuple containing the player's real name, display name, and whether they are disguised or not.</returns>
106+
public static (string realName, string displayName, bool isDisguised) GetPlayerIdentity(PlayerControl player)
107+
{
108+
if (player == null || player.Data == null) return ("", "", false);
109+
110+
var realName = $"<color=#{ColorUtility.ToHtmlStringRGB(player.Data.Color)}>{player.Data.PlayerName}</color>";
111+
var displayName = $"<color=#{ColorUtility.ToHtmlStringRGB(Palette.PlayerColors[player.CurrentOutfit.ColorId])}>{player.CurrentOutfit.PlayerName}</color>";
112+
var isDisguised = player.CurrentOutfit.PlayerName != player.Data.PlayerName;
113+
114+
return (realName, displayName, isDisguised);
115+
}
116+
101117
// Check if player is currently vanished
102118
public static bool isVanished(NetworkedPlayerInfo playerInfo)
103119
{
@@ -329,6 +345,17 @@ public static SystemTypes getCurrentRoom(){
329345
return HudManager.Instance.roomTracker.LastRoom.RoomId;
330346
}
331347

348+
/// <summary>
349+
/// Get the PlainShipRoom from a Vector2 position.
350+
/// </summary>
351+
/// <param name="position">The position to check for the room.</param>
352+
/// <returns>The PlainShipRoom at the given position, or null if none found.</returns>
353+
public static PlainShipRoom GetRoomFromPosition(Vector2 position)
354+
{
355+
return ShipStatus.Instance == null ? null : ShipStatus.Instance.AllRooms.FirstOrDefault(
356+
room => room != null && room.roomArea != null && room.roomArea.OverlapPoint(position));
357+
}
358+
332359
// Fancy colored ping text
333360
public static string getColoredPingText(int ping)
334361
{

0 commit comments

Comments
 (0)