Add Transposer

This commit is contained in:
Gurge44 committed 2026-08-13 13:49:47 +02:00
1 parent 01fa31824b
commit f100926055
9 files changed
+156 -14

No files matched your search

+1
View File
@@ -118,6 +118,7 @@ public enum CustomRoles
Swooper,
Stealth,
TimeThief,
Transposer,
Trapster,
Trickster,
Twister,
+4
View File
@@ -186,6 +186,7 @@ internal static class CustomRolesHelper
CustomRoles.Freezer => CustomRoles.Shapeshifter,
CustomRoles.Changeling => CustomRoles.Phantom,
CustomRoles.Swapster => CustomRoles.Shapeshifter,
CustomRoles.Transposer => CustomRoles.Shapeshifter,
CustomRoles.Kamikaze => CustomRoles.Impostor,
CustomRoles.Librarian => CustomRoles.Shapeshifter,
CustomRoles.Cantankerous => CustomRoles.Impostor,
@@ -802,6 +803,7 @@ internal static class CustomRolesHelper
CustomRoles.Freezer or
CustomRoles.Changeling or
CustomRoles.Swapster or
CustomRoles.Transposer or
CustomRoles.Loner or
CustomRoles.Kamikaze or
CustomRoles.Librarian or
@@ -1071,6 +1073,7 @@ internal static class CustomRolesHelper
CustomRoles.Glitch or
CustomRoles.Illusionist or
CustomRoles.Swapster or
CustomRoles.Transposer or
CustomRoles.Echo or
CustomRoles.Hangman or
CustomRoles.Generator;
@@ -1313,6 +1316,7 @@ internal static class CustomRolesHelper
CustomRoles.Swiftclaw => RoleOptionType.Impostor_Concealing,
CustomRoles.Swooper => RoleOptionType.Impostor_Concealing,
CustomRoles.Stealth => RoleOptionType.Impostor_Concealing,
CustomRoles.Transposer => RoleOptionType.Impostor_Concealing,
CustomRoles.Trickster => RoleOptionType.Impostor_Concealing,
CustomRoles.Undertaker => RoleOptionType.Impostor_Concealing,
CustomRoles.Vampire => RoleOptionType.Impostor_Concealing,
+1
View File
@@ -570,6 +570,7 @@ public static class HudSpritePatch
}
case CustomRoles.Transporter when player.GetRoleTypes() == RoleTypes.Shapeshifter:
case CustomRoles.Swapster:
case CustomRoles.Transposer:
{
newAbilityButton = CustomButton.Get("Transport");
break;
+1 -1
View File
@@ -931,7 +931,7 @@ internal static class ShapeshiftPatch
if (!shapeshifting && !shapeshifter.Is(CustomRoles.Glitch) && isSSneeded)
LateTask.New(() => Main.Instance.StartCoroutine(NotifyEveryoneAsync()), 1.2f, "ShapeShiftNotify");
if (!(shapeshifting && doSSwithoutAnim) && !isSSneeded && !Swapster.FirstSwapTarget.ContainsKey(shapeshifter.PlayerId) && !Transporter.FirstSwapTarget.ContainsKey(shapeshifter.PlayerId))
if (!(shapeshifting && doSSwithoutAnim) && !isSSneeded && !Swapster.FirstSwapTarget.ContainsKey(shapeshifter.PlayerId) && !Transporter.FirstSwapTarget.ContainsKey(shapeshifter.PlayerId) && !Transposer.FirstSwapTarget.ContainsKey(shapeshifter.PlayerId))
LateTask.New(shapeshifter.RpcResetAbilityCooldown, 0.01f, log: false);
if (!isSSneeded)
+3
View File
@@ -2283,6 +2283,9 @@
"TimeThiefDecreaseMeetingTime": "Lower Meeting Time by",
"TimeThiefLowerLimitVotingTime": "Minimum Voting Time",
"TimeThiefReturnStolenTimeUponDeath": "Return Stolen Time Upon Death",
"Transposer": "Transposer",
"TransposerInfo": "Swap Players' Appearance",
"TransposerInfoLong": "(Impostors):\\nAs the Transposer, you can select 2 players using your shapeshift menu to forcefully shapeshift them into each other for some time.",
"Trapster": "Trapster",
"TrapsterInfo": "Start a killing chain",
"TrapsterInfoLong": "(Impostors):\\nAs the Trapster, you can vanish/pet to create a fake dead body. Anyone who reports this body will die, and depending on settings, the body will be reported.\\n\\n(Legacy Version):\\nIn the game, the Trapster kills a player and then puts a trap on their body. If another player reports the dead body and it has the Trapster's trap on it, the player who reports it will die instantly without the body getting reported. The player killed by the Trapster's trap will also become a Trapster. This chain reaction continues until no more players are left to become Trapsters. However, the Trapster will die immediately after making a kill. It's worth noting that the Trapster's own dead body cannot trigger their trap.",
@@ -55,11 +55,7 @@ internal class Transporter : RoleBase
{
if (!shapeshifting) return true;
if (shapeshifter == null || target == null || shapeshifter == target || !shapeshifter.IsAlive() || !target.IsAlive())
return false;
if (shapeshifter.GetAbilityUseLimit() < 1f)
return false;
if (!target.IsAlive() || shapeshifter.GetAbilityUseLimit() < 1f) return false;
if (FirstSwapTarget.TryGetValue(shapeshifter.PlayerId, out byte firstTargetId))
{
@@ -71,7 +67,7 @@ internal class Transporter : RoleBase
PlayerControl firstTarget = firstTargetId.GetPlayer();
if (firstTarget == null || !firstTarget.IsAlive())
if (!firstTarget || !firstTarget.IsAlive())
{
FirstSwapTarget.Remove(shapeshifter.PlayerId);
shapeshifter.Notify(CustomRoles.Impostor.ColoredTextByRole(Translator.GetString("ErrorTeleport")));
@@ -1,7 +1,5 @@
using System;
using AmongUs.GameOptions;
using AmongUs.GameOptions;
using Hazel;
using InnerNet;
namespace EHR.Roles;
@@ -0,0 +1,134 @@
using System.Collections.Generic;
using AmongUs.GameOptions;
using Hazel;
namespace EHR.Roles;
public class Transposer : RoleBase
{
public static bool On;
public static Dictionary<byte, byte> FirstSwapTarget = [];
private static OptionItem AbilityCooldown;
private static OptionItem AbilityDuration;
private static OptionItem AbilityUseLimit;
private static OptionItem AbilityUseGainWithEachKill;
public override bool IsEnable => On;
public override void SetupCustomOption()
{
StartSetup(659600)
.AutoSetupOption(ref AbilityCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds)
.AutoSetupOption(ref AbilityDuration, 15f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds)
.AutoSetupOption(ref AbilityUseLimit, 1f, new FloatValueRule(0, 20, 0.05f), OptionFormat.Times)
.AutoSetupOption(ref AbilityUseGainWithEachKill, 0.5f, new FloatValueRule(0f, 5f, 0.1f), OptionFormat.Times);
}
public override void Init()
{
On = false;
FirstSwapTarget = [];
}
public override void Add(byte playerId)
{
On = true;
playerId.SetAbilityUseLimit(AbilityUseLimit.GetFloat());
}
public override void Remove(byte playerId)
{
FirstSwapTarget.Remove(playerId);
}
public override void ApplyGameOptions(IGameOptions opt, byte playerId)
{
AURoleOptions.ShapeshifterCooldown = AbilityCooldown.GetFloat();
AURoleOptions.ShapeshifterDuration = 1f;
}
public override bool OnShapeshift(PlayerControl transposer, PlayerControl target, bool shapeshifting)
{
if (!shapeshifting) return true;
if (FirstSwapTarget.TryGetValue(transposer.PlayerId, out byte firstTargetId))
{
PlayerControl firstTarget = Utils.GetPlayerById(firstTargetId);
if (firstTarget && firstTarget.IsAlive() && target.IsAlive())
{
transposer.RpcRemoveAbilityUse(notify: false);
CustomRpcSender sender = CustomRpcSender.Create("Transposer", SendOption.Reliable);
sender.StartMessage();
sender.StartRpc(target.NetId, RpcCalls.Shapeshift)
.WriteNetObject(firstTarget)
.Write(false)
.EndRpc();
sender.StartRpc(firstTarget.NetId, RpcCalls.Shapeshift)
.WriteNetObject(target)
.Write(false)
.EndRpc();
sender.SendMessage();
try
{
firstTarget.Shapeshift(target, false);
target.Shapeshift(firstTarget, false);
}
catch { }
LateTask.New(() =>
{
if (!GameStates.IsInTask || ExileController.Instance || AntiBlackout.SkipTasks || ReportDeadBodyPatch.MeetingStarted) return;
if (transposer && transposer.IsAlive())
transposer.RpcResetAbilityCooldown();
bool hasValue = false;
CustomRpcSender writer = CustomRpcSender.Create("Transposer Revert", SendOption.Reliable);
writer.StartMessage();
if (firstTarget && firstTarget.IsAlive() && firstTarget.IsShifted())
{
hasValue = true;
try { firstTarget.Shapeshift(firstTarget, false); } catch { }
writer.StartRpc(firstTarget.NetId, RpcCalls.Shapeshift)
.WriteNetObject(firstTarget)
.Write(false)
.EndRpc();
}
if (target && target.IsAlive() && target.IsShifted())
{
hasValue = true;
try { target.Shapeshift(target, false); } catch { }
writer.StartRpc(target.NetId, RpcCalls.Shapeshift)
.WriteNetObject(target)
.Write(false)
.EndRpc();
}
writer.SendMessage(dispose: !hasValue);
}, AbilityDuration.GetFloat(), "Transposer ability");
}
FirstSwapTarget.Remove(transposer.PlayerId);
}
else
{
if (transposer.GetAbilityUseLimit() < 1f) return false;
FirstSwapTarget[transposer.PlayerId] = target.PlayerId;
}
return false;
}
public override void SetButtonTexts(HudManager hud, byte id)
{
hud.AbilityButton?.OverrideText(Translator.GetString("BountyHunterChangeButtonText"));
}
}
+9 -4
View File
@@ -44,14 +44,19 @@ internal class Swapster : RoleBase
public override bool OnShapeshift(PlayerControl swapster, PlayerControl target, bool shapeshifting)
{
if (swapster == null || target == null || swapster == target || !shapeshifting) return true;
if (!shapeshifting) return true;
if (FirstSwapTarget.TryGetValue(swapster.PlayerId, out byte firstTargetId))
{
PlayerControl firstTarget = Utils.GetPlayerById(firstTargetId);
Vector2 pos = firstTarget.Pos();
firstTarget.TP(target);
target.TP(pos);
if (firstTarget && firstTarget.IsAlive())
{
Vector2 pos = firstTarget.Pos();
firstTarget.TP(target);
target.TP(pos);
}
FirstSwapTarget.Remove(swapster.PlayerId);
}
else