Skip to content
17 changes: 9 additions & 8 deletions src/MalumMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ namespace MalumMenu;
public partial class MalumMenu : BasePlugin
{
public Harmony Harmony { get; } = new(Id);
public static string malumVersion = "2.4.2";
public static List<string> supportedAU = new List<string> { "2024.9.4" };
public static string malumVersion = "2.5.3";
public static List<string> supportedAU = new List<string> { "2025.9.9" };
public static MenuUI menuUI;
// public static ConsoleUI consoleUI;
public static ConsoleUI consoleUI;
public static ConfigEntry<string> menuKeybind;
public static ConfigEntry<string> menuHtmlColor;
public static ConfigEntry<string> spoofLevel;
Expand Down Expand Up @@ -77,15 +77,16 @@ public override void Load()
Harmony.PatchAll();

menuUI = AddComponent<MenuUI>();
// consoleUI = AddComponent<ConsoleUI>();
consoleUI = AddComponent<ConsoleUI>();

// Disable Telemetry (haven't fully tested if it works, but according to Unity docs it should)
// Disable Telemetry (better one)
if (noTelemetry.Value){

Analytics.enabled = false;
Analytics.deviceStatsEnabled = false;
Analytics.enabled = false;
Analytics.initializeOnStartup = false;
Analytics.limitUserTracking = true;
CrashReportHandler.enableCaptureExceptions = false;
PerformanceReporting.enabled = false;

}

SceneManager.add_sceneLoaded((Action<Scene, LoadSceneMode>) ((scene, _) =>
Expand Down
3 changes: 2 additions & 1 deletion src/Patches/MeetingHudPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,6 @@ public static void Prefix(MeetingHud __instance)
voteSpreader.Votes.Clear();
}
MeetingHud_Update.votedPlayers.Clear();
PlayerPhysics_LateUpdate.ClearAllStates();
}
}
}
47 changes: 46 additions & 1 deletion src/Patches/OtherPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using UnityEngine;
using System;
using System.Security.Cryptography;
using System.Collections.Generic;

namespace MalumMenu;

Expand Down Expand Up @@ -227,4 +228,48 @@ public static void Postfix(Vent __instance, NetworkedPlayerInfo pc, ref bool can
}
}
}
}
}

[HarmonyPatch(typeof(GameData), nameof(GameData.RemovePlayer))]
public static class GameData_RemovePlayer_Patch
{
private static readonly HashSet<byte> notifiedDisconnects = new();

public static void ClearNotifiedDisconnects() => notifiedDisconnects.Clear();

// Use a Prefix patch to capture the PlayerInfo *before* it gets removed from the game's data lists.
public static void Prefix(GameData __instance, byte playerId)
{
// Only notify during an active game, not in lobby or post-game.
if (CheatToggles.notifyOnDisconnect && Utils.isInGame)
{
// If we've already notified for this player, don't do it again.
if (notifiedDisconnects.Contains(playerId))
{
return;
}

var player = __instance.GetPlayerById(playerId);
// The check for `!player.Disconnected` was preventing this from ever firing. It's removed.
if (player != null)
{
NotificationHandler.HandlePlayerDisconnect(player);
// Add the player to the set so we don't notify again for this game session.
notifiedDisconnects.Add(playerId);
}
}
}
}

[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameEnd))]
public static class AmongUsClient_OnGameEnd_Patch
{
public static void Postfix()
{
// Clear the set of notified disconnected players when a game ends.
GameData_RemovePlayer_Patch.ClearNotifiedDisconnects();

// Clear the set of notified killed victims when a game ends.
PlayerControl_MurderPlayer_Patch.ClearNotifiedKilledVictims();
}
}
45 changes: 45 additions & 0 deletions src/Patches/PlayerControlPatches.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using HarmonyLib;
using System.Collections.Generic;

namespace MalumMenu;

Expand Down Expand Up @@ -80,3 +81,47 @@ public static void Prefix(ref bool shouldAnimate){
}
}
}

