Skip to content

Commit 13d0201

Browse files
committed
Implement Overload fix, update README.md, and allow players to paste into chat
1 parent c329ec4 commit 13d0201

12 files changed

Lines changed: 242 additions & 46 deletions

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
using MiraAPI.Hud;
2+
using MiraAPI.Utilities.Assets;
3+
using NewMod.Roles.NeutralRoles;
4+
using UnityEngine;
5+
6+
namespace NewMod.Buttons.Overload
7+
{
8+
/// <summary>
9+
/// Defines a custom action button for the Overload role.
10+
/// This button mimics another role's ability by adopting its appearance and functionality.
11+
/// </summary>
12+
public class OverloadButton : CustomActionButton
13+
{
14+
/// <summary>
15+
/// The display text shown on the button UI.
16+
/// Set by the absorbed ability.
17+
/// </summary>
18+
public string absorbedText = "";
19+
20+
/// <summary>
21+
/// The sprite icon used on the button.
22+
/// Loaded from the absorbed ability.
23+
/// </summary>
24+
public LoadableAsset<Sprite> absorbedSprite;
25+
26+
/// <summary>
27+
/// The method invoked when the Overload button is clicked.
28+
/// Mirrors the absorbed button's behavior.
29+
/// </summary>
30+
public System.Action absorbedOnClick;
31+
32+
/// <summary>
33+
/// Maximum number of times the button can be used.
34+
/// Mirrors the absorbed button's uses.
35+
/// </summary>
36+
public int absorbedMaxUses;
37+
38+
/// <summary>
39+
/// Cooldown in seconds for reusing the button.
40+
/// Mirrors the absorbed button's cooldown.
41+
/// </summary>
42+
public float absorbedCooldown;
43+
44+
/// <summary>
45+
/// The name displayed on the button.
46+
/// </summary>
47+
public override string Name => absorbedText;
48+
49+
/// <summary>
50+
/// Cooldown duration before the button can be reused.
51+
/// </summary>
52+
public override float Cooldown => absorbedCooldown;
53+
54+
/// <summary>
55+
/// Number of remaining uses. Zero means unlimited.
56+
/// </summary>
57+
public override int MaxUses => absorbedMaxUses;
58+
59+
/// <summary>
60+
/// Determines how long the effect from clicking the button lasts. In this case, no duration is set.
61+
/// </summary>
62+
public override float EffectDuration => 0f;
63+
64+
/// <summary>
65+
/// The UI position for the button.
66+
/// </summary>
67+
public override ButtonLocation Location => ButtonLocation.BottomRight;
68+
69+
/// <summary>
70+
/// The icon displayed on the button.
71+
/// </summary>
72+
public override LoadableAsset<Sprite> Sprite => absorbedSprite;
73+
74+
/// <summary>
75+
/// Copies functionality and appearance from another role's button.
76+
/// </summary>
77+
/// <param name="target">The button to absorb.</param>
78+
public void Absorb(CustomActionButton target)
79+
{
80+
absorbedText = target.Name;
81+
absorbedCooldown = target.Cooldown;
82+
absorbedMaxUses = target.MaxUses;
83+
absorbedSprite = target.Sprite;
84+
absorbedOnClick = () => target.GetType().GetMethod("OnClick", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
85+
?.Invoke(target, null);
86+
87+
OverrideName(absorbedText);
88+
OverrideSprite(absorbedSprite.LoadAsset());
89+
SetUses(absorbedMaxUses);
90+
SetTimer(0f);
91+
}
92+
93+
/// <summary>
94+
/// Called when the player presses the button.
95+
/// </summary>
96+
protected override void OnClick()
97+
{
98+
absorbedOnClick?.Invoke();
99+
100+
}
101+
102+
/// <summary>
103+
/// Determines whether this button should be active on the HUD.
104+
/// Only visible when the role is Overload and an ability has been absorbed.
105+
/// </summary>
106+
/// <param name="role">The role of the player.</param>
107+
/// <returns>True if Overload with a valid absorbed ability.</returns>
108+
public override bool Enabled(RoleBehaviour role)
109+
{
110+
return role is OverloadRole && absorbedOnClick != null;
111+
}
112+
113+
/// <summary>
114+
/// Determines if the button can currently be pressed.
115+
/// </summary>
116+
/// <returns>True if usable.</returns>
117+
public override bool CanUse()
118+
{
119+
return base.CanUse() && absorbedOnClick != null;
120+
}
121+
}
122+
}

NewMod/DebugWindow.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using UnityEngine;
2020
using UnityEngine.Events;
2121
using Il2CppInterop.Runtime.Attributes;
22+
using NewMod.Buttons.Overload;
2223

2324
namespace NewMod
2425
{
@@ -123,9 +124,20 @@ public class DebugWindow(nint ptr) : MonoBehaviour(ptr)
123124
p.PlayerId != PlayerControl.LocalPlayer.PlayerId);
124125
if (prey != null)
125126
{
126-
if (prey.Data.Role is ICustomRole customRole)
127+
if (prey.Data.Role is ICustomRole customRole && Utils.RoleToButtonsMap.TryGetValue(customRole.GetType(), out var buttonsType))
127128
{
128-
Debug.Log("[Overload] Absorbing ability from custom role...");
129+
Debug.Log("Starting to absorb ability...");
130+
131+
foreach (var buttonType in buttonsType)
132+
{
133+
var button = CustomButtonManager.Buttons.FirstOrDefault(b => b.GetType() == buttonType);
134+
135+
if (button != null)
136+
{
137+
CustomButtonSingleton<OverloadButton>.Instance.Absorb(button);
138+
}
139+
Debug.Log($"[Overload] Successfully absorbed ability: {button.Name}");
140+
}
129141
}
130142
else if (prey.Data.Role.Ability != null)
131143
{

NewMod/Modifiers/FalseFormModifier.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@ namespace NewMod.Modifiers
99
public class FalseFormModifier : TimedModifier
1010
{
1111
public override string ModifierName => "FalseForm";
12-
public override bool AutoStart =>
13-
OptionGroupSingleton<FalseFormModifierOptions>.Instance.EnableModifier;
14-
public override float Duration =>
15-
(int)OptionGroupSingleton<FalseFormModifierOptions>.Instance.FalseFormDuration;
12+
public override bool AutoStart => OptionGroupSingleton<FalseFormModifierOptions>.Instance.EnableModifier;
13+
public override float Duration => (int)OptionGroupSingleton<FalseFormModifierOptions>.Instance.FalseFormDuration;
1614
public override bool ShowInFreeplay => true;
1715
public override bool HideOnUi => false;
1816
public override bool RemoveOnComplete => true;

NewMod/Modifiers/StickyModifier.cs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,23 @@ namespace NewMod.Modifiers
1111
public class StickyModifier : TimedModifier
1212
{
1313
public override string ModifierName => "Sticky";
14-
15-
public override bool AutoStart =>
16-
OptionGroupSingleton<StickyModifierOptions>.Instance.EnableModifier;
17-
18-
public override float Duration =>
19-
(int)OptionGroupSingleton<StickyModifierOptions>.Instance.StickyDuration;
20-
14+
public override bool AutoStart => OptionGroupSingleton<StickyModifierOptions>.Instance.EnableModifier;
15+
public override float Duration => (int)OptionGroupSingleton<StickyModifierOptions>.Instance.StickyDuration;
2116
public override bool HideOnUi => false;
2217
public override bool ShowInFreeplay => true;
2318
public override bool RemoveOnComplete => true;
24-
2519
public static List<PlayerControl> linkedPlayers = new();
26-
2720
public override bool? CanVent()
2821
{
2922
return Player.Data.Role.CanVent;
3023
}
31-
3224
public override string GetDescription()
3325
{
3426
float distance = OptionGroupSingleton<StickyModifierOptions>.Instance.StickyDistance.Value;
3527
float duration = OptionGroupSingleton<StickyModifierOptions>.Instance.StickyDuration.Value;
3628

3729
return $"{ModifierName}: Pulls nearby players within {distance} units for {duration} seconds.";
3830
}
39-
4031
public override void FixedUpdate()
4132
{
4233
base.FixedUpdate();
@@ -56,10 +47,9 @@ public override void FixedUpdate()
5647
}
5748
}
5849
}
59-
6050
public IEnumerator CoFollowStickyPlayer(PlayerControl player)
6151
{
62-
float duration = 5f;
52+
float duration = Duration;
6353

6454
var info = new StickyState
6555
{

NewMod/NewMod.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
using NewMod.Options.Roles.OverloadOptions;
2525
using MiraAPI.Events;
2626
using NewMod.Patches.Compatibility;
27+
using NewMod.Buttons.Overload;
2728

2829
namespace NewMod;
2930

@@ -119,9 +120,17 @@ public static void OnAfterMurder(AfterMurderEvent evt)
119120

120121
foreach (var pc in PlayerControl.AllPlayerControls.ToArray().Where(p => p.AmOwner && p.Data.Role is OverloadRole))
121122
{
122-
if (target.Data.Role is ICustomRole customRole)
123+
if (target.Data.Role is ICustomRole customRole && Utils.RoleToButtonsMap.TryGetValue(customRole.GetType(), out var buttonsType))
123124
{
124-
// TODO: Awaiting appropriate event in MiraAPI to implement this functionality.
125+
foreach (var buttonType in buttonsType)
126+
{
127+
var button = CustomButtonManager.Buttons.FirstOrDefault(b => b.GetType() == buttonType);
128+
129+
if (button != null)
130+
{
131+
CustomButtonSingleton<OverloadButton>.Instance.Absorb(button);
132+
}
133+
}
125134
}
126135
else if (target.Data.Role is not ICustomRole)
127136
{

NewMod/NewMod.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
<ItemGroup>
2323
<PackageReference Include="Reactor" Version="2.3.1" />
24-
<PackageReference Include="AllOfUs.MiraAPI" Version="0.2.0-ci.558" />
24+
<PackageReference Include="AllOfUs.MiraAPI" Version="0.2.0-ci.571"/>
2525
<PackageReference Condition="'$(Configuration)' == 'Debug' Or '$(Configuration)' == 'Release' " Include="AmongUs.GameLibs.Steam" Version="2025.4.15" PrivateAssets="all" />
2626
<PackageReference Condition="'$(Configuration)' == 'ANDROID' " Include="AmongUs.GameLibs.Android" Version="2024.10.29" PrivateAssets="all" />
2727
<PackageReference Include="BepInEx.AutoPlugin" Version="1.1.0" PrivateAssets="all" />

NewMod/Patches/ClipboardPatch.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using HarmonyLib;
2+
using UnityEngine;
3+
4+
namespace NewMod.Patches
5+
{
6+
/// <summary>
7+
/// Allows players to paste clipboard text into chat using Ctrl+V.
8+
/// Inspired by:https://github.com/nyomo/TownOfPlus/blob/origin/TownOfPlus/Patches/ChatPlus.cs#L80-L158
9+
/// </summary>
10+
[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))]
11+
public static class ClipboardPatch
12+
{
13+
public static void Prefix(ChatController __instance)
14+
{
15+
if (!HudManager.Instance.Chat.IsOpenOrOpening) return;
16+
17+
bool ctrlPressed = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
18+
19+
if (ctrlPressed && Input.GetKeyDown(KeyCode.V))
20+
{
21+
string clipboard = GUIUtility.systemCopyBuffer;
22+
23+
if (!string.IsNullOrWhiteSpace(clipboard))
24+
{
25+
clipboard = clipboard.Replace("<", "")
26+
.Replace(">", "")
27+
.Replace("\r", "");
28+
29+
if (!Input.GetKey(KeyCode.LeftShift) && !Input.GetKey(KeyCode.RightShift))
30+
clipboard = clipboard.Replace("\n", "");
31+
32+
__instance.freeChatField.textArea.SetText(__instance.freeChatField.textArea.text + clipboard);
33+
}
34+
}
35+
}
36+
}
37+
}

NewMod/Roles/ImpostorRoles/Necromancer.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,9 @@ public class NecromancerRole : ImpostorRole, ICustomRole
2222
OptionsScreenshot = NewModAsset.Banner,
2323
MaxRoleCount = 3,
2424
};
25+
public TeamIntroConfiguration TeamConfiguration => new()
26+
{
27+
IntroTeamDescription = RoleDescription,
28+
IntroTeamColor = RoleColor
29+
};
2530
}

NewMod/Roles/NeutralRoles/Egoist.cs

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,20 @@ public class EgoistRole : CrewmateRole, ICustomRole
2121
public ModdedRoleTeams Team => ModdedRoleTeams.Custom;
2222
public RoleOptionsGroup RoleOptionsGroup => RoleOptionsGroup.Neutral;
2323

24-
public CustomRoleConfiguration Configuration =>
25-
new(this)
26-
{
27-
AffectedByLightOnAirship = false,
28-
CanGetKilled = true,
29-
UseVanillaKillButton = false,
30-
CanUseVent = false,
31-
CanUseSabotage = false,
32-
TasksCountForProgress = false,
33-
ShowInFreeplay = true,
34-
HideSettings = false,
35-
MaxRoleCount = 1,
36-
OptionsScreenshot = null,
37-
Icon = null,
38-
};
24+
public CustomRoleConfiguration Configuration => new(this)
25+
{
26+
AffectedByLightOnAirship = false,
27+
CanGetKilled = true,
28+
UseVanillaKillButton = false,
29+
CanUseVent = false,
30+
CanUseSabotage = false,
31+
TasksCountForProgress = false,
32+
ShowInFreeplay = true,
33+
HideSettings = false,
34+
MaxRoleCount = 1,
35+
OptionsScreenshot = null,
36+
Icon = null,
37+
};
3938

4039
[RegisterEvent]
4140
public static void OnEjection(EjectionEvent evt)

NewMod/Roles/NeutralRoles/Overload.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ public static IEnumerator CoShowMenu(float delay)
6767
public static void UnlockFinalAbility()
6868
{
6969
var btn = Instantiate(HudManager.Instance.AbilityButton, HudManager.Instance.AbilityButton.transform.parent);
70+
btn.name = "FinalButton";
71+
7072
var rect = btn.GetComponent<RectTransform>();
71-
rect.SetParent(HudManager.Instance.transform, false);
7273
rect.anchorMin = new(0.5f, 0.5f);
7374
rect.anchorMax = new(0.5f, 0.5f);
7475
rect.pivot = new(0.5f, 0.5f);

0 commit comments

Comments
 (0)