Compare commits

..
Author SHA1 Message Date
ang-xd 878eab25b8 Merge branch 'dev' into 2025.3.25 2025-04-16 20:11:51 -04:00
angxl wtf e4f15f2ed9 Merge pull request #33 from All-Of-Us-Mods/feat-addressables-2
Addressables Loading
2025-04-16 19:51:27 -04:00
ang-xd bdc9fa28ac oops 2025-04-15 18:20:24 -04:00
ang-xd 64604aff98 Fix bug if you change hideonui 2025-04-15 18:05:20 -04:00
ang-xd 3ba71d8f19 Add pagination 2025-04-12 16:22:55 -04:00
ang-xd e5b6400eb8 Fix CastVote event issue 2025-04-06 14:50:29 -04:00
XtraCube 0d1d1891a8 Merge branch 'dev' into 2025.3.25 2025-04-05 16:30:17 -04:00
XtraCube a3fc847d07 Merge branch 'dev' into 2025.3.25 2025-04-05 14:32:26 -04:00
XtraCube 31ffe6274c fix silly mistake with settings 2025-04-05 14:19:52 -04:00
XtraCube f0e37daf0e Merge branch 'dev' into 2025.3.25 2025-04-01 12:25:59 -04:00
XtraCube be46ee7329 fix custom murder stats 2025-03-25 17:11:51 -04:00
XtraCube 75a15dc090 fix minigame patch 2025-03-25 17:09:16 -04:00
XtraCube cffb104392 update libs 2025-03-25 17:06:02 -04:00
13 changed files with 214 additions and 117 deletions

No files matched your search

