Skip to content

Commit

Permalink
Finish visual and end game
Browse files Browse the repository at this point in the history
  • Loading branch information
NikoCat233 committed Mar 1, 2025
1 parent ac93b26 commit bb4fad6
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 47 deletions.
95 changes: 68 additions & 27 deletions GameModes/SpeedRun.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using UnityEngine;
using static TOHE.Utils;
using static TOHE.Translator;
using Il2CppSystem.Text;

namespace TOHE;

Expand Down Expand Up @@ -106,21 +107,14 @@ public static void Init()
PlayerNumKills = [];
}

public static void OnGameEnd()
{
StartedAt = 0;
PlayerTaskCounts = [];
PlayerTaskFinishedAt = [];
PlayerNumKills = [];
}

public static void RpcSyncSpeedRunStates(byte specificPlayerId = 255) // Not 255, Sync single single player
{
if (specificPlayerId != 255)
{
if (Main.PlayerStates.TryGetValue(specificPlayerId, out var state) && state.MainRole == CustomRoles.Runner)
{
var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncSpeedRunStates, ExtendedPlayerControl.RpcSendOption);
writer.Write(StartedAt.ToString());
writer.Write(1);
writer.Write(specificPlayerId);
writer.Write((byte)PlayerTaskCounts[specificPlayerId].Item1);
Expand All @@ -144,6 +138,7 @@ public static void RpcSyncSpeedRunStates(byte specificPlayerId = 255) // Not 255
{
var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncSpeedRunStates, ExtendedPlayerControl.RpcSendOption);
var amount = Main.PlayerStates.Count(x => x.Value.MainRole == CustomRoles.Runner);
writer.Write(StartedAt.ToString());
writer.Write((byte)amount);

foreach (var id in Main.PlayerStates.Where(x => x.Value.MainRole == CustomRoles.Runner).Select(x => x.Value.PlayerId))
Expand All @@ -168,6 +163,7 @@ public static void RpcSyncSpeedRunStates(byte specificPlayerId = 255) // Not 255

public static void HandleSyncSpeedRunStates(MessageReader reader)
{
StartedAt = long.Parse(reader.ReadString());
var amount = reader.ReadByte();
for (int i = 0; i < amount; i++)
{
Expand Down Expand Up @@ -213,40 +209,76 @@ public static void OnMurderPlayer(PlayerControl killer, PlayerControl target)
NotifyRoles();
}

public static void AppendGameState(Il2CppSystem.Text.StringBuilder builder)
public static string GetGameState(bool forGameEnd = false)
{
StringBuilder builder = new();
var playerInfoList = new List<(byte playerId, string playerName, bool isAlive, bool finishedTasks, int kills, long finishTime, int completedTasks, int totalTasks)>();

foreach (var kvp in Main.PlayerStates)
{
var playerId = kvp.Value.PlayerId;
var playerState = kvp.Value;

if (kvp.Value.MainRole is not CustomRoles.Runner) continue;

var playerName = ColorString(Main.PlayerColors.GetValueOrDefault(playerId, Color.white), GetPlayerById(playerId)?.GetRealName() ?? "ERROR");
var playerName = ColorString(Main.PlayerColors.GetValueOrDefault(playerId, Color.white), Main.AllPlayerNames[playerId] ?? "ERROR");
bool isAlive = !playerState.IsDead;
bool finishedTasks = false;
int kills = 0;
long finishTime = long.MaxValue;
int completedTasks = 0;
int totalTasks = 0;

if (PlayerTaskCounts.ContainsKey(playerId))
{
var taskCount = PlayerTaskCounts[playerId];
completedTasks = taskCount.Item1;
totalTasks = taskCount.Item2;

if (taskCount.Item1 >= taskCount.Item2 && taskCount.Item1 != 0)
{
finishedTasks = true;
finishTime = PlayerTaskFinishedAt.ContainsKey(playerId) ?
PlayerTaskFinishedAt[playerId] - StartedAt : long.MaxValue;
kills = PlayerNumKills.ContainsKey(playerId) ? PlayerNumKills[playerId] : 0;
}
}

playerInfoList.Add((playerId, playerName, isAlive, finishedTasks, kills, finishTime, completedTasks, totalTasks));
}

// Alive > Finish all tasks > Kill num > Time cost to finish all tasks > Not yet finish tasks then task num
playerInfoList = playerInfoList.OrderByDescending(p => p.isAlive)
.ThenByDescending(p => p.finishedTasks)
.ThenByDescending(p => p.kills)
.ThenBy(p => p.finishTime)
.ThenByDescending(p => p.completedTasks)
.ToList();

builder.AppendLine(playerName);
for (int i = 0; i < playerInfoList.Count; i++)
{
var info = playerInfoList[i];
builder.Append((forGameEnd && info.isAlive ? "<#c4aa02>★</color>" : $"{i + 1}. ") + $"{info.playerName} ({(info.isAlive ? ColorString(Color.green, GetString("Alive")) : ColorString(Color.gray, GetString("Death")))})");

if (playerState.IsDead)
if (info.finishedTasks)
{
builder.Append(Utils.ColorString(Color.gray, $"( {PlayerTaskCounts[playerId].Item1}/{PlayerTaskCounts[playerId].Item2})"));
builder.Append(" " + string.Format(GetString("TaskFinishedSeconds"), info.finishTime));
builder.Append(" " + string.Format(GetString("KillCount"), info.kills));
}
else
{
if (PlayerTaskCounts.ContainsKey(playerId))
{
var taskCount = PlayerTaskCounts[playerId];
if (taskCount.Item1 >= taskCount.Item2 && taskCount.Item1 != 0)
{
builder.Append($" Tasks completed at: {PlayerTaskFinishedAt[playerId]}");
builder.Append($" Kills: {PlayerNumKills[playerId]}");
}
else
{
builder.Append(ColorString(Color.yellow, $" ({taskCount.Item1}/{taskCount.Item2})"));
}
}
var taskColor = info.isAlive ? Color.yellow : Color.gray;
builder.Append(ColorString(taskColor, $" ({info.completedTasks}/{info.totalTasks})"));
}

// 如果不是最后一个玩家,添加换行符
if (i < playerInfoList.Count - 1)
{
builder.AppendLine();
}
}

return builder.ToString();
}
}

Expand All @@ -256,11 +288,20 @@ public override bool CheckForEndGame(out GameOverReason reason)
{
reason = GameOverReason.ImpostorByKill;

if (Main.AllAlivePlayerControls.Count(x => x.Is(CustomRoles.Runner)) <= 1) return true;
if (Main.AllAlivePlayerControls.Count(x => x.Is(CustomRoles.Runner)) <= 1)
{
CustomWinnerHolder.WinnerIds.Clear();
Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Runner)).Select(x => x.PlayerId).Do(x => CustomWinnerHolder.WinnerIds.Add(x));
Main.DoBlockNameChange = true;
return true;
}

if (SpeedRun.StartedAt != 0 && GetTimeStamp() - SpeedRun.StartedAt >= SpeedRun.SpeedRun_MaxTimeForTie.GetInt())
{
reason = GameOverReason.HumansByTask;
CustomWinnerHolder.WinnerIds.Clear();
Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Runner)).Select(x => x.PlayerId).Do(x => CustomWinnerHolder.WinnerIds.Add(x));
Main.DoBlockNameChange = true;
return true;
}