[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.MurderPlayer))]
public static class PlayerControl_MurderPlayer_Patch
{
// A HashSet to track victims for whom a notification has already been sent on this client.
// This prevents duplicate notifications if the event is somehow triggered more than once.
private static readonly HashSet<byte> notifiedKilledVictims = new();

/// <summary>
/// Clears the set of notified victims. This must be called at the end of each game.
/// </summary>
public static void ClearNotifiedKilledVictims() => notifiedKilledVictims.Clear();

// A Prefix runs *before* the original method. This lets us check conditions before the kill happens.
public static void Prefix(PlayerControl __instance, PlayerControl target)
{
if (target == null)
{
return;
}

// Check if the target is protected by a Guardian Angel.
if (target.protectedByGuardianId != -1)
{
// This is a "save" event. Show the notification but do not add the player to the
// notifiedKilledVictims set, allowing a future kill notification to appear.
NotificationHandler.HandleGuardianAngelSave(__instance, target);
}
else
{
// This is a potential kill event. Check if we've already notified for this victim's death.
if (notifiedKilledVictims.Contains(target.PlayerId))
{
return;
}

// If not protected and not already notified, the kill is successful.
NotificationHandler.HandlePlayerKill(__instance, target);

// Add the victim's ID to the set ONLY on a successful kill to prevent duplicate kill notifications.
notifiedKilledVictims.Add(target.PlayerId);
}
}
}
44 changes: 39 additions & 5 deletions src/Patches/PlayerPhysicsPatches.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,48 @@
using HarmonyLib;
using UnityEngine;
using System.Collections.Generic;

namespace MalumMenu;

[HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.LateUpdate))]
public static class PlayerPhysics_LateUpdate
{
private static readonly Dictionary<byte, bool> wasInVent = new();
public static readonly Dictionary<byte, Vector2> lastKnownPositions = new();

public static void ClearAllStates()
{
wasInVent.Clear();
lastKnownPositions.Clear();
}

public static void Postfix(PlayerPhysics __instance)
{
if (__instance.myPlayer != null && !__instance.myPlayer.Data.IsDead)
{
// Update the player's last known position every frame they are not in a vent.
if (!__instance.myPlayer.inVent)
{
lastKnownPositions[__instance.myPlayer.PlayerId] = __instance.myPlayer.GetTruePosition();
}

// Vent usage detection
if (CheatToggles.notifyOnVent && Utils.isInGame)
{
byte playerId = __instance.myPlayer.PlayerId;
bool currentlyInVent = __instance.myPlayer.inVent;

if (wasInVent.TryGetValue(playerId, out bool previouslyInVent) && currentlyInVent != previouslyInVent)
{
Vector2 positionToCheck = currentlyInVent ? lastKnownPositions[playerId] : __instance.myPlayer.GetTruePosition();
PlainShipRoom room = Utils.getRoomFromPosition(positionToCheck);
string roomName = room != null ? room.RoomId.ToString() : "an unknown location";

NotificationHandler.HandleVent(__instance.myPlayer, currentlyInVent, roomName);
}
wasInVent[playerId] = currentlyInVent;
}
}

MalumESP.playerNametags(__instance);
MalumESP.seeGhostsCheat(__instance);
Expand Down Expand Up @@ -40,11 +75,10 @@ public static void Postfix(PlayerPhysics __instance)
{
DeadBody deadBody = bodyObject.GetComponent<DeadBody>();

if (deadBody){
if (!deadBody.Reported){ // Only draw tracers for unreported dead bodies
TracersHandler.drawBodyTracer(deadBody);
}
if (deadBody && !deadBody.Reported)
{
TracersHandler.drawBodyTracer(deadBody);
}
}
}
}
}
6 changes: 6 additions & 0 deletions src/UI/CheatToggles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ public struct CheatToggles
public static bool freeCosmetics = true;
public static bool avoidBans = true;

