Experimental: Auto Rejoin for Modded Clients (credit to Maxi0fc/AutoRejoin) (#643)

* autorejoin for modded clients (just 1 thing, why did it crash on my client?)

* ahhh, so put log there is the cause (I think?)

* Update README.md

* Update README.md
This commit is contained in:
Newchan authored and GitHub committed 2026-07-02 07:57:45 +02:00
1 parent 9303094e68
commit 32ddd3cba9
10 files changed
+99 -71

No files matched your search

+2
View File
@@ -201,6 +201,7 @@ public class Main : BasePlugin
public static ConfigEntry<bool> UnlockFps { get; private set; }
public static ConfigEntry<bool> ShowFps { get; private set; }
public static ConfigEntry<bool> AutoStart { get; private set; }
public static ConfigEntry<bool> AutoPlayAgain { get; private set; }
public static ConfigEntry<bool> ForceOwnLanguage { get; private set; }
public static ConfigEntry<bool> ForceOwnLanguageRoleName { get; private set; }
public static ConfigEntry<bool> EnableCustomButton { get; private set; }
@@ -359,6 +360,7 @@ public class Main : BasePlugin
UnlockFps = Config.Bind("Client Options", "UnlockFPS", false);
ShowFps = Config.Bind("Client Options", "ShowFPS", false);
AutoStart = Config.Bind("Client Options", "AutoStart", false);
AutoPlayAgain = Config.Bind("Client Options", "AutoPlayAgain", false);
ForceOwnLanguage = Config.Bind("Client Options", "ForceOwnLanguage", false);
ForceOwnLanguageRoleName = Config.Bind("Client Options", "ForceOwnLanguageRoleName", false);
EnableCustomButton = Config.Bind("Client Options", "EnableCustomButton", true);
+1 -1
View File
@@ -106,7 +106,7 @@ internal static class Logger
if (!Main.Loaded)
{
LateTask.New(() => CustomLogger.Instance.Log(level.ToString(), LogText, multiLine), 1f, "Log Retry");
LateTask.New(() => CustomLogger.Instance.Log(level.ToString(), LogText, multiLine), 1f, log: false);
return;
}
+4 -4
View File
@@ -436,7 +436,7 @@ public static class Options
public static OptionItem MareHasIncreasedSpeed;
public static OptionItem MareSpeedDuringLightsOut;
public static OptionItem AutoPlayAgain;
//public static OptionItem AutoPlayAgain;
public static OptionItem AutoPlayAgainCountdown;
public static OptionItem AutoStartTimer;
@@ -1682,10 +1682,10 @@ public static class Options
AutoStartTimer = new IntegerOptionItem(44423, "AutoStartTimer", new(10, 600, 1), 20, TabGroup.SystemSettings)
.SetValueFormat(OptionFormat.Seconds);
AutoPlayAgain = new BooleanOptionItem(44424, "AutoPlayAgain", false, TabGroup.SystemSettings);
//AutoPlayAgain = new BooleanOptionItem(44424, "AutoPlayAgain", false, TabGroup.SystemSettings);
AutoPlayAgainCountdown = new IntegerOptionItem(44425, "AutoPlayAgainCountdown", new(1, 90, 1), 10, TabGroup.SystemSettings)
.SetParent(AutoPlayAgain);
AutoPlayAgainCountdown = new IntegerOptionItem(44425, "AutoPlayAgainCountdown", new(1, 90, 1), 10, TabGroup.SystemSettings);
//.SetParent(AutoPlayAgain);
AutoGMPollCommandAfterJoin = new BooleanOptionItem(19309, "AutoGMPollCommandAfterJoin", false, TabGroup.SystemSettings)
.SetHeader(true);
+4
View File
@@ -15,6 +15,7 @@ public static class OptionsMenuBehaviourStartPatch
private static ClientOptionItem UnlockFPS;
private static ClientOptionItem ShowFPS;
private static ClientOptionItem AutoStart;
private static ClientOptionItem AutoPlayAgain;
private static ClientOptionItem ForceOwnLanguage;
private static ClientOptionItem ForceOwnLanguageRoleName;
private static ClientOptionItem EnableCustomButton;
@@ -88,6 +89,9 @@ public static class OptionsMenuBehaviourStartPatch
}
}
if (AutoPlayAgain == null || !AutoPlayAgain.ToggleButton)
AutoPlayAgain = ClientOptionItem.Create("AutoPlayAgain", Main.AutoPlayAgain, __instance);
if (ForceOwnLanguage == null || !ForceOwnLanguage.ToggleButton)
ForceOwnLanguage = ClientOptionItem.Create("ForceOwnLanguage", Main.ForceOwnLanguage, __instance);
+53 -39
View File
@@ -1,3 +1,5 @@
using System;
using System.Linq;
using HarmonyLib;
using InnerNet;
using TMPro;
@@ -7,62 +9,74 @@ using static EHR.Translator;
namespace EHR.Patches;
[HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.ShowButtons))]
public static class EndGameManagerPatch
public static class EndGameManagerPatch
{
private static GameObject CountdownText;
private static bool IsRestarting { get; set; }
public static void Postfix(EndGameManager __instance)
public static void Postfix(EndGameManager __instance)
{
GameEndChecker.LoadingEndScreen = false;
if (!AmongUsClient.Instance.AmHost || !Options.AutoPlayAgain.GetBool()) return;
if (!Main.AutoPlayAgain.Value) return;
IsRestarting = false;
LateTask.New(() =>
{
if (CountdownText != null) GameObject.Destroy(CountdownText);
IsRestarting = true;
int startSeconds = AmongUsClient.Instance.AmHost ? Options.AutoPlayAgainCountdown.GetInt() : 10;
LateTask.New(() => {
Logger.Msg("Beginning Auto Play Again Countdown!", "AutoPlayAgain");
IsRestarting = true;
BeginAutoPlayAgainCountdown(__instance, Options.AutoPlayAgainCountdown.GetInt());
}, 0.5f, "Auto Play Again");
BeginAutoPlayAgainCountdown(__instance, startSeconds);
}, AmongUsClient.Instance.AmHost ? 0.5f : 1.5f, "Auto Play Again");
}
private static void BeginAutoPlayAgainCountdown(EndGameManager endGameManager, int seconds)
private static void BeginAutoPlayAgainCountdown(EndGameManager endGameManager, int seconds)
{
if (!IsRestarting) return;
if (!IsRestarting || endGameManager == null || endGameManager.Navigation == null) { CleanupText(); return; }
if (endGameManager == null) return;
EndGameNavigation navigation = endGameManager.Navigation;
if (navigation == null) return;
if (seconds == Options.AutoPlayAgainCountdown.GetInt())
if (CountdownText == null)
{
CountdownText = new("CountdownText")
{
transform =
{
position = new(0f, 2.5f, 10f)
}
};
var countdownTextTMP = CountdownText.AddComponent<TextMeshPro>();
countdownTextTMP.text = string.Format(GetString("CountdownText"), seconds);
countdownTextTMP.alignment = TextAlignmentOptions.Center;
countdownTextTMP.fontSize = 3f;
}
else
{
var countdownTextTMP = CountdownText.GetComponent<TextMeshPro>();
countdownTextTMP.text = string.Format(GetString("CountdownText"), seconds);
CountdownText = new GameObject("CountdownText") { transform = { position = new Vector3(0f, 2.5f, 10f) } };
var tmp = CountdownText.AddComponent<TextMeshPro>();
tmp.alignment = TextAlignmentOptions.Center;
tmp.fontSize = 3f;
}
if (seconds == 0)
CountdownText.GetComponent<TextMeshPro>().text = string.Format(GetString("CountdownText"), seconds);
if (seconds <= 0)
{
navigation.NextGame();
CountdownText.transform.DestroyChildren();
if (AmongUsClient.Instance.AmHost) endGameManager.Navigation.NextGame();
else ClickPlayAgain();
CleanupText();
}
else LateTask.New(() => BeginAutoPlayAgainCountdown(endGameManager, seconds - 1), 1f, log: false);
}
private static void ClickPlayAgain()
{
var btn = Object.FindObjectsOfType<PassiveButton>().FirstOrDefault(x => x.gameObject.name == "PlayAgainButton");
if (btn == null) { Logger.Warn("PlayAgainButton not found.", "AutoPlayAgain"); return; }
try
{
btn.OnClick.Invoke();
Logger.Info("Successfully invoked PlayAgainButton click event.", "AutoPlayAgain");
}
else LateTask.New(() => { BeginAutoPlayAgainCountdown(endGameManager, seconds - 1); }, 1f, log: false);
catch (Exception ex)
{
Logger.Warn($"Direct click invocation failed: {ex.Message}. Attempting mechanical click fallback.", "AutoPlayAgain");
try { btn.ReceiveClickDown(); btn.ReceiveClickUp(); } catch { }
}
}
private static void CleanupText()
{
IsRestarting = false;
if (CountdownText == null) return;
GameObject.Destroy(CountdownText);
CountdownText = null;
}
}
+30 -22
View File
@@ -180,26 +180,27 @@ You can also report bugs and suggest features in the #bug-reports and #suggestio
| Rabbit | Trapster | Stalker | | Stressed |
| Randomizer | Trickster | Starspawn | | Swift |
| Retributionist | Twister | Cultist | | Sunglasses |
| Rhapsode | Undertaker | Sunnyboy | | Talkative |
| Ricochet | Vampire | Tank | | Taskcounter |
| Safeguard | Venerer | Technician | | Task Master |
| Scanner | Ventriloquist | Terrorist | | Tiebreaker |
| Scout | Vindicator | Thanos | | Tired |
| Security Guard | Visionary | Thief | | Torch |
| Sensor | Warlock | Tiger | | Truant |
| Sentinel | Wasp | Traitor | | Unbound |
| Sentry | Wildling | Tremor | | Undead |
| Sheriff | Wiper | Vector | | Underdog |
| Shiftguard | Witch | Vengeance | | Unlucky |
| Snitch | Yin Yanger | Vengeful Romantic | | Urgent |
| Socialite | Zombie | Virus | | Venom |
| Soothsayer | | Vortex | | Warden |
| Spiritualist | | Vulture | | Watcher |
| Speedrunner | | Wraith | | Workhorse |
| Speed Booster | | Werewolf | | YouTuber |
| Spy | | Weapon Master | | |
| Super Star | | Weatherman | | |
| Survivor | | Workaholic | | |
| Revenant | Undertaker | Sunnyboy | | Talkative |
| Rhapsode | Vampire | Tank | | Taskcounter |
| Ricochet | Venerer | Technician | | Task Master |
| Safeguard | Ventriloquist | Terrorist | | Tiebreaker |
| Scanner | Vindicator | Thanos | | Tired |
| Scout | Visionary | Thief | | Torch |
| Security Guard | Warlock | Tiger | | Truant |
| Sensor | Wasp | Traitor | | Unbound |
| Sentinel | Wildling | Tremor | | Undead |
| Sentry | Wiper | Vector | | Underdog |
| Sheriff | Witch | Vengeance | | Unlucky |
| Shiftguard | Yin Yanger | Vengeful Romantic | | Urgent |
| Snitch | Zombie | Virus | | Venom |
| Socialite | | Vortex | | Warden |
| Soothsayer | | Vulture | | Watcher |
| Spiritualist | | Wraith | | Workhorse |
| Speedrunner | | Werewolf | | YouTuber |
| Speed Booster | | Weapon Master | | |
| Spy | | Weatherman | | |
| Super Star | | Workaholic | | |
| Survivor | | | | |
| Swapper | | | | |
| Tar | | | | |
| Task Manager | | | | |
@@ -224,7 +225,7 @@ You can also report bugs and suggest features in the #bug-reports and #suggestio
| Witness | | | | |
| Wizard | | | | |
| | | | | |
| 143 | 113 | 120 | 21 | 117 |
| 144 | 113 | 120 | 21 | 117 |
## VIP, Moderator, and Admin perks
@@ -398,7 +399,8 @@ Click this badge to view our translation project:
| /xor | Specify 2 roles that will never spawn in the same game. | &#x1F538; **role** – A main role that will never spawn in the same game with the other role<br>&#x1F538; **role** – A main role that will never spawn in the same game with the other role<br> | :purple_circle: Everyone | :purple_square: Always | :x: |
| /ci<br>/chemistinfo | Show all processes available for the Chemist role. | – | :purple_circle: Everyone | :purple_square: Always | :x: |
| /forge | As the Forger, forge a fake role for a player. | &#x1F538; **id** – The player's ID to forge a fake role for<br>&#x1F538; **role** – The fake role to forge<br> | :purple_circle: Everyone | :yellow_square: In Meetings | :heavy_check_mark: |
| /choose<br>/pick | Choose your role as the Pawn after completing all tasks. | &#x1F538; **role** – The role to choose<br> | :purple_circle: Everyone | :yellow_square: In Meetings | :heavy_check_mark: |
| /choose<br>/pick | Choose your role as the Changeling, or as the Pawn after completing all tasks. | &#x1F538; **role** – The role to choose<br> | :purple_circle: Everyone | :yellow_square: In Meetings | :heavy_check_mark: |
| /mark | Mark a target as the Markseeker. | &#x1F538; **id** – The player's ID to mark as the target<br> | :purple_circle: Everyone | :yellow_square: In Meetings | :heavy_check_mark: |
| /copypreset<br>/presetcopy<br>/cpp | Copy all settings from a preset to another preset. | &#x1F538; **sourcepreset** – The preset to copy all settings from<br>&#x1F538; **targetpreset** – The preset to paste all settings to<br> | :yellow_circle: Host | :green_square: In Lobby | :x: |
| /addadmin | Add a player to the list of admins | &#x1F538; **id** – The player's ID to add to the list of admins<br> | :yellow_circle: Host | :purple_square: Always | :x: |
| /deleteadmin | Remove a player from the list of admins | &#x1F538; **id** – The player's ID to remove from the list of admins<br> | :yellow_circle: Host | :purple_square: Always | :x: |
@@ -552,6 +554,12 @@ Click this badge to view our translation project:
> - Reference: Custom Region Installer (adding modded regions)
>
### :star: [AutoRejoin](https://github.com/Maxi0fc/AutoRejoin) :
>
> - Reference: Auto Rejoin
>
### :star: [Crowded](https://github.com/CrowdedMods/CrowdedMod)
>
+2 -2
View File
@@ -8686,8 +8686,8 @@
"MaxWaitAutoStart": "Force start when Lobby Timer goes below (in minutes)",
"PlayerAutoStart": "Minimum Player Threshold to auto-start",
"AutoStartTimer": "Initial Countdown for auto-starting",
"AutoPlayAgain": "Auto Re-Enter Lobby after Game End",
"AutoPlayAgainCountdown": "Delay before Auto Re-Entering Lobby",
"AutoPlayAgain": "Auto Rejoin",
"AutoPlayAgainCountdown": "Delay before Auto Rejoining Lobby (Only works as the Host)",
"AutoGMPollCommandAfterJoin": "Auto Execute /gmpoll After Joining The Lobby",
"AutoGMPollCommandCooldown": "Delay Before Auto Executing /gmpoll (After Joining The Lobby)",
"AutoMPollCommandAfterJoin": "Auto Execute /mpoll After Joining The Lobby",
@@ -74,7 +74,7 @@ public class Inquirer : RoleBase
public static void ProcessGuesserUI(byte playerId, CustomRoles role)
{
PlayerControl pc = Utils.GetPlayerById(playerId);
if (!pc || !pc.IsAlive() || !pc.Is(CustomRoles.Inquirer) || Starspawn.IsDayBreak) return;
if (!pc || !pc.IsAlive() || !PlayerControl.LocalPlayer.Is(CustomRoles.Inquirer) || Starspawn.IsDayBreak) return;
var command = $"/check {playerId} {GetString(role.ToString())}";
+1 -1
View File
@@ -67,7 +67,7 @@ public class Forger : RoleBase
public static void ProcessGuesserUI(byte playerId, CustomRoles role)
{
PlayerControl pc = Utils.GetPlayerById(playerId);
if (!pc || !pc.IsAlive() || !pc.Is(CustomRoles.Forger) || Starspawn.IsDayBreak) return;
if (!pc || !pc.IsAlive() || !PlayerControl.LocalPlayer.Is(CustomRoles.Forger) || Starspawn.IsDayBreak) return;
var command = $"/forge {playerId} {GetString(role.ToString())}";
@@ -90,7 +90,7 @@ public class Loner : RoleBase
public static void ProcessGuesserUI(byte playerId, CustomRoles role)
{
PlayerControl pc = Utils.GetPlayerById(playerId);
if (!pc || !pc.IsAlive() || !pc.Is(CustomRoles.Loner) || Starspawn.IsDayBreak) return;
if (!pc || !pc.IsAlive() || !PlayerControl.LocalPlayer.Is(CustomRoles.Loner) || Starspawn.IsDayBreak) return;
var command = $"/select {playerId} {GetString(role.ToString())}";