Expand Down
4 changes: 4 additions & 0 deletions Modules/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,10 @@ public static void ShowLastRoles(byte PlayerId = byte.MaxValue, bool sendMessage
sb.Append($"\n  ").Append(EndGamePatch.SummaryText[id.Item2]);
}
break;
case CustomGameMode.SpeedRun:
sb.Clear();
sb.Append(SpeedRun.GetGameState(forGameEnd: true));
break;
default: // Normal game
foreach (byte id in cloneRoles.ToArray())
{
Expand Down
13 changes: 13 additions & 0 deletions Patches/CheckGameEndPatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,19 @@ public static bool Prefix()
return false;
}

// Speed Run
if (Options.CurrentGameMode == CustomGameMode.SpeedRun)
{
if (WinnerIds.Count > 0 || WinnerTeam != CustomWinner.Default)
{
SpeedRun.RpcSyncSpeedRunStates();
ShipStatus.Instance.enabled = false;
StartEndGame(reason);
predicate = null;
}
return false;
}

// Start end game
if (WinnerTeam != CustomWinner.Default)
{
Expand Down
29 changes: 14 additions & 15 deletions Patches/HudPatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -353,27 +353,26 @@ public static void Postfix(TaskPanelBehaviour __instance)

break;
case CustomGameMode.SpeedRun:
Il2CppSystem.Text.StringBuilder builder2 = new();
builder2.Append(AllText + "\r\n");
Runner runner = player.GetRoleClass() as Runner;
var lines2 = taskText.Split("\r\n</color>\n")[0].Split("\r\n\n")[0].Split("\r\n");
StringBuilder sb2 = new();
foreach (var eachLine in lines2)
{
var line = eachLine.Trim();
if ((line.StartsWith("<color=#FF1919FF>") || line.StartsWith("<color=#FF0000FF>")) && sb2.Length < 1 && !line.Contains('(')) continue;
sb2.Append(line + "\r\n");
}

if (runner != null && !runner.BasisChanged)
if (sb2.Length > 1)
{
for (int i = 0; i < PlayerControl.LocalPlayer.myTasks.Count; i++)
{
PlayerTask playerTask = PlayerControl.LocalPlayer.myTasks[i];
if (playerTask)
{
playerTask.AppendTaskText(builder2);
}
}
var text = sb2.ToString().TrimEnd('\n').TrimEnd('\r');
if (!Utils.HasTasks(player.Data, false) && sb2.ToString().Count(s => (s == '\n')) >= 1)
text = $"{Utils.ColorString(Utils.GetRoleColor(player.GetCustomRole()).ShadeColor(0.2f), GetString("FakeTask"))}\r\n{text}";
AllText += $"\r\n\r\n<size=85%>{text}</size>";
}

SpeedRun.AppendGameState(builder2);
AllText = builder2.ToString();
AllText += $"\r\n\r\n<size=70%>{SpeedRun.GetGameState()}</size>";

break;

}