+2 -2
View File
@@ -2,10 +2,10 @@
<ItemGroup>
<PackageReference Include="Reactor" Version="2.3.1" />
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.697" Private="false" ExcludeAssets="runtime;native" />
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2024.10.29" PrivateAssets="all" />
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2025.3.25" PrivateAssets="all" />
<PackageReference Include="BepInEx.AutoPlugin" Version="1.1.0" PrivateAssets="all" />
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.0.1" PrivateAssets="all" ExcludeAssets="runtime" />
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.1.0-rc.1" PrivateAssets="all" ExcludeAssets="runtime" />
</ItemGroup>
<Target Name="Copy" AfterTargets="Build" Condition="'$(AmongUs)' != ''">
+1 -1
View File
@@ -34,7 +34,7 @@
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" PrivateAssets="all" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)\stylecop.json" Link="stylecop.json" Visible="false" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.32.0.97167">
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.7.0.110445">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
@@ -24,6 +24,11 @@ public class HandleVoteEvent : MiraCancelableEvent
/// </summary>
public byte TargetId { get; }
/// <summary>
/// Gets a value indicating whether to prevent the vote from commencing.
/// </summary>
public bool PreventVote { get; }
/// <summary>
/// Gets the player info of the target.
/// </summary>
@@ -39,6 +44,7 @@ public class HandleVoteEvent : MiraCancelableEvent
VoteData = playerVoteData;
Player = playerVoteData.Owner;
TargetId = targetId;
PreventVote = false;
TargetPlayerInfo = GameData.Instance.GetPlayerById(targetId);
}
}
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using HarmonyLib;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities.Attributes;
@@ -23,11 +24,19 @@ public class ModifierDisplayComponent(nint ptr) : MonoBehaviour(ptr)
/// </summary>
public static ModifierDisplayComponent? Instance { get; private set; }
private GameObject _children = null!;
private RectTransform _children = null!;
private GameObject _modTemplate = null!;
private PassiveButton _toggleButton = null!;
private TextMeshPro _toggleBtnText = null!;
private RectTransform _pagination = null!;
private TextMeshPro _pageText = null!;
private PassiveButton _nextButton = null!;
private PassiveButton _backButton = null!;
private int _currentPage = 0;
private const int _itemsPerPage = 3;
private readonly Dictionary<BaseModifier, ModifierUiComponent> _modifiers = [];
/// <summary>
@@ -45,7 +54,7 @@ public class ModifierDisplayComponent(nint ptr) : MonoBehaviour(ptr)
{
Instance = this;
_children = transform.GetChild(0).gameObject;
_children = transform.GetChild(0).GetComponent<RectTransform>();
_modTemplate = transform.GetChild(2).gameObject;
_toggleButton = transform.GetChild(1).GetComponent<PassiveButton>();
@@ -60,13 +69,49 @@ public class ModifierDisplayComponent(nint ptr) : MonoBehaviour(ptr)
_toggleButton.HoverSound = HudManager.Instance.GameMenu.StreamerModeButton.GetComponent<PassiveButton>().HoverSound;
_toggleButton.inactiveSprites = _toggleButton.transform.GetChild(1).gameObject;
_toggleBtnText = _toggleButton.transform.GetChild(0).GetComponent<TextMeshPro>();
_toggleBtnText.font = HudManager.Instance.TaskPanel.taskText.font;
_toggleBtnText.fontMaterial = HudManager.Instance.TaskPanel.taskText.fontMaterial;
_pagination = _children.transform.GetChild(0).GetComponent<RectTransform>();
_pageText = _pagination.transform.GetChild(0).GetComponent<TextMeshPro>();
_nextButton = _pagination.transform.GetChild(1).GetComponent<PassiveButton>();
_backButton = _pagination.transform.GetChild(2).GetComponent<PassiveButton>();
_toggleBtnText.font = _pageText.font = _nextButton.buttonText.font = _backButton.buttonText.font = HudManager.Instance.TaskPanel.taskText.font;
_toggleBtnText.fontMaterial = _pageText.fontMaterial = _nextButton.buttonText.fontMaterial = _backButton.buttonText.fontMaterial = HudManager.Instance.TaskPanel.taskText.fontMaterial;
_nextButton.OnClick = new Button.ButtonClickedEvent();
_nextButton.OnClick.AddListener((UnityAction)(() =>
{
_currentPage++;
UpdatePage();
}));
_backButton.OnClick = new Button.ButtonClickedEvent();
_backButton.OnClick.AddListener((UnityAction)(() =>
{
_currentPage--;
UpdatePage();
}));
IsOpen = false;
_children.gameObject.SetActive(false);
}
/// <summary>
/// Use this to refresh the modifiers UI. Useful if you hide modifiers uis based on options.
/// </summary>
public void RefreshModifiers()
{
UpdateModifiersList([.. PlayerControl.LocalPlayer.GetModifierComponent().ActiveModifiers]);
}
internal void DestroyComponent(ModifierUiComponent component)
{
_modifiers.Remove(component.Modifier!);
component.gameObject.DestroyImmediate();
RefreshModifiers();
}
[HideFromIl2Cpp]
internal static ModifierDisplayComponent CreateDisplay()
{
@@ -82,20 +127,54 @@ public class ModifierDisplayComponent(nint ptr) : MonoBehaviour(ptr)
newMod.transform.name = modifier.ModifierName;
var comp = newMod.AddComponent<ModifierUiComponent>();
comp.Modifier = modifier;
newMod.gameObject.SetActive(true);
return comp;
}
private void UpdatePage()
{
var totalPages = Mathf.CeilToInt((float)_modifiers.Count / _itemsPerPage);
_currentPage = Mathf.Clamp(_currentPage, 0, Mathf.Max(0, totalPages - 1));
if (_modifiers.Count <= _itemsPerPage)
{
_modifiers.Do(x => x.Value.gameObject.SetActive(true));
_pagination.gameObject.SetActive(false);
return;
}
_pagination.gameObject.SetActive(true);
if (_pageText != null)
{
_pageText.text = $"Page {_currentPage + 1}/{Mathf.Max(totalPages, 1)}";
}
_nextButton.gameObject.SetActive(true);
_backButton.gameObject.SetActive(true);
_modifiers.Do(x => x.Value.gameObject.SetActive(false));
var start = _currentPage * _itemsPerPage;
var end = Mathf.Min(start + _itemsPerPage, _modifiers.Count);
var modifiers = _modifiers.ToArray();
for (var i = start; i < end; i++)
{
modifiers[i].Value.gameObject.SetActive(true);
}
_pagination.SetAsLastSibling();
}
[HideFromIl2Cpp]
internal void UpdateModifiersList(List<BaseModifier> modifiers)
{
var filteredModifiers = modifiers.Where(x => !x.HideOnUi && x.GetDescription() != string.Empty).ToList();
var modifiersToAdd = filteredModifiers.ToList();
foreach (var mod in _modifiers.ToArray())
{
if (filteredModifiers.Contains(mod.Key))
if (modifiersToAdd.Contains(mod.Key))
{
filteredModifiers.Remove(mod.Key);
modifiersToAdd.Remove(mod.Key);
continue;
}
@@ -103,9 +182,9 @@ public class ModifierDisplayComponent(nint ptr) : MonoBehaviour(ptr)
_modifiers.Remove(mod.Key);
}
_toggleBtnText.text = $"Modifiers ({modifiers.Count})";
_toggleBtnText.text = $"Modifiers ({filteredModifiers.Count})";
if (modifiers.Count == 0)
if (filteredModifiers.Count == 0)
{
_toggleButton.gameObject.SetActive(false);
return;
@@ -113,9 +192,11 @@ public class ModifierDisplayComponent(nint ptr) : MonoBehaviour(ptr)
_toggleButton.gameObject.SetActive(true);
foreach (var mod in filteredModifiers)
foreach (var mod in modifiersToAdd)
{
_modifiers.Add(mod, CreateForModifier(mod));
}
UpdatePage();
}
}
@@ -43,6 +43,12 @@ public class ModifierUiComponent(nint ptr) : MonoBehaviour(ptr)
{
if (Modifier == null || ModifierDisplayComponent.Instance == null || !ModifierDisplayComponent.Instance.IsOpen) return;
if (Modifier.HideOnUi || Modifier.GetDescription() == string.Empty)
{
ModifierDisplayComponent.Instance.DestroyComponent(this);
return;
}
nameText.text = Modifier.ModifierName;
desc.text = Modifier.GetDescription();
modBg.gameObject.SetActive(true);
+7 -6
View File
@@ -1,5 +1,6 @@
using System.Collections;
using System.Linq;
using AmongUs.Data;
using AmongUs.GameOptions;
using Assets.CoreScripts;
using BepInEx.Unity.IL2CPP.Utils;
@@ -96,7 +97,7 @@ public static class CustomMurderRpc
var flag = PlayerControl.LocalPlayer.Data.Role.Role == RoleTypes.GuardianAngel;
if (flag && PlayerControl.LocalPlayer.Data.PlayerId == target.protectedByGuardianId)
{
StatsManager.Instance.IncrementStat(StringNames.StatsGuardianAngelCrewmatesProtected);
DataManager.Player.Stats.IncrementStat(StatID.Role_GuardianAngel_CrewmatesProtected);
AchievementManager.Instance.OnProtectACrewmate();
}
@@ -127,14 +128,14 @@ public static class CustomMurderRpc
DebugAnalytics.Instance.Analytics.Kill(target.Data, source.Data);
if (source.AmOwner)
{
StatsManager.Instance.IncrementStat(
DataManager.Player.Stats.IncrementStat(
GameManager.Instance.IsHideAndSeek()
? StringNames.StatsImpostorKills_HideAndSeek
: StringNames.StatsImpostorKills);
? StatID.HideAndSeek_ImpostorKills
: StatID.ImpostorKills);
if (source.CurrentOutfitType == PlayerOutfitType.Shapeshifted)
{
StatsManager.Instance.IncrementStat(StringNames.StatsShapeshifterShiftedKills);
DataManager.Player.Stats.IncrementStat(StatID.Role_Shapeshifter_ShiftedKills);
}
if (Constants.ShouldPlaySfx() && playKillSound)
@@ -152,7 +153,7 @@ public static class CustomMurderRpc
target.gameObject.layer = LayerMask.NameToLayer("Ghost");
if (target.AmOwner)
{
StatsManager.Instance.IncrementStat(StringNames.StatsTimesMurdered);
DataManager.Player.Stats.IncrementStat(StatID.TimesMurdered);
if (Minigame.Instance)
{
try
+5
View File
@@ -44,4 +44,9 @@ public enum MiraRpc : uint
/// Custom RPC to undo a player's vote.
/// </summary>
RemoveVote,
/// <summary>
/// Custom RPC to replace Among Us CastVote
/// </summary>
CastVote,
}
@@ -0,0 +1,12 @@
using HarmonyLib;
namespace MiraAPI.Patches;
[HarmonyPatch(typeof(Minigame), nameof(Minigame.Begin))]
internal static class MinigameBeginErrorPatch
{
public static void Prefix(Minigame __instance)
{
__instance.logger ??= new Logger(Logger.Category.Gameplay, "Minigame");
}
}
@@ -8,7 +8,7 @@ namespace MiraAPI.Patches.Roles;
/// <summary>
/// Patches to return the correct role counts.
/// </summary>
[HarmonyPatch(typeof(RoleOptionsCollectionV08))]
[HarmonyPatch(typeof(RoleOptionsCollectionV09))]
public static class RoleOptionsCollectionPatch
{
/// <summary>
@@ -16,7 +16,7 @@ public static class RoleOptionsCollectionPatch
/// </summary>
/// <returns>Return false to skip original method, true to not.</returns>
[HarmonyPrefix]
[HarmonyPatch(nameof(RoleOptionsCollectionV08.GetChancePerGame))]
[HarmonyPatch(nameof(RoleOptionsCollectionV09.GetChancePerGame))]
public static bool GetChancePrefix(RoleTypes role, ref int __result)
{
if (!CustomRoleManager.GetCustomRoleBehaviour(role, out var customRole) || customRole == null)
@@ -46,7 +46,7 @@ public static class RoleOptionsCollectionPatch
/// </summary>
/// <returns>Return false to skip original method, true to not.</returns>
[HarmonyPrefix]
[HarmonyPatch(nameof(RoleOptionsCollectionV08.GetNumPerGame))]
[HarmonyPatch(nameof(RoleOptionsCollectionV09.GetNumPerGame))]
public static bool GetNumPrefix(RoleTypes role, ref int __result)
{
if (!CustomRoleManager.GetCustomRoleBehaviour(role, out var customRole) || customRole == null)
-11
View File
@@ -33,15 +33,4 @@ public static class MinigameStubs
{
// nothing needed
}
/// <summary>
/// Fixes the error when creating custom minigames. Credit to Submerged for discovering this fix.
/// </summary>
/// <param name="__instance">The Minigame instance.</param>
[HarmonyPrefix]
[HarmonyPatch(typeof(Minigame), nameof(Minigame.Begin))]
public static void MinigameErrorFixPatch(Minigame __instance)
{
__instance.logger ??= new Logger("Minigame", Logger.Level.Info, Logger.Category.Gameplay);
}
}
+10 -79
View File
@@ -8,6 +8,7 @@ using MiraAPI.Events.Vanilla.Meeting.Voting;
using MiraAPI.Modifiers;
using MiraAPI.Utilities;
using MiraAPI.Voting;
using Reactor.Utilities;
namespace MiraAPI.Patches.Voting;
@@ -140,84 +141,6 @@ internal static class MeetingHudPatches
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(MeetingHud.CastVote))]
public static bool CastVotePatch(MeetingHud __instance, byte srcPlayerId, byte suspectPlayerId)
{
var plr = GameData.Instance.GetPlayerById(srcPlayerId);
if (plr == null)
{
return false;
}
var voteData = plr.Object.GetVoteData();
if (voteData == null || voteData.VotesRemaining == 0 ||
voteData.VotedFor(suspectPlayerId))
{
return false;
}
VotingUtils.HandleVote(voteData, suspectPlayerId);
__instance.SetDirtyBit(1U);
__instance.CheckForEndVoting();
if (voteData.VotesRemaining != 0)
{
return false;
}
__instance.playerStates.First(x => x.TargetPlayerId == srcPlayerId).SetVote(suspectPlayerId);
PlayerControl.LocalPlayer.RpcSendChatNote(srcPlayerId, ChatNoteTypes.DidVote);
return false;
}
[HarmonyPostfix]
[HarmonyPatch(nameof(MeetingHud.CmdCastVote))]
public static void CmdCastVotePatch(MeetingHud __instance, byte playerId, byte suspectIdx)
{
var plr = GameData.Instance.GetPlayerById(playerId);
var voteData = plr.Object.GetVoteData();
if (!AmongUsClient.Instance.AmHost)
{
if (voteData == null || voteData.VotesRemaining == 0 ||
voteData.VotedFor(suspectIdx))
{
return;
}
VotingUtils.HandleVote(voteData, suspectIdx);
if (voteData.VotesRemaining == 0)
{
__instance.playerStates.First(x => x.TargetPlayerId == playerId).SetVote(suspectIdx);
}
}
if (PlayerControl.LocalPlayer.PlayerId != playerId)
{
return;
}
SoundManager.Instance.PlaySound(__instance.VoteLockinSound, false);
foreach (var playerVoteArea in __instance.playerStates)
{
playerVoteArea.ClearButtons();
}
__instance.SkipVoteButton.ClearButtons();
var localVoteData = PlayerControl.LocalPlayer.GetVoteData();
if (localVoteData == null || localVoteData.VotesRemaining != 0) return;
__instance.SkipVoteButton.voteComplete = true;
__instance.SkipVoteButton.gameObject.SetActive(false);
}
[HarmonyPrefix]
[HarmonyPatch(nameof(MeetingHud.CheckForEndVoting))]
public static bool EndCheck(MeetingHud __instance)
@@ -280,12 +203,20 @@ internal static class MeetingHudPatches
return false;
}
// TODO: figure out a way to do host-authorization since right now any player can send RpcCastVote
[HarmonyPrefix]
[HarmonyPatch(nameof(MeetingHud.CmdCastVote))]
public static bool CmdCastVoteOverridePatch(MeetingHud __instance, byte playerId, byte suspectIdx)
{
VotingUtils.RpcCastVote(PlayerControl.LocalPlayer, playerId, suspectIdx);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(MeetingHud.Confirm))]
public static bool ConfirmPatch(MeetingHud __instance, [HarmonyArgument(0)] byte suspect)
{
__instance.CmdCastVote(PlayerControl.LocalPlayer.PlayerId, suspect);
return false;
}
}
Binary file not shown.
+71 -5
View File
@@ -41,16 +41,26 @@ public static class VotingUtils
/// </summary>
/// <param name="voteData">The player's vote data.</param>
/// <param name="suspectIdx">Who the player voted for.</param>
public static void HandleVote(PlayerVoteData voteData, byte suspectIdx)
/// <param name="cancelVote">Whether you want the vote to commence or not.</param>
private static void HandleVote(PlayerVoteData voteData, byte suspectIdx, out bool cancelVote)
{
var @event = new HandleVoteEvent(voteData, suspectIdx);
MiraEventManager.InvokeEvent(@event);
cancelVote = @event.PreventVote;
if (@event.IsCancelled)
{
return;
}
if (voteData.VotesRemaining == 0 ||
voteData.VotedFor(suspectIdx))
{
cancelVote = true;
return;
}
voteData.DecreaseRemainingVotes(1);
voteData.VoteForPlayer(suspectIdx);
}
@@ -64,10 +74,7 @@ public static class VotingUtils
[MethodRpc((uint)MiraRpc.RemoveVote)]
public static void RpcRemoveVote(PlayerControl source, byte voterId, byte votedFor)
{
if (source.OwnerId != AmongUsClient.Instance.HostId)
{
return;
}
if (!source.IsHost()) return;
MeetingHud.Instance.playerStates.First(state => state.TargetPlayerId == voterId).UnsetVote();
@@ -94,6 +101,65 @@ public static class VotingUtils
MeetingHud.Instance.SkipVoteButton.gameObject.SetActive(true);
}
/// <summary>
/// Networks the casting of a vote. We replace the vanilla solution with a custom version that works for the use case.
/// </summary>
/// <param name="source">The player who sent this RPC.</param>
/// <param name="srcPlayerId">The id of the player who casted the vote.</param>
/// <param name="suspectPlayerId">The voted player's id.</param>
[MethodRpc((uint)MiraRpc.CastVote)]
public static void RpcCastVote(PlayerControl source, byte srcPlayerId, byte suspectPlayerId)
{
CustomCastVote(srcPlayerId, suspectPlayerId);
}
private static void CustomCastVote(byte srcPlayerId, byte suspectPlayerId)
{
var plr = GameData.Instance.GetPlayerById(srcPlayerId);
if (!plr) return;
var voteData = plr.Object.GetVoteData();
if (!voteData) return;
HandleVote(voteData, suspectPlayerId, out var cancelVote);
// Handle local behaviour for the voter (for some reason checking AmOwner does not work for host)
if (PlayerControl.LocalPlayer.PlayerId == srcPlayerId)
{
if (!cancelVote) SoundManager.Instance.PlaySound(MeetingHud.Instance.VoteLockinSound, false);
foreach (var playerVoteArea in MeetingHud.Instance.playerStates)
{
playerVoteArea.ClearButtons();
}
MeetingHud.Instance.SkipVoteButton.ClearButtons();
var localVoteData = PlayerControl.LocalPlayer.GetVoteData();
if (!localVoteData || localVoteData.VotesRemaining != 0) return;
MeetingHud.Instance.SkipVoteButton.voteComplete = true;
MeetingHud.Instance.SkipVoteButton.gameObject.SetActive(false);
}
if (cancelVote) return;
// If player has no more votes, then make it show that the player has used all their votes.
if (voteData.VotesRemaining == 0)
{
MeetingHud.Instance.playerStates.First(x => x.TargetPlayerId == srcPlayerId).SetVote(suspectPlayerId);
}
// If host, then check end voting, and set votes/send chat if applicable
if (!PlayerControl.LocalPlayer.IsHost()) return;
MeetingHud.Instance.SetDirtyBit(1U);
MeetingHud.Instance.CheckForEndVoting();
if (voteData.VotesRemaining != 0) return;
PlayerControl.LocalPlayer.RpcSendChatNote(srcPlayerId, ChatNoteTypes.DidVote);
}
/// <summary>
/// Calculates the total number of votes.
/// </summary>