//Notifications
public static bool notifyOnDeath;
public static bool notifyOnDisconnect;
public static bool notifyOnVent;
public static bool showNotificationLog;

public static void DisablePPMCheats(string variableToKeep)
{
reportBody = variableToKeep != "reportBody" ? false : reportBody;
Expand Down
9 changes: 7 additions & 2 deletions src/UI/ConsoleUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class ConsoleUI : MonoBehaviour
{
public bool isVisible = false;
private Vector2 scrollPosition = Vector2.zero;
private List<string> logEntries = new List<string>();
public static List<string> logEntries = new List<string>();
private Rect windowRect = new Rect(320, 10, 500, 300); // Adjust size and position as needed
private GUIStyle logStyle;

Expand All @@ -33,7 +33,8 @@ private void OnGUI()

logStyle = new GUIStyle(GUI.skin.label)
{
fontSize = 20
fontSize = 20,
richText = true // Essential for colored names
};

}
Expand All @@ -57,6 +58,10 @@ private void ConsoleWindow(int windowID)
GUILayout.Label(log, logStyle); // Use the custom GUIStyle with the specified font size
}

if (GUILayout.Button("Clear Log")){
logEntries.Clear();
}

GUILayout.EndScrollView();
GUILayout.EndVertical();

Expand Down
18 changes: 10 additions & 8 deletions src/UI/MenuUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ private void Start()
groups.Add(new GroupInfo("Chat", false, new List<ToggleInfo>() {
new ToggleInfo(" Enable Chat", () => CheatToggles.alwaysChat, x => CheatToggles.alwaysChat = x),
new ToggleInfo(" Unlock Textbox", () => CheatToggles.chatJailbreak, x => CheatToggles.chatJailbreak = x)
}, new List<SubmenuInfo>()));
}, []));

// Host-Only cheats are temporarly disabled because of some bugs

Expand All @@ -113,12 +113,6 @@ private void Start()
// new ToggleInfo(" VoteImmune", () => CheatSettings.voteImmune, x => CheatSettings.voteImmune = x)
//}, new List<SubmenuInfo>()));

// Console is temporarly disabled until we implement some features for it

//groups.Add(new GroupInfo("Console", false, new List<ToggleInfo>() {
// new ToggleInfo(" ConsoleUI", () => MalumMenu.consoleUI.isVisible, x => MalumMenu.consoleUI.isVisible = x),
//}, new List<SubmenuInfo>()));

groups.Add(new GroupInfo("Host-Only", false,
new List<ToggleInfo>{
new ToggleInfo(" Kill While Vanished", () => CheatToggles.killVanished, x => CheatToggles.killVanished = x),
Expand All @@ -139,7 +133,15 @@ private void Start()
new ToggleInfo(" Free Cosmetics", () => CheatToggles.freeCosmetics, x => CheatToggles.freeCosmetics = x),
new ToggleInfo(" Avoid Penalties", () => CheatToggles.avoidBans, x => CheatToggles.avoidBans = x),
new ToggleInfo(" Unlock Extra Features", () => CheatToggles.unlockFeatures, x => CheatToggles.unlockFeatures = x),
}, new List<SubmenuInfo>()));
}, []));

groups.Add(new GroupInfo("Notifications", false, [
new ToggleInfo(" Console", () => MalumMenu.consoleUI.isVisible, x => MalumMenu.consoleUI.isVisible = x),
new ToggleInfo(" On Player Death", () => CheatToggles.notifyOnDeath, x => CheatToggles.notifyOnDeath = x),
new ToggleInfo(" On Player Disconnect", () => CheatToggles.notifyOnDisconnect, x => CheatToggles.notifyOnDisconnect = x),
new ToggleInfo(" On Vent Usage", () => CheatToggles.notifyOnVent, x => CheatToggles.notifyOnVent = x),
new ToggleInfo(" Show Notification Log", () => CheatToggles.showNotificationLog, x => CheatToggles.showNotificationLog = x)
], []));
}

private void Update(){
Expand Down
Loading