__instance.taskText.text = AllText;
Expand Down
24 changes: 24 additions & 0 deletions Patches/IntroPatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@ public static void Postfix(IntroCutscene __instance)
__instance.RoleBlurbText.color = color;
__instance.RoleBlurbText.text = "KILL EVERYONE TO WIN";
}
else if (Options.CurrentGameMode == CustomGameMode.SpeedRun)
{
var color = ColorUtility.TryParseHtmlString("#00ffff", out var c) ? c : new(255, 255, 255, 255);
__instance.YouAreText.transform.gameObject.SetActive(false);
__instance.RoleText.text = GetString("SpeedRun");
__instance.RoleText.color = color;
__instance.RoleBlurbText.color = color;
__instance.RoleBlurbText.text = GetString("RunnerInfo");
}
else
{
if (!role.IsVanilla())
Expand Down Expand Up @@ -603,6 +612,15 @@ public static void Postfix(IntroCutscene __instance)
__instance.ImpostorText.text = "KILL EVERYONE TO WIN";
}

if (Options.CurrentGameMode == CustomGameMode.SpeedRun)
{
__instance.TeamTitle.text = GetString("SpeedRun");
__instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(0, 255, 255, byte.MaxValue);
PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter);
__instance.ImpostorText.gameObject.SetActive(true);
__instance.ImpostorText.text = GetString("RunnerInfo");
}

// I hope no one notices this in code
// unfortunately niko noticed while fixing others' shxt
if (Input.GetKey(KeyCode.RightShift))
Expand Down Expand Up @@ -848,6 +866,12 @@ public static void Postfix()
}
}

if (Options.CurrentGameMode is CustomGameMode.SpeedRun)
{
SpeedRun.StartedAt = Utils.GetTimeStamp();
SpeedRun.RpcSyncSpeedRunStates();
}

foreach (var player in Main.AllPlayerControls)
{
if (player.Is(CustomRoles.GM))
Expand Down
10 changes: 7 additions & 3 deletions Patches/OutroPatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,6 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ref En
GameOptionsSender.AllSenders.Clear();
GameOptionsSender.AllSenders.Add(new NormalGameOptionsSender());
/* Send SyncSettings RPC */

SpeedRun.OnGameEnd();
}
}
}
Expand Down Expand Up @@ -210,7 +208,7 @@ public static void Postfix(EndGameManager __instance)
string AdditionalWinnerText = "";
string CustomWinnerColor = Utils.GetRoleColorCode(CustomRoles.Crewmate);

if (Options.CurrentGameMode == CustomGameMode.FFA)
if (Options.CurrentGameMode is CustomGameMode.FFA or CustomGameMode.SpeedRun)
{
var winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault();
__instance.BackgroundBar.material.color = new Color32(0, 255, 255, 255);
Expand Down Expand Up @@ -359,6 +357,12 @@ static string GetAdditionalWinnerRoleName(CustomRoles role)
sb.Append($"\n ").Append(EndGamePatch.SummaryText[id.Item2]);
break;
}
case CustomGameMode.SpeedRun:
{
sb.Clear();
sb.Append(SpeedRun.GetGameState(forGameEnd: true));
break;
}
default: // Normal game
{
sb.Append($"</b>\n");
Expand Down
3 changes: 2 additions & 1 deletion Resources/Lang/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -4178,7 +4178,7 @@
"BypassRateLimitAC": "Experimental Changes to Bypass RateLimit Anticheat",

"Runner": "Runner",
"RunnerInfo": "Finish your tasks and kill others. Be the last one standing!",
"RunnerInfo": "Finish your tasks and kill others.\nBe the last one standing!",
"RunnerInfoLong": "(SpeedRun):\nAs the Runner, you have to finish all your tasks to get your kill button, and kill other runners to win.\nBe the last one standing!",
"SpeedRun": "Speed Run",
"SpeedRun_NumCommonTasks": "Num of Common Tasks",
Expand All @@ -4200,6 +4200,7 @@
"SpeedRun_ProtectKcd": "Reset killer's kcd to * seconds",
"SpeedRun_EndGameForTime": "Force end the game after * seconds",
"SpeedRun_MaxTimeForTie": "* seconds to end the game",
"TaskFinishedSeconds": "Tasks Finished in {0}s",

"ChiefOfPoliceSkillCooldown": "Cooldown for recruiting <color=#ffb347>Sheriffs</color>",
"PolicCanImpostor": "Can recruit <color=#ff1919>Impostor</color>",
Expand Down
3 changes: 2 additions & 1 deletion Resources/roleColor.json
Original file line number Diff line number Diff line change
Expand Up @@ -245,5 +245,6 @@
"Sloth": "#376db8",
"Eavesdropper": "#ffe6bf",
"Shocker": "#CCCC00",
"Revenant": "#cc9329"
"Revenant": "#cc9329",
"Runner": "#fffb00"
}

0 comments on commit bb4fad6

Please sign in to comment.