Reduce allocations, CNO pooling, code improvements

This commit is contained in:
Gurge44 committed 2026-05-12 14:33:58 +02:00
1 parent c0ce71417a
commit f2ac622318
47 files changed
+539 -440

No files matched your search

+1 -1
View File
@@ -234,7 +234,6 @@ public enum CustomRoles
Medic,
Medium,
Merchant,
Telecommunication,
Mole,
Monarch,
Mortician,
@@ -277,6 +276,7 @@ public enum CustomRoles
Swapper,
Tar,
TaskManager,
Telecommunication,
Telekinetic,
Tether,
TimeManager,
+11 -10
View File
@@ -552,23 +552,24 @@ internal static class CustomHnS
try
{
var validIds = new HashSet<byte>();
foreach (var pc in Main.EnumeratePlayerControls())
validIds.Add(pc.PlayerId);
var toRemove = new List<byte>();
List<byte> toRemove = null;
foreach (var id in PlayerRoles.Keys)
{
if (!validIds.Contains(id))
if (!id.GetPlayer())
{
toRemove ??= [];
toRemove.Add(id);
}
}
foreach (var id in toRemove)
if (toRemove != null)
{
PlayerRoles.Remove(id);
Utils.SendRPC(CustomRPC.HNSSync, 2, id);
foreach (var id in toRemove)
{
PlayerRoles.Remove(id);
Utils.SendRPC(CustomRPC.HNSSync, 2, id);
}
}
}
catch { }
+7 -3
View File
@@ -109,8 +109,7 @@ internal static class HotPotato
switch (reader.ReadPackedInt32())
{
case 1:
int timeLeft = reader.ReadPackedInt32();
HotPotatoState.TimeLeft = timeLeft;
HotPotatoState.TimeLeft = reader.ReadPackedInt32();
break;
case 2:
HotPotatoState.HolderID = reader.ReadByte();
@@ -119,6 +118,11 @@ internal static class HotPotato
}
}
public static void RecordDeath(byte id, int time)
{
SurvivalTimes[id] = time;
}
public static int GetKillInterval()
{
return Time.GetInt() + Main.CurrentMap switch
@@ -159,7 +163,7 @@ internal static class HotPotato
if (HotPotatoState.TimeLeft <= 0)
{
holder.Suicide();
SurvivalTimes[HotPotatoState.HolderID] = (HotPotatoState.RoundNum - 1) * GetKillInterval();
RecordDeath(HotPotatoState.HolderID, (HotPotatoState.RoundNum - 1) * GetKillInterval());
PassHotPotato();
if (holder.AmOwner)
+67 -35
View File
@@ -747,51 +747,81 @@ public static class KingOfTheZones
}
else
{
List<SystemTypes> added = [];
List<SystemTypes> removed = [];
foreach ((SystemTypes zone, long moveTS) in ZoneMoveSchedules)
if (atOnce)
{
if (moveTS > now) continue;
try
if (Zones.Count > 0 && ZoneMoveSchedules.TryGetValue(Zones[0], out long moveTS) && moveTS <= now)
{
SystemTypes newZone = AllRooms.Except(Zones).RandomElement();
IEnumerable<SystemTypes> newZones = AllRooms.Except(Zones).TakeRandom(Zones.Count);
int index = Zones.IndexOf(zone);
Zones.RemoveAt(index);
Zones.Insert(index, newZone);
Zones.Clear();
ZoneDomination.Clear();
added.Add(newZone);
removed.Add(zone);
ZoneDomination.Remove(zone);
Logger.Info($"Zone moved: {zone} => {newZone}", "KOTZ");
foreach (SystemTypes newZone in newZones)
{
Zones.Add(newZone);
ZoneDomination[newZone] = KOTZTeam.None;
ZoneMoveSchedules[newZone] = now + moveTime + downtime;
if (downtime > 0) ZoneDowntimeExpire[newZone] = now + downtime;
}
}
catch (Exception e) { Utils.ThrowException(e); }
}
removed.ForEach(x => ZoneMoveSchedules.Remove(x));
added.ForEach(x =>
else
{
ZoneMoveSchedules[x] = now + moveTime + downtime;
if (downtime > 0) ZoneDowntimeExpire[x] = now + downtime;
});
SystemTypes? added = null;
SystemTypes? removed = null;
foreach ((SystemTypes zone, long moveTS) in ZoneMoveSchedules)
{
if (moveTS > now) continue;
try
{
SystemTypes newZone = AllRooms.Except(Zones).RandomElement();
int index = Zones.IndexOf(zone);
Zones.RemoveAt(index);
Zones.Insert(index, newZone);
added = newZone;
removed = zone;
ZoneDomination.Remove(zone);
Logger.Info($"Zone moved: {zone} => {newZone}", "KOTZ");
break;
}
catch (Exception e) { Utils.ThrowException(e); }
}
if (removed.HasValue) ZoneMoveSchedules.Remove(removed.Value);
if (added.HasValue)
{
ZoneDomination[added.Value] = KOTZTeam.None;
ZoneMoveSchedules[added.Value] = now + moveTime + downtime;
if (downtime > 0) ZoneDowntimeExpire[added.Value] = now + downtime;
}
}
}
if (downtime > 0 && ZoneDowntimeExpire.Count > 0)
{
List<SystemTypes> toRemove = [];
foreach ((SystemTypes zone, long downtimeExpire) in ZoneDowntimeExpire)
if (atOnce)
{
if (downtimeExpire > now) continue;
toRemove.Add(zone);
if (Zones.Count > 0 && ZoneDowntimeExpire.TryGetValue(Zones[0], out long expireTS) && expireTS <= now)
ZoneDowntimeExpire.Clear();
}
else
{
SystemTypes? toRemove = null;
toRemove.ForEach(x => ZoneDowntimeExpire.Remove(x));
foreach ((SystemTypes zone, long downtimeExpire) in ZoneDowntimeExpire)
{
if (downtimeExpire > now) continue;
toRemove = zone;
}
if (toRemove.HasValue) ZoneDowntimeExpire.Remove(toRemove.Value);
}
}
}
}
@@ -803,15 +833,16 @@ public static class KingOfTheZones
{
if (SpawnProtectionTimes.Count > 0)
{
List<byte> toRemove = [];
List<byte> toRemove = null;
foreach ((byte id, long protectionTS) in SpawnProtectionTimes)
{
if (protectionTS > now) continue;
toRemove ??= [];
toRemove.Add(id);
}
toRemove.ForEach(x => SpawnProtectionTimes.Remove(x));
toRemove?.ForEach(x => SpawnProtectionTimes.Remove(x));
}
}
catch (Exception e) { Utils.ThrowException(e); }
@@ -822,7 +853,7 @@ public static class KingOfTheZones
{
if (RespawnTimes.Count > 0)
{
List<byte> toRemove = [];
List<byte> toRemove = null;
foreach ((byte id, long respawnTS) in RespawnTimes)
{
@@ -846,12 +877,13 @@ public static class KingOfTheZones
}
catch (Exception e) { Utils.ThrowException(e); }
toRemove ??= [];
toRemove.Add(id);
Logger.Info($"{Main.AllPlayerNames[id]} respawned", "KOTZ");
}
toRemove.ForEach(x => RespawnTimes.Remove(x));
toRemove?.ForEach(x => RespawnTimes.Remove(x));
}
}
catch (Exception e) { Utils.ThrowException(e); }
+103 -112
View File
@@ -19,7 +19,7 @@ namespace EHR.Gamemodes;
public static class NaturalDisasters
{
private const float Range = 1.5f;
private static List<Type> AllDisasters = [];
private static Dictionary<string, Type> AllDisasters = [];
private static readonly List<Disaster> ActiveDisasters = [];
private static readonly List<NaturalDisaster> PreparingDisasters = [];
public static readonly Dictionary<byte, int> SurvivalTimes = [];
@@ -38,7 +38,6 @@ public static class NaturalDisasters
private static readonly string[] LimitReachedOptions =
[
"ND_LimitReachedOptions.OnlySpawnInstantDisasters",
"ND_LimitReachedOptions.RemoveRandom",
"ND_LimitReachedOptions.RemoveOldest"
];
@@ -55,9 +54,9 @@ public static class NaturalDisasters
return ActiveDisasters;
}
public static List<Type> GetAllDisasters()
public static IEnumerable<Type> GetAllDisasters()
{
return AllDisasters;
return AllDisasters.Values;
}
public static int FrequencyOfDisasters => DisasterFrequency.GetInt();
@@ -90,7 +89,7 @@ public static class NaturalDisasters
.SetParent(LimitMaximumDisastersAtOnce)
.SetColor(color);
WhenLimitIsReached = new StringOptionItem(id++, "ND_WhenLimitIsReached", LimitReachedOptions, 2, TabGroup.GameSettings)
WhenLimitIsReached = new StringOptionItem(id++, "ND_WhenLimitIsReached", LimitReachedOptions, 1, TabGroup.GameSettings)
.SetGameMode(gameMode)
.SetParent(LimitMaximumDisastersAtOnce)
.SetColor(color);
@@ -110,20 +109,24 @@ public static class NaturalDisasters
LoadAllDisasters();
AllDisasters.ConvertAll(x => x.Name).ForEach(x => DisasterSpawnChances[x] = new IntegerOptionItem(id++, "ND_Disaster.SpawnChance", new(0, 100, 5), 50, TabGroup.GameSettings)
.SetGameMode(gameMode)
.SetColor(color)
.SetValueFormat(OptionFormat.Percent)
.AddReplacement(("{disaster}", Translator.GetString($"ND_{x}"))));
foreach ((string name, Type type) in AllDisasters)
{
DisasterSpawnChances[name] = new IntegerOptionItem(id++, "ND_Disaster.SpawnChance", new(0, 100, 5), 50, TabGroup.GameSettings)
.SetGameMode(gameMode)
.SetColor(color)
.SetHeader(true)
.SetValueFormat(OptionFormat.Percent)
.AddReplacement(("{disaster}", Translator.GetString($"ND_{name}")));
AllDisasters.ForEach(x => x.GetMethod("SetupOwnCustomOption")?.Invoke(null, null));
type.GetMethod("SetupOwnCustomOption")?.Invoke(null, null);
}
}
public static void LoadAllDisasters()
{
AllDisasters = Main.AllTypes
.Where(x => x.IsClass && !x.IsAbstract && x.IsSubclassOf(typeof(Disaster)))
.ToList();
.ToDictionary(x => x.Name, x => x);
}
public static void OnGameStart()
@@ -164,26 +167,31 @@ public static class NaturalDisasters
public static void RebuildSuffixText()
{
var allRooms = ShipStatus.Instance.AllRooms;
var collapsedRooms = BuildingCollapse.CollapsedRooms;
string cb;
if (allRooms.Count / 2 <= collapsedRooms.Count)
try
{
SystemTypes[] remainingRooms = allRooms.Select(x => x.RoomId).Where(x => x is not (SystemTypes.Hallway or SystemTypes.Outside) && !x.ToString().Contains("Decontamination")).Except(collapsedRooms.ConvertAll(x => x.RoomId)).ToArray();
cb = string.Format(Translator.GetString("AvailableBuildings"), remainingRooms.Length > 0
? remainingRooms.Select(x => Translator.GetString($"{x}")).Distinct().Join()
: $"<#ff0000>{Translator.GetString("None")}</color>");
}
else
{
cb = string.Format(Translator.GetString("CollapsedBuildings"), collapsedRooms.Count > 0
? collapsedRooms.Select(x => Translator.GetString($"{x.RoomId}")).Distinct().Join()
: Translator.GetString("None"));
}
var allRooms = ShipStatus.Instance.AllRooms;
var collapsedRooms = BuildingCollapse.CollapsedRooms;
string cb;
string ts = ActiveDisasters.Exists(x => x is Thunderstorm) ? $"\n{Translator.GetString("OngoingThunderstorm")}" : string.Empty;
SuffixText = $"<size=70%>{cb}{ts}</size>";
if (allRooms.Count / 2 <= collapsedRooms.Count)
{
SystemTypes[] remainingRooms = allRooms.Select(x => x.RoomId).Where(x => x is not (SystemTypes.Hallway or SystemTypes.Outside or SystemTypes.Decontamination2 or SystemTypes.Decontamination3)).Except(collapsedRooms.ConvertAll(x => x.RoomId)).ToArray();
cb = string.Format(Translator.GetString("AvailableBuildings"), remainingRooms.Length > 0
? remainingRooms.Select(x => Translator.GetString($"{x}")).Distinct().Join()
: $"<#ff0000>{Translator.GetString("None")}</color>");
}
else
{
cb = string.Format(Translator.GetString("CollapsedBuildings"), collapsedRooms.Count > 0
? collapsedRooms.Select(x => Translator.GetString($"{x.RoomId}")).Distinct().Join()
: Translator.GetString("None"));
}
string ts = ActiveDisasters.Exists(x => x is Thunderstorm) ? $"\n{Translator.GetString("OngoingThunderstorm")}" : string.Empty;
string rp = string.Format(Translator.GetString("ND_RemainingPlayers"), Utils.ColorString(Utils.GetRoleColor(CustomRoles.NDPlayer), Main.AllAlivePlayerControls.Count.ToString()));
SuffixText = $"<size=70%>{cb}{ts}\n{rp}</size>";
}
catch (Exception e) { Utils.ThrowException(e); }
}
public static int SurvivalTime(byte id)
@@ -191,9 +199,13 @@ public static class NaturalDisasters
return SurvivalTimes.GetValueOrDefault(id, 0);
}
private static readonly List<(byte id, PlayerState.DeathReason deathReason, long recordTimeStamp)> DeathMessageQueue = [];
public static void RecordDeath(PlayerControl pc, PlayerState.DeathReason deathReason)
{
SurvivalTimes[pc.PlayerId] = (int)(Utils.TimeStamp - IntroCutsceneDestroyPatch.IntroDestroyTS - FixedUpdatePatch.WaitTime);
RebuildSuffixText();
long now = Utils.TimeStamp;
SurvivalTimes[pc.PlayerId] = (int)(now - IntroCutsceneDestroyPatch.IntroDestroyTS - FixedUpdatePatch.WaitTime);
string message = Translator.GetString($"ND_DRLaughMessage-{IRandom.Instance.Next(4)}.{deathReason}");
Color color = DeathReasonColor(deathReason);
@@ -201,10 +213,7 @@ public static class NaturalDisasters
LateTask.New(() => pc.Notify(message, 20f), 1f, $"{pc.GetRealName()} died with the reason {deathReason}, survived for {SurvivalTime(pc.PlayerId)} seconds");
Utils.SendRPC(CustomRPC.NaturalDisastersSync, pc.PlayerId, SurvivalTimes[pc.PlayerId]);
var aapc = Main.AllAlivePlayerControls;
string remaining = string.Format(Translator.GetString("ND_RemainingPlayers"), Utils.ColorString(Utils.GetRoleColor(CustomRoles.NDPlayer), aapc.Count.ToString()));
string msgOthers = string.Format(Translator.GetString($"ND_DRLaughMessageOthers-{IRandom.Instance.Next(4)}.{deathReason}"), pc.PlayerId.ColoredPlayerName());
aapc.NotifyPlayers($"<#ff0000>[╳]</color> {Utils.ColorString(color, msgOthers)} {remaining}", 10f);
DeathMessageQueue.Add((pc.PlayerId, deathReason, now));
}
private static Color DeathReasonColor(PlayerState.DeathReason deathReason)
@@ -240,7 +249,6 @@ public static class NaturalDisasters
public static class FixedUpdatePatch
{
public static readonly Stopwatch LastDisasterTimer = new();
private static readonly Stopwatch FailSafeTimer = new();
private static long LastSync = Utils.TimeStamp;
public static int WaitTime;
@@ -269,46 +277,40 @@ public static class NaturalDisasters
UpdatePreparingDisasters();
for (var index = 0; index < ActiveDisasters.Count; index++)
{
if (ActiveDisasters[index].Update())
index--;
}
for (int index = ActiveDisasters.Count - 1; index >= 0; index--)
ActiveDisasters[index].Update();
Sinkhole.OnFixedUpdate();
BuildingCollapse.OnFixedUpdate();
if (LimitMaximumDisastersAtOnce.GetBool())
{
int numDisasters = ActiveDisasters.Count + PreparingDisasters.Count + Sinkhole.Sinkholes.Count;
int numDisasters = ActiveDisasters.Count + Sinkhole.Sinkholes.Count;
if (numDisasters > MaximumDisastersAtOnce.GetInt())
{
switch (WhenLimitIsReached.GetValue())
if (Sinkhole.Sinkholes.Count > 0)
{
case 0:
Sinkhole.RemoveRandomSinkhole();
}
else
{
switch (WhenLimitIsReached.GetValue())
{
AllDisasters.RemoveAll(x => x.Name is "Earthquake" or "VolcanoEruption" or "Tornado" or "Thunderstorm" or "SandStorm" or "Tsunami");
break;
}
case 1 or 2 when Sinkhole.Sinkholes.Count > 0:
{
Sinkhole.RemoveRandomSinkhole();
break;
}
case 1:
{
Disaster remove = PreferRemovingThunderstorm.GetBool() && ActiveDisasters.FindFirst(x => x is Thunderstorm, out Disaster thunderstorm) ? thunderstorm : ActiveDisasters.RandomElement();
remove?.Duration = 0;
remove?.RemoveIfExpired();
break;
}
case 2:
{
Disaster oldest = ActiveDisasters.MinBy(x => x.StartTimeStamp);
oldest.Duration = 0;
oldest.RemoveIfExpired();
break;
case 0:
{
Disaster remove = PreferRemovingThunderstorm.GetBool() && ActiveDisasters.FindFirst(x => x is Thunderstorm, out Disaster thunderstorm) ? thunderstorm : ActiveDisasters.RandomElement();
remove?.Duration = 0;
remove?.RemoveIfExpired();
break;
}
case 1:
{
Disaster oldest = ActiveDisasters.MinBy(x => x.StartTimeStamp);
oldest.Duration = 0;
oldest.RemoveIfExpired();
break;
}
}
}
}
@@ -324,40 +326,36 @@ public static class NaturalDisasters
if (LastDisasterTimer.Elapsed.TotalSeconds >= frequency)
{
LastDisasterTimer.Restart();
List<Type> disasters = AllDisasters.SelectMany(x => Enumerable.Repeat(x, DisasterSpawnChances[x.Name].GetInt() / 5)).ToList();
Dictionary<string, Type> pool = AllDisasters.ToDictionary(x => x.Key, x => x.Value);
if (ActiveDisasters.Exists(x => x is Thunderstorm))
disasters.RemoveAll(x => x.Name == "Thunderstorm");
pool.Remove("Thunderstorm");
(SystemTypes CollapsingRoom, Vector2 WarningPosition) buildingCollapseInfo = (default(SystemTypes), default(Vector2));
PlainShipRoom[] nonCollapsedRooms = ShipStatus.Instance.AllRooms.Where(x => x.RoomId is not (SystemTypes.Hallway or SystemTypes.Outside or SystemTypes.Decontamination2 or SystemTypes.Decontamination3)).Except(BuildingCollapse.CollapsedRooms).ToArray();
if ((Main.LIMap ? ShipStatus.Instance.AllRooms.Count : RandomSpawn.SpawnMap.GetSpawnMap().Positions.Count - (Main.CurrentMap == MapNames.Polus ? 3 : 1)) <= BuildingCollapse.CollapsedRooms.Count)
disasters.RemoveAll(x => x.Name == "BuildingCollapse");
if (nonCollapsedRooms.Length == 0)
{
pool.Remove("BuildingCollapse");
}
else
{
PlainShipRoom collapsingRoom = nonCollapsedRooms.RandomElement();
buildingCollapseInfo = (collapsingRoom.RoomId, (Main.LIMap ? [] : RandomSpawn.SpawnMap.GetSpawnMap().Positions).TryGetValue(collapsingRoom.RoomId, out Vector2 spawnLocation) ? spawnLocation : collapsingRoom.transform.position);
}
Type disaster = disasters.RandomElement();
Type disaster = pool.SelectMany(x => Enumerable.Repeat(x.Value, DisasterSpawnChances[x.Key].GetInt() / 5)).RandomElement();
if (disaster.Name == "Thunderstorm")
{
_ = new Thunderstorm(Vector2.zero, null);
return;
}
KeyValuePair<SystemTypes, Vector2> roomKvp;
if (Main.LIMap)
{
var plainShipRoom = ShipStatus.Instance.AllRooms.Where(x => x.RoomId is not (SystemTypes.Hallway or SystemTypes.Outside) && !x.RoomId.ToString().Contains("Decontamination")).Except(BuildingCollapse.CollapsedRooms).RandomElement();
roomKvp = new KeyValuePair<SystemTypes, Vector2>(plainShipRoom.RoomId, plainShipRoom.transform.position);
}
else
{
roomKvp = RandomSpawn.SpawnMap.GetSpawnMap().Positions
.Where(x => x.Key is not (SystemTypes.Hallway or SystemTypes.Outside) && !x.Key.ToString().Contains("Decontamination"))
.ExceptBy(BuildingCollapse.CollapsedRooms.ConvertAll(x => x.RoomId), x => x.Key)
.RandomElement();
}
var aapc = Main.AllAlivePlayerControls;
bool bc = disaster.Name == "BuildingCollapse";
bool spawnOnPlayer = aapc.Count > 0 && DisasterSpawnMode.GetValue() switch
bool spawnOnPlayer = !bc && aapc.Count > 0 && DisasterSpawnMode.GetValue() switch
{
0 => true,
1 => false,
@@ -365,25 +363,13 @@ public static class NaturalDisasters
};
Vector2 position = bc
? roomKvp.Value
? buildingCollapseInfo.WarningPosition
: spawnOnPlayer
? aapc.RandomElement().Pos()
: RandomInRoomPosition();
: new(Random.Range(MapBounds.X.Left, MapBounds.X.Right), Random.Range(MapBounds.Y.Bottom, MapBounds.Y.Top));
SystemTypes? room = bc ? roomKvp.Key : null;
SystemTypes? room = bc ? buildingCollapseInfo.CollapsingRoom : null;
AddPreparingDisaster(position, disaster.Name, room);
static Vector2 RandomInRoomPosition()
{
FailSafeTimer.Start();
Vector2 vector2;
do vector2 = new(Random.Range(MapBounds.X.Left, MapBounds.X.Right), Random.Range(MapBounds.Y.Top, MapBounds.Y.Bottom));
while (FailSafeTimer.ElapsedMilliseconds < 3 && !vector2.GetPlainShipRoom());
FailSafeTimer.Reset();
return vector2;
}
}
if (now - LastSync >= 15)
@@ -399,28 +385,36 @@ public static class NaturalDisasters
LastSync = now;
Utils.MarkEveryoneDirtySettings();
}
if (DeathMessageQueue.Count > 0 && DeathMessageQueue.FindFirst(x => x.recordTimeStamp + 3 <= now, out var expired))
{
var names = string.Join(", ", DeathMessageQueue.Where(x => x.deathReason == expired.deathReason).Select(x => x.id.ColoredPlayerName()));
string msgOthers = string.Format(Translator.GetString($"ND_DRLaughMessageOthers-{IRandom.Instance.Next(4)}.{expired.deathReason}"), names);
Main.EnumerateAlivePlayerControls().NotifyPlayers($"<#ff0000>[╳]</color> {Utils.ColorString(DeathReasonColor(expired.deathReason), msgOthers)}");
DeathMessageQueue.RemoveAll(x => x.deathReason == expired.deathReason);
}
}
public static void UpdatePreparingDisasters()
{
for (var index = 0; index < PreparingDisasters.Count; index++)
for (var index = PreparingDisasters.Count - 1; index >= 0; index--)
{
NaturalDisaster naturalDisaster = PreparingDisasters[index];
naturalDisaster.Update();
if (float.IsNaN(naturalDisaster.SpawnTimer))
if (!naturalDisaster.SpawnTimer.IsRunning)
{
Type type = AllDisasters.Find(d => d.Name == naturalDisaster.DisasterName);
Type type = AllDisasters[naturalDisaster.DisasterName];
Activator.CreateInstance(type, naturalDisaster.Position, naturalDisaster);
PreparingDisasters.RemoveAt(index);
index--;
}
}
}
public static void AddPreparingDisaster(Vector2 position, string disasterName, SystemTypes? room)
{
PreparingDisasters.Add(new(position, DisasterWarningTime.GetFloat(), Sprite(disasterName), disasterName, room));
try { PreparingDisasters.Add(new(position, DisasterWarningTime.GetInt(), Sprite(disasterName), disasterName, room)); }
catch (Exception e) { Utils.ThrowException(e); }
}
}
@@ -447,6 +441,9 @@ public static class NaturalDisasters
case CustomGameMode.Mingle:
Mingle.HandleDisconnect();
goto default;
case CustomGameMode.HotPotato:
HotPotato.RecordDeath(pc.PlayerId, (int)(Utils.TimeStamp - IntroCutsceneDestroyPatch.IntroDestroyTS - FixedUpdatePatch.WaitTime));
goto default;
default:
pc.Suicide(deathReason);
return;
@@ -515,7 +512,6 @@ public static class NaturalDisasters
Color color = Utils.GetRoleColor(CustomRoles.NDPlayer);
DurationOpt = new IntegerOptionItem(id, "ND_Earthquake.DurationOpt", new(1, 120, 1), 30, TabGroup.GameSettings)
.SetHeader(true)
.SetGameMode(CustomGameMode.NaturalDisasters)
.SetColor(color)
.SetValueFormat(OptionFormat.Seconds);
@@ -612,7 +608,6 @@ public static class NaturalDisasters
Color color = Utils.GetRoleColor(CustomRoles.NDPlayer);
FlowStepDelay = new FloatOptionItem(id, "ND_VolcanoEruption.FlowStepDelay", new(0.5f, 5f, 0.5f), 1f, TabGroup.GameSettings)
.SetHeader(true)
.SetGameMode(CustomGameMode.NaturalDisasters)
.SetColor(color)
.SetValueFormat(OptionFormat.Seconds);
@@ -679,7 +674,6 @@ public static class NaturalDisasters
Color color = Utils.GetRoleColor(CustomRoles.NDPlayer);
DurationOpt = new IntegerOptionItem(id, "ND_Tornado.DurationOpt", new(1, 120, 1), 20, TabGroup.GameSettings)
.SetHeader(true)
.SetGameMode(CustomGameMode.NaturalDisasters)
.SetColor(color)
.SetValueFormat(OptionFormat.Seconds);
@@ -789,7 +783,6 @@ public static class NaturalDisasters
Color color = Utils.GetRoleColor(CustomRoles.NDPlayer);
DurationOpt = new IntegerOptionItem(id, "ND_Thunderstorm.DurationOpt", new(1, 120, 1), 20, TabGroup.GameSettings)
.SetHeader(true)
.SetGameMode(CustomGameMode.NaturalDisasters)
.SetColor(color)
.SetValueFormat(OptionFormat.Seconds);
@@ -864,7 +857,6 @@ public static class NaturalDisasters
Color color = Utils.GetRoleColor(CustomRoles.NDPlayer);
DurationOpt = new IntegerOptionItem(id, "ND_SandStorm.DurationOpt", new(1, 120, 1), 30, TabGroup.GameSettings)
.SetHeader(true)
.SetGameMode(CustomGameMode.NaturalDisasters)
.SetColor(color)
.SetValueFormat(OptionFormat.Seconds);
@@ -1021,7 +1013,6 @@ public static class NaturalDisasters
Color color = Utils.GetRoleColor(CustomRoles.NDPlayer);
MovingSpeed = new FloatOptionItem(id, "ND_Tsunami.MovingSpeed", new(0.25f, 10f, 0.25f), 2f, TabGroup.GameSettings)
.SetHeader(true)
.SetGameMode(CustomGameMode.NaturalDisasters)
.SetColor(color)
.SetValueFormat(OptionFormat.Multiplier);
+1 -1
View File
@@ -502,7 +502,7 @@ public static class Quiz
var spectators = aapc.ExceptBy(dyingPlayers, x => x.PlayerId).ToArray();
spectators.Do(x => x.ExileTemporarily());
yield return new WaitForSecondsRealtime(0.2f);
var stillLiving = dyingPlayers.ToValidPlayers().FindAll(x => x.IsAlive());
var stillLiving = dyingPlayers.ToValidPlayers().Where(x => x.IsAlive()).ToList();
stillLiving.ForEach(x => x.RpcChangeRoleBasis(CustomRoles.SerialKiller));
Utils.SendRPC(CustomRPC.QuizSync, AllowKills);
+1 -1
View File
@@ -36,7 +36,7 @@ public static class RoomRush
private static HashSet<byte> DonePlayers = [];
private static bool GameGoing;
private static DateTime GameStartDateTime;
public static DateTime GameStartDateTime;
private static RandomSpawn.SpawnMap Map;
+1 -1
View File
@@ -613,7 +613,7 @@ public static class TheMindGame
Round = 5;
HiddenPoints.Clear();
EjectedPlayers.ToValidPlayers().FindAll(x => !x.IsAlive()).ForEach(x => x.RpcRevive());
EjectedPlayers.ToValidPlayers().Where(x => !x.IsAlive()).Do(x => x.RpcRevive());
yield return NotifyEveryone("TMG.Notify.Round4End", 3);
if (Stop) yield break;
+114 -64
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using AmongUs.InnerNet.GameDataMessages;
using EHR.Gamemodes;
@@ -28,13 +29,15 @@ namespace EHR
private string Sprite;
private int SnapToSendFrameCount;
private bool IsPooled;
public void RpcChangeSprite(string sprite)
{
if (!AmongUsClient.Instance.AmHost) return;
if (this is not NaturalDisaster nd || nd.SpawnTimer <= 0f) Logger.Info($" Change Custom Net Object {GetType().Name} (ID {Id}) sprite", "CNO.RpcChangeSprite");
if (this is not NaturalDisaster nd || !nd.SpawnTimer.IsRunning) Logger.Info($" Change Custom Net Object {GetType().Name} (ID {Id}) sprite", "CNO.RpcChangeSprite");
bool notImportant = this is BedWarsItemGenerator || (this is NaturalDisaster { SpawnTimer: > 1f } && Options.CurrentGameMode != CustomGameMode.NaturalDisasters && GameStates.CurrentServerType == GameStates.ServerType.Vanilla);
bool notImportant = this is BedWarsItemGenerator || (this is NaturalDisaster n && n.SpawnTimer.Elapsed.TotalSeconds < n.TotalWarningTime - 1 && Options.CurrentGameMode != CustomGameMode.NaturalDisasters && GameStates.CurrentServerType == GameStates.ServerType.Vanilla);
SendOption channel = notImportant ? SendOption.None : SendOption.Reliable;
DataFlagRateLimiter.Enqueue(() =>
@@ -92,15 +95,55 @@ namespace EHR
public void TP(Vector2 position)
{
Position = position;
SnapToSendFrameCount = 30;
}
private bool TryReusePooledObject(string sprite, Vector2 position)
{
try
{
if (this is not NaturalDisaster) return false;
CustomNetObject pooled = AllObjects.Find(x => x.IsPooled && x.playerControl);
if (pooled == null) return false;
Logger.Info($" Reusing pooled Custom Net Object NaturalDisaster (ID {pooled.Id})", "CNO.CreateNetObject");
playerControl = pooled.playerControl;
Id = pooled.Id;
IsPooled = false;
AllObjects.Remove(pooled);
AllObjects.Add(this);
TP(position);
RpcChangeSprite(sprite);
return true;
}
catch (Exception e)
{
Utils.ThrowException(e);
return false;
}
}
public void Despawn()
public void Despawn(bool canPool = true)
{
if (!AmongUsClient.Instance.AmHost) return;
Logger.Info($" Despawn Custom Net Object {GetType().Name} (ID {Id})", "CNO.Despawn");
try
{
if (canPool && this is NaturalDisaster && playerControl)
{
Logger.Info($" Pooled Custom Net Object {GetType().Name} (ID {Id})", "CNO.Despawn");
IsPooled = true;
TP(new Vector2(50f, 50f));
return;
}
Logger.Info($" Despawn Custom Net Object {GetType().Name} (ID {Id})", "CNO.Despawn");
if (playerControl)
{
MessageWriter writer = MessageWriter.Get(SendOption.Reliable);
@@ -168,7 +211,7 @@ namespace EHR
return false;
}
if (this is not ShapeshiftMenuElement)
if (this is not ShapeshiftMenuElement && player.IsNonHostModdedClient())
{
LateTask.New(() =>
{
@@ -220,7 +263,7 @@ namespace EHR
protected void CreateNetObject(string sprite, Vector2 position)
{
if (GameStates.IsEnded || !AmongUsClient.Instance.AmHost) return;
if (GameStates.IsEnded || !AmongUsClient.Instance.AmHost || TryReusePooledObject(sprite, position)) return;
Logger.Info($" Create Custom Net Object {GetType().Name} (ID {MaxId + 1}) at {position} - Time since game start: {Utils.TimeStamp - IntroCutsceneDestroyPatch.IntroDestroyTS}s", "CNO.CreateNetObject");
Main.Instance.StartCoroutine(CoRoutine());
@@ -303,50 +346,53 @@ namespace EHR
qa = DataFlagRateLimiter.Enqueue(() =>
{
int messages = 0;
int packingLimit = AmongUsClient.Instance.GetMaxMessagePackingLimit();
MessageWriter stream = MessageWriter.Get(SendOption.Reliable);
stream.StartMessage(26);
stream.WritePacked(AmongUsClient.Instance.GameId);
foreach (PlayerControl pc in Main.EnumeratePlayerControls())
if (PlayerControl.AllPlayerControls.Count > 1)
{
if (pc.AmOwner) continue;
int messages = 0;
int packingLimit = AmongUsClient.Instance.GetMaxMessagePackingLimit();
MessageWriter stream = MessageWriter.Get(SendOption.Reliable);
stream.StartMessage(26);
stream.WritePacked(AmongUsClient.Instance.GameId);
if (stream.Length > 500 || messages + 3 > packingLimit)
foreach (PlayerControl pc in Main.EnumeratePlayerControls())
{
if (pc.AmOwner) continue;
if (stream.Length > 500 || messages + 3 > packingLimit)
{
stream.EndMessage();
AmongUsClient.Instance.SendOrDisconnect(stream);
stream.Clear(SendOption.Reliable);
stream.StartMessage(26);
stream.WritePacked(AmongUsClient.Instance.GameId);
}
stream.StartMessage(6);
stream.Write(AmongUsClient.Instance.GameId);
stream.WritePacked(pc.OwnerId);
stream.StartMessage(1);
stream.WritePacked(playerControl.NetId);
stream.Write(pc.PlayerId);
stream.EndMessage();
AmongUsClient.Instance.SendOrDisconnect(stream);
stream.Clear(SendOption.Reliable);
stream.StartMessage(26);
stream.WritePacked(AmongUsClient.Instance.GameId);
stream.StartMessage(2);
stream.WritePacked(playerControl.NetId);
stream.Write((byte)RpcCalls.MurderPlayer);
stream.WriteNetObject(playerControl);
stream.Write((int)MurderResultFlags.FailedError);
stream.EndMessage();
stream.StartMessage(1);
stream.WritePacked(playerControl.NetId);
stream.Write((byte)254);
stream.EndMessage();
stream.EndMessage();
messages += 3;
}
stream.StartMessage(6);
stream.Write(AmongUsClient.Instance.GameId);
stream.WritePacked(pc.OwnerId);
stream.StartMessage(1);
stream.WritePacked(playerControl.NetId);
stream.Write(pc.PlayerId);
stream.EndMessage();
stream.StartMessage(2);
stream.WritePacked(playerControl.NetId);
stream.Write((byte)RpcCalls.MurderPlayer);
stream.WriteNetObject(playerControl);
stream.Write((int)MurderResultFlags.FailedError);
stream.EndMessage();
stream.StartMessage(1);
stream.WritePacked(playerControl.NetId);
stream.Write((byte)254);
stream.EndMessage();
stream.EndMessage();
messages += 3;
AmongUsClient.Instance.SendOrDisconnect(stream);
stream.Recycle();
}
stream.EndMessage();
AmongUsClient.Instance.SendOrDisconnect(stream);
stream.Recycle();
playerControl.CachedPlayerData = PlayerControl.LocalPlayer.Data;
}, calls: 2); // WAITING FOR THE SLOTHS TO VERIFY THIS
@@ -441,20 +487,20 @@ namespace EHR
public static void FixedUpdate()
{
foreach (CustomNetObject cno in AllObjects.ToArray())
cno?.OnFixedUpdate();
for (int index = AllObjects.Count - 1; index >= 0; index--)
AllObjects[index]?.OnFixedUpdate();
}
public static CustomNetObject Get(int id)
{
return AllObjects.FirstOrDefault(x => x.Id == id);
return AllObjects.Find(x => x.Id == id);
}
public static void Reset()
{
try
{
AllObjects.ToArray().Do(x => x.Despawn());
AllObjects.ToArray().Do(x => x.Despawn(canPool: false));
AllObjects.Clear();
}
catch (Exception e) { Utils.ThrowException(e); }
@@ -607,51 +653,56 @@ namespace EHR
public sealed class NaturalDisaster : CustomNetObject
{
public NaturalDisaster(Vector2 position, float time, string sprite, string disasterName, SystemTypes? room)
public NaturalDisaster(Vector2 position, int time, string sprite, string disasterName, SystemTypes? room)
{
string name = Translator.GetString($"ND_{disasterName}");
string warning = $"<size=250%>{Math.Ceiling(time):N0}</size>\n{name}";
string warning = $"<size=250%>{time}</size>\n{name}";
if (room.HasValue)
{
warning = $"<#ff4444>{warning}</color>";
Main.EnumerateAlivePlayerControls().DoIf(x => x.IsInRoom(room.Value), x => x.ReactorFlash());
try { Main.EnumerateAlivePlayerControls().DoIf(x => x.IsInRoom(room.Value), x => x.ReactorFlash()); }
catch (Exception e) { Utils.ThrowException(e); }
}
SpawnTimer = time;
TotalWarningTime = time;
DisasterSprite = sprite;
DisasterName = disasterName;
DisasterNameTranslated = name;
Room = room;
SpawnTimer = Stopwatch.StartNew();
CreateNetObject(warning, position);
}
public SystemTypes? Room { get; }
public string DisasterName { get; }
public float SpawnTimer { get; private set; }
public Stopwatch SpawnTimer { get; }
public int TotalWarningTime { get; }
private string DisasterSprite { get; }
private int TimeInt => (int)Math.Ceiling(SpawnTimer);
private int TimeInt => (int)(SpawnTimer.Elapsed.TotalSeconds);
private int PreviousTimeInt { get; set; }
private string DisasterNameTranslated { get; }
public void Update()
{
if (float.IsNaN(SpawnTimer)) return;
if (!SpawnTimer.IsRunning) return;
int newTime = TimeInt;
int oldTime = TimeInt;
SpawnTimer -= Time.fixedDeltaTime;
if (SpawnTimer <= 0f)
if (newTime >= TotalWarningTime)
{
SpawnTimer.Stop();
if (!Room.HasValue) RpcChangeSprite(DisasterSprite);
SpawnTimer = float.NaN;
}
else
{
int newTime = TimeInt;
if (oldTime == newTime) return;
string warning = $"<size=250%>{newTime:N0}</size>\n{DisasterNameTranslated}";
if (PreviousTimeInt == newTime) return;
PreviousTimeInt = newTime;
string warning = $"<size=250%>{TotalWarningTime - newTime}</size>\n{DisasterNameTranslated}";
if (Room.HasValue) warning = $"<#ff4444>{warning}</color>";
RpcChangeSprite(warning);
}
@@ -660,7 +711,7 @@ namespace EHR
internal sealed class Lightning : CustomNetObject
{
private float Timer = 5f;
private readonly Stopwatch Timer = Stopwatch.StartNew();
public Lightning(Vector2 position)
{
@@ -670,8 +721,7 @@ namespace EHR
protected override void OnFixedUpdate()
{
base.OnFixedUpdate();
Timer -= Time.fixedDeltaTime;
if (Timer <= 0) Despawn();
if (Timer.Elapsed.TotalSeconds >= 3f) Despawn();
}
}
+2 -2
View File
@@ -81,7 +81,7 @@ internal static class CustomRoleSelector
Dictionary<RoleAssignType, List<RoleAssignInfo>> roles = [];
Enum.GetValues<RoleAssignType>().Do(x => roles[x] = []);
foreach (byte id in Main.SetRoles.Keys.Where(id => Utils.GetPlayerById(id) == null).ToArray()) Main.SetRoles.Remove(id);
foreach (byte id in Main.SetRoles.Keys.Where(id => !Utils.GetPlayerById(id)).ToArray()) Main.SetRoles.Remove(id);
(bool Spawning, bool OneIsImp) loversData = (Lovers.LegacyLovers.GetBool(), rd.Next(100) < Lovers.LovingImpostorSpawnChance.GetInt());
loversData.Spawning &= rd.Next(100) < Options.CustomAdtRoleSpawnRate[CustomRoles.Lovers].GetInt();
@@ -559,7 +559,7 @@ internal static class CustomRoleSelector
{
if (Options.CurrentGameMode != CustomGameMode.Standard) return;
foreach (byte id in Main.SetAddOns.Keys.Where(id => Utils.GetPlayerById(id) == null).ToArray()) Main.SetAddOns.Remove(id);
foreach (byte id in Main.SetAddOns.Keys.Where(id => !Utils.GetPlayerById(id)).ToArray()) Main.SetAddOns.Remove(id);
AddonRolesList = [];
+2 -2
View File
@@ -85,9 +85,9 @@ public abstract class CustomSabotage
public static void UpdateAll()
{
foreach (CustomSabotage sabotage in Instances.ToArray())
for (int index = Instances.Count - 1; index >= 0; index--)
{
try { sabotage.Update(); }
try { Instances[index].Update(); }
catch (Exception e) { Utils.ThrowException(e); }
}
+2
View File
@@ -25,6 +25,8 @@ public class ErrorText : MonoBehaviour
public void Update()
{
if (AllErrors.Count == 0) return;
AllErrors.ForEach(err => err.IncreaseTimer());
ErrorData[] ToRemove = AllErrors.Where(err => err.ErrorLevel <= 1 && 30f < err.Timer).ToArray();
+25 -20
View File
@@ -170,7 +170,8 @@ internal static class ExtendedPlayerControl
public void FreezeForOthers()
{
if (!AmongUsClient.Instance.AmHost) return;
bool hasValue = false;
CustomRpcSender sender = CustomRpcSender.Create($"SnapTo Freeze ({player.GetNameWithRole()})", SendOption.Reliable);
sender.StartPackedMessage();
@@ -188,16 +189,17 @@ internal static class ExtendedPlayerControl
.EndRpc();
sender.EndMessage();
NumSnapToCallsThisRound += 2;
hasValue = true;
}
sender.SendMessage();
sender.SendMessage(dispose: !hasValue);
player.Visible = false;
}
public void SetChatVisible(bool visible, MessageWriter packedWriter = null)
public bool SetChatVisible(bool visible, MessageWriter packedWriter = null)
{
if (!AmongUsClient.Instance.AmHost) return;
if (!AmongUsClient.Instance.AmHost) return false;
Logger.Info($"Setting the chat {(visible ? "visible" : "hidden")} for {player.GetNameWithRole()}", "SetChatVisible");
@@ -205,7 +207,7 @@ internal static class ExtendedPlayerControl
{
HudManager.Instance.Chat.SetVisible(visible);
HudManager.Instance.Chat.HideBanButton();
return;
return false;
}
if (player.IsModdedClient())
@@ -213,12 +215,12 @@ internal static class ExtendedPlayerControl
var msg = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetChatVisible, SendOption.Reliable, player.OwnerId);
msg.Write(visible);
AmongUsClient.Instance.FinishRpcImmediately(msg);
return;
return false;
}
if (packedWriter == null) DataFlagRateLimiter.Enqueue(Action, calls: 3);
else Action();
return;
return true;
void Action()
{
@@ -409,14 +411,11 @@ internal static class ExtendedPlayerControl
if (setRoleMap)
{
foreach ((byte seerID, byte targetID) in StartGameHostPatch.RpcSetRoleReplacer.RoleMap.Keys.ToArray())
foreach ((byte seerID, byte targetID) in StartGameHostPatch.RpcSetRoleReplacer.RoleMap.Keys.Where(x => x.TargetID == player.PlayerId).ToArray())
{
if (targetID == player.PlayerId)
{
var value = StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(seerID, targetID)];
value.RoleType = roleTypes;
StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(seerID, targetID)] = value;
}
var value = StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(seerID, targetID)];
value.RoleType = roleTypes;
StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(seerID, targetID)] = value;
}
}
}
@@ -1514,7 +1513,8 @@ internal static class ExtendedPlayerControl
AmongUsClient.Instance.FinishRpcImmediately(writer);
NotifyRoles(SpecifyTarget: player);
}
bool hasValue = false;
var sender = CustomRpcSender.Create("RpcMakeInvisible", SendOption.Reliable);
sender.StartPackedMessage();
@@ -1534,9 +1534,10 @@ internal static class ExtendedPlayerControl
sender.EndMessage();
NumSnapToCallsThisRound += 2;
hasValue = true;
}
sender.SendMessage();
sender.SendMessage(dispose: !hasValue);
Logger.Info($"Made {player.GetNameWithRole()} invisible", "RpcMakeInvisible");
}
@@ -1565,7 +1566,8 @@ internal static class ExtendedPlayerControl
AmongUsClient.Instance.FinishRpcImmediately(writer);
NotifyRoles(SpecifyTarget: player);
}
bool hasValue = false;
var sender = CustomRpcSender.Create("RpcMakeVisible", SendOption.Reliable);
sender.StartPackedMessage();
@@ -1589,9 +1591,10 @@ internal static class ExtendedPlayerControl
sender.EndMessage();
NumSnapToCallsThisRound += 3;
hasValue = true;
}
sender.SendMessage();
sender.SendMessage(dispose: !hasValue);
Logger.Info($"Made {player.GetNameWithRole()} visible", "RpcMakeVisible");
}
@@ -1600,7 +1603,8 @@ internal static class ExtendedPlayerControl
if (!AmongUsClient.Instance.AmHost) return;
if (!Main.Invisible.Contains(player.PlayerId)) return;
if (phantom && Options.CurrentGameMode != CustomGameMode.Standard) return;
bool hasValue = false;
var sender = CustomRpcSender.Create("RpcResetInvisibility", SendOption.Reliable);
sender.StartPackedMessage();
@@ -1636,9 +1640,10 @@ internal static class ExtendedPlayerControl
sender.EndMessage();
NumSnapToCallsThisRound += 4;
hasValue = true;
}
sender.SendMessage();
sender.SendMessage(dispose: !hasValue);
Logger.Info($"Reset invisibility for {player.GetNameWithRole()}", "RpcResetInvisibility");
}
@@ -484,16 +484,6 @@ public static class CollectionExtensions
{
return playerIds.Select(Utils.GetPlayer).Where(x => x);
}
/// <summary>
/// Converts a list of player IDs to a list of <see cref="PlayerControl" /> instances
/// </summary>
/// <param name="playerIds"></param>
/// <returns></returns>
public static List<PlayerControl> ToValidPlayers(this List<byte> playerIds)
{
return playerIds.ConvertAll(Utils.GetPlayer).FindAll(x => x);
}
#endregion
+39 -27
View File
@@ -1195,35 +1195,47 @@ public static class GuessManager
[SuppressMessage("ReSharper", "UnusedMember.Local")]
public static void Postfix()
{
if (TextTemplate) Object.Destroy(TextTemplate.gameObject);
if (GuesserUI) Object.Destroy(GuesserUI);
foreach (List<Transform> roleButtonsValue in RoleButtons.Values)
{
foreach (Transform transform in roleButtonsValue)
{
if (transform?.gameObject)
Object.Destroy(transform.gameObject);
}
}
foreach (SpriteRenderer spriteRenderer in RoleSelectButtons.Values)
{
if (spriteRenderer?.gameObject)
Object.Destroy(spriteRenderer.gameObject);
}
foreach (SpriteRenderer spriteRenderer in PageButtons)
{
if (spriteRenderer?.gameObject)
Object.Destroy(spriteRenderer.gameObject);
}
if (TextTemplate && TextTemplate.gameObject) Object.Destroy(TextTemplate.gameObject);
TextTemplate = null;
if (GuesserUI) Object.Destroy(GuesserUI);
GuesserUI = null;
RoleButtons.Clear();
RoleSelectButtons.Clear();
PageButtons.Clear();
if (RoleButtons != null)
{
foreach (List<Transform> roleButtonsValue in RoleButtons.Values)
{
foreach (Transform transform in roleButtonsValue)
{
if (transform && transform.gameObject)
Object.Destroy(transform.gameObject);
}
}
RoleButtons = null;
}
if (RoleSelectButtons != null)
{
foreach (SpriteRenderer spriteRenderer in RoleSelectButtons.Values)
{
if (spriteRenderer && spriteRenderer.gameObject)
Object.Destroy(spriteRenderer.gameObject);
}
RoleSelectButtons = null;
}
if (PageButtons != null)
{
foreach (SpriteRenderer spriteRenderer in PageButtons)
{
if (spriteRenderer && spriteRenderer.gameObject)
Object.Destroy(spriteRenderer.gameObject);
}
PageButtons = null;
}
}
}
+3 -2
View File
@@ -72,6 +72,7 @@ internal static class LocateArrow
{
ArrowInfo arrowInfo = new(seer, locate);
List<ArrowInfo> removeList = new(LocateArrows.Keys.Where(k => k.Equals(arrowInfo)));
if (removeList.Count == 0) return;
removeList.ForEach(a => LocateArrows.Remove(a));
Utils.SendRPC(CustomRPC.Arrow, false, 2, seer, locate);
@@ -85,8 +86,8 @@ internal static class LocateArrow
public static void RemoveAllTarget(byte seer)
{
List<ArrowInfo> removeList = new(LocateArrows.Keys.Where(k => k.From == seer));
if (removeList.Count == 0) return;
removeList.ForEach(a => LocateArrows.Remove(a));
Utils.SendRPC(CustomRPC.Arrow, false, 3, seer);
Logger.Info($"Removed all locate arrows for: {seer} ({seer.GetPlayer()?.GetRealName()})", "LocateArrow");
}
@@ -129,7 +130,7 @@ internal static class LocateArrow
var update = false;
foreach (ArrowInfo arrowInfo in arrowList.ToArray())
foreach (ArrowInfo arrowInfo in arrowList)
{
Vector3 loc = arrowInfo.To;
+15 -7
View File
@@ -60,21 +60,29 @@ public static class NameNotifyManager
if (now == LastUpdate) return;
LastUpdate = now;
List<byte> toNotify = [];
List<byte> toNotify = null;
foreach ((byte id, Dictionary<string, long> notifies) in Notifies)
{
List<string> toRemove = [];
List<string> toRemove = null;
notifies.DoIf(x => x.Value <= now, x => toRemove.Add(x.Key));
notifies.DoIf(x => x.Value <= now, x =>
{
toRemove ??= [];
toRemove.Add(x.Key);
});
toRemove.ForEach(x => notifies.Remove(x));
if (toRemove.Count > 0) toNotify.Add(id);
if (toRemove != null)
{
toRemove.ForEach(x => notifies.Remove(x));
toNotify ??= [];
toNotify.Add(id);
}
}
if (toNotify.Count == 0 || !AmongUsClient.Instance.AmHost) return;
if (!AmongUsClient.Instance.AmHost) return;
toNotify.ToValidPlayers().ForEach(x => Utils.NotifyRoles(SpecifySeer: x, SpecifyTarget: x));
toNotify?.ToValidPlayers().Do(x => Utils.NotifyRoles(SpecifySeer: x, SpecifyTarget: x));
}
public static bool GetNameNotify(PlayerControl player, out string name)
+5 -3
View File
@@ -72,7 +72,8 @@ internal static class TargetArrow
{
ArrowInfo arrowInfo = new(seer, target);
List<ArrowInfo> removeList = new(TargetArrows.Keys.Where(k => k.Equals(arrowInfo)));
foreach (ArrowInfo a in removeList.ToArray()) TargetArrows.Remove(a);
if (removeList.Count == 0) return;
removeList.ForEach(a => TargetArrows.Remove(a));
Utils.SendRPC(CustomRPC.Arrow, true, 2, seer, target);
Logger.Info($"Removed target arrow: {seer} ({seer.GetPlayer()?.GetRealName()}) => {target} ({target.GetPlayer()?.GetRealName()})", "TargetArrow");
@@ -85,7 +86,8 @@ internal static class TargetArrow
public static void RemoveAllTarget(byte seer)
{
List<ArrowInfo> removeList = new(TargetArrows.Keys.Where(k => k.From == seer));
foreach (ArrowInfo arrowInfo in removeList.ToArray()) TargetArrows.Remove(arrowInfo);
if (removeList.Count == 0) return;
removeList.ForEach(a => TargetArrows.Remove(a));
Utils.SendRPC(CustomRPC.Arrow, true, 3, seer);
Logger.Info($"Removed all target arrows for {seer} ({seer.GetPlayer()?.GetRealName()})", "TargetArrow");
@@ -128,7 +130,7 @@ internal static class TargetArrow
var update = false;
foreach (ArrowInfo arrowInfo in arrowList.ToArray())
foreach (ArrowInfo arrowInfo in arrowList)
{
byte targetId = arrowInfo.To;
PlayerControl target = Utils.GetPlayerById(targetId);
+24 -13
View File
@@ -2549,19 +2549,30 @@ public static class Utils
public static PlayerControl GetPlayerById(int playerId, bool fast = true)
{
if (playerId is > byte.MaxValue or < byte.MinValue) return null;
if (fast && GameStates.InGame && Main.PlayerStates.TryGetValue((byte)playerId, out PlayerState state) && state.Player) return state.Player;
if (playerId == PlayerControl.LocalPlayer.PlayerId) return PlayerControl.LocalPlayer;
foreach (var pc in PlayerControl.AllPlayerControls)
try
{
if (pc.PlayerId == playerId)
return pc;
}
if (playerId == PlayerControl.LocalPlayer.PlayerId)
return PlayerControl.LocalPlayer;
return null;
byte id = (byte)playerId;
if (fast && GameStates.InGame &&
Main.PlayerStates.TryGetValue(id, out PlayerState state) &&
state.Player) { return state.Player; }
foreach (var pc in PlayerControl.AllPlayerControls)
{
if (pc.PlayerId == id)
return pc;
}
return null;
}
catch (Exception e)
{
ThrowException(e);
return null;
}
}
public static void SetupLongRoleDescriptions()
@@ -4534,8 +4545,8 @@ public static class Utils
packedWriter.WritePacked(AmongUsClient.Instance.GameId);
}
pc.SetChatVisible(visible, packedWriter);
messages++;
if (pc.SetChatVisible(visible, packedWriter))
messages++;
}
packedWriter.EndMessage();
+1 -1
View File
@@ -1170,7 +1170,7 @@ internal static class TaskPanelBehaviourPatch
{
Dictionary<byte, string> SummaryText3 = new Dictionary<byte, string>();
foreach (byte id in Main.PlayerStates.Keys.ToArray())
foreach (byte id in Main.PlayerStates.Keys)
{
string name = Main.AllPlayerNames[id].RemoveHtmlTags().Replace("\r\n", string.Empty);
var summary = $"{Utils.GetProgressText(id)} {Utils.ColorString(Main.PlayerColors[id], name)}";
+5 -2
View File
@@ -340,7 +340,7 @@ internal static class SetUpRoleTextPatch
{
LateTask.New(() =>
{
if (AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || lp == null) return;
if (AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || !lp) return;
lp.SetName(Main.AllPlayerNames[lp.PlayerId]);
}, 1f, "Reset Name For Modded Client");
}
@@ -1272,7 +1272,7 @@ internal static class IntroCutsceneDestroyPatch
try
{
System.Collections.Generic.List<PlayerControl> spectators = ChatCommands.Spectators.ToList().ToValidPlayers();
System.Collections.Generic.List<PlayerControl> spectators = ChatCommands.Spectators.ToValidPlayers().ToList();
if (Main.GM.Value) spectators.Add(PlayerControl.LocalPlayer);
spectators.ForEach(x =>
@@ -1374,6 +1374,9 @@ internal static class IntroCutsceneDestroyPatch
case CustomGameMode.StopAndGo:
StopAndGo.RoundTimer = Stopwatch.StartNew();
break;
case CustomGameMode.RoomRush:
RoomRush.GameStartDateTime = DateTime.Now;
break;
}
}
+6 -2
View File
@@ -769,7 +769,9 @@ internal static class MeetingHudStartPatch
if (MeetingStates.FirstMeeting && CustomRoles.Workaholic.RoleExist() && Workaholic.WorkaholicGiveAdviceAlive.GetBool() && !Workaholic.WorkaholicCannotWinAtDeath.GetBool() /* && !Options.GhostIgnoreTasks.GetBool()*/)
{
foreach (PlayerControl pc in Main.EnumerateAlivePlayerControls().Where(x => x.Is(CustomRoles.Workaholic)).ToArray()) Workaholic.WorkaholicAlive.Add(pc.PlayerId);
foreach (PlayerControl pc in Main.EnumerateAlivePlayerControls())
if (pc.Is(CustomRoles.Workaholic))
Workaholic.WorkaholicAlive.Add(pc.PlayerId);
List<string> workaholicAliveList = [];
workaholicAliveList.AddRange(Workaholic.WorkaholicAlive.Select(whId => Main.AllPlayerNames[whId]));
@@ -781,7 +783,9 @@ internal static class MeetingHudStartPatch
// Bait Notify
if (MeetingStates.FirstMeeting && CustomRoles.Bait.RoleExist() && Options.BaitNotification.GetBool())
{
foreach (PlayerControl pc in Main.EnumerateAlivePlayerControls().Where(x => x.Is(CustomRoles.Bait)).ToArray()) Main.BaitAlive.Add(pc.PlayerId);
foreach (PlayerControl pc in Main.EnumerateAlivePlayerControls())
if (pc.Is(CustomRoles.Bait))
Main.BaitAlive.Add(pc.PlayerId);
List<string> baitAliveList = [];
baitAliveList.AddRange(Main.BaitAlive.Select(whId => Main.AllPlayerNames[whId]));
+3 -3
View File
@@ -380,9 +380,9 @@ public static class TextBoxPatch
public static void OnMeetingStart()
{
PlaceHolderText?.transform.SetAsLastSibling();
CommandInfoText?.transform.SetAsLastSibling();
AdditionalInfoText?.transform.SetAsLastSibling();
if (PlaceHolderText) PlaceHolderText.transform.SetAsLastSibling();
if (CommandInfoText) CommandInfoText.transform.SetAsLastSibling();
if (AdditionalInfoText) AdditionalInfoText.transform.SetAsLastSibling();
}
[HarmonyPatch(typeof(TextBoxTMP), nameof(TextBoxTMP.Update))]
-1
View File
@@ -4700,7 +4700,6 @@
"ND_LimitMaximumDisastersAtOnce": "Limit Maximum Active Disasters at Once",
"ND_MaximumDisastersAtOnce": "Maximum Active Disasters at Once",
"ND_WhenLimitIsReached": "When the Limit is Reached",
"ND_LimitReachedOptions.OnlySpawnInstantDisasters": "Only Spawn Instant Disasters",
"ND_LimitReachedOptions.RemoveOldest": "Remove Oldest Disaster",
"ND_LimitReachedOptions.RemoveRandom": "Remove Random Disaster",
"ND_DisasterSpawnMode": "Disaster Spawn Mode",
+3 -2
View File
@@ -81,7 +81,7 @@ internal class Asthmatic : IAddon
{
if (Timers.Count == 0) return;
List<byte> toRemove = [];
List<byte> toRemove = null;
foreach ((byte id, Counter counter) in Timers)
{
@@ -89,6 +89,7 @@ internal class Asthmatic : IAddon
if (state.IsDead || !state.SubRoles.Contains(CustomRoles.Asthmatic) || state.MainRole == CustomRoles.Pestilence)
{
toRemove ??= [];
toRemove.Add(id);
continue;
}
@@ -96,7 +97,7 @@ internal class Asthmatic : IAddon
counter.Update();
}
if (toRemove.Count == 0) return;
if (toRemove == null) return;
foreach (byte id in toRemove)
{
@@ -94,16 +94,6 @@ public class Damocles : IAddon
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncDamoclesTimer, SendOption.Reliable);
writer.Write(playerId);
writer.Write(Timer[playerId]);
writer.Write(LastUpdate[playerId].ToString());
List<int> pev = PreviouslyEnteredVents.GetValueOrDefault(playerId, []);
writer.Write(pev.Count);
if (pev.Count > 0)
{
foreach (int vent in pev.ToArray())
writer.Write(vent);
}
AmongUsClient.Instance.FinishRpcImmediately(writer);
}
@@ -111,14 +101,6 @@ public class Damocles : IAddon
{
byte playerId = reader.ReadByte();
Timer[playerId] = reader.ReadInt32();
LastUpdate[playerId] = long.Parse(reader.ReadString());
int elements = reader.ReadInt32();
if (elements > 0)
{
for (var i = 0; i < elements; i++)
PreviouslyEnteredVents[playerId].Add(reader.ReadInt32());
}
}
public static void OnMurder(byte id)
+4 -9
View File
@@ -749,16 +749,11 @@ internal class Randomizer : RoleBase
{
long now = Utils.TimeStamp;
foreach (KeyValuePair<Effect, (long StartTimeStamp, int Duration)> item in effects.ToArray())
foreach (KeyValuePair<Effect, (long StartTimeStamp, int Duration)> item in effects.Where(item => item.Value.StartTimeStamp + item.Value.Duration < now).ToArray())
{
if (item.Value.StartTimeStamp + item.Value.Duration < now)
{
if (item.Key.IsVisionChangingEffect()) RevertVisionChangesForPlayer(pc, true);
if (item.Key.IsSpeedChangingEffect()) RevertSpeedChangesForPlayer(pc, true);
effects.Remove(item.Key);
}
if (item.Key.IsVisionChangingEffect()) RevertVisionChangesForPlayer(pc, true);
if (item.Key.IsSpeedChangingEffect()) RevertSpeedChangesForPlayer(pc, true);
effects.Remove(item.Key);
}
}
}
+7 -2
View File
@@ -134,7 +134,9 @@ internal class Tornado : RoleBase
float tornadoRange = TornadoRange.GetFloat();
int tornadoDuration = TornadoDuration.GetInt();
foreach (KeyValuePair<(Vector2 Location, string RoomName), long> tornado in Tornados.ToArray())
List<(Vector2 Location, string RoomName)> toRemove = null;
foreach (KeyValuePair<(Vector2 Location, string RoomName), long> tornado in Tornados)
{
if (FastVector2.DistanceWithinRange(tornado.Key.Location, pc.Pos(), tornadoRange))
{
@@ -148,11 +150,14 @@ internal class Tornado : RoleBase
if (tornado.Value + tornadoDuration < now)
{
Tornados.Remove(tornado.Key);
toRemove ??= [];
toRemove.Add(tornado.Key);
SendRPCAddTornado(false, tornado.Key.Location, tornado.Key.RoomName);
NotifyRoles(SpecifySeer: TornadoPC, SpecifyTarget: TornadoPC);
}
}
toRemove?.ForEach(x => Tornados.Remove(x));
}
else
{
+7 -2
View File
@@ -166,16 +166,21 @@ public class Druid : RoleBase
{
if (!GameStates.IsInTask || Triggers.Count <= 0 || PlayerIdList.Contains(pc.PlayerId)) return;
foreach (KeyValuePair<Vector2, string> trigger in Triggers.ToArray())
List<Vector2> toRemove = null;
foreach (KeyValuePair<Vector2, string> trigger in Triggers)
{
if (FastVector2.DistanceWithinRange(trigger.Key, pc.Pos(), 1.5f))
{
Triggers.Remove(trigger.Key);
toRemove ??= [];
toRemove.Add(trigger.Key);
DruidPC.Notify(string.Format(GetString("DruidTriggerTriggered"), GetFormattedRoomName(trigger.Value), GetFormattedVectorText(trigger.Key)));
SendRPCAddTrigger(false, DruidPC.PlayerId, trigger.Key);
CustomNetObject.Get(TriggerIds[trigger.Key])?.Despawn();
}
}
toRemove?.ForEach(x => Triggers.Remove(x));
}
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
+5 -7
View File
@@ -98,7 +98,7 @@ public class Scout : RoleBase
public static string GetTargetMark(PlayerControl seer, PlayerControl target)
{
return !(seer == null || target == null) && TrackerTarget.ContainsKey(seer.PlayerId) && TrackerTarget[seer.PlayerId].Contains(target.PlayerId) ? Utils.ColorString(seer.GetRoleColor(), "◀") : string.Empty;
return TrackerTarget.ContainsKey(seer.PlayerId) && TrackerTarget[seer.PlayerId].Contains(target.PlayerId) ? Utils.ColorString(seer.GetRoleColor(), "◀") : string.Empty;
}
public override bool OnVote(PlayerControl player, PlayerControl target)
@@ -124,7 +124,7 @@ public class Scout : RoleBase
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
if (seer == null || seer.PlayerId != TrackerId || target != null && seer.PlayerId != target.PlayerId || !TrackerTarget.ContainsKey(seer.PlayerId) || meeting || hud) return string.Empty;
if (seer.PlayerId != TrackerId || seer.PlayerId != target.PlayerId || !TrackerTarget.ContainsKey(seer.PlayerId) || meeting || hud) return string.Empty;
return TrackerTarget[seer.PlayerId].Aggregate(string.Empty, (current, trackTarget) => current + Utils.ColorString(CanGetColoredArrow.GetBool() ? Main.PlayerColors[trackTarget] : Color.white, TargetArrow.GetArrows(seer, trackTarget))) + LocateArrow.GetArrows(seer);
}
@@ -137,15 +137,13 @@ public class Scout : RoleBase
public static string GetArrowAndLastRoom(PlayerControl seer, PlayerControl target)
{
if (seer == null || target == null) return string.Empty;
Color roleColor = Utils.GetRoleColor(CustomRoles.Scout);
string text = Utils.ColorString(roleColor, TargetArrow.GetArrows(seer, target.PlayerId));
text += Utils.ColorString(roleColor, LocateArrow.GetArrows(seer));
PlainShipRoom room = Main.PlayerStates[target.PlayerId].LastRoom;
if (room == null)
if (!room)
text += Utils.ColorString(Color.gray, "@" + GetString("FailToTrack"));
else
text += Utils.ColorString(roleColor, "@" + GetString(room.RoomId.ToString()));
@@ -155,7 +153,7 @@ public class Scout : RoleBase
public static void OnPlayerDeath(PlayerControl player)
{
if (player == null) return;
if (!player) return;
foreach (KeyValuePair<byte, List<byte>> kvp in TrackerTarget)
{
@@ -178,7 +176,7 @@ public class Scout : RoleBase
{
PlayerControl pc = Utils.GetPlayerById(id);
if (pc == null || !pc.IsAlive())
if (!pc || !pc.IsAlive())
{
kvp.Value.Remove(id);
TargetArrow.Remove(kvp.Key, id);
@@ -41,9 +41,9 @@ public class PortalMaker : RoleBase
public override void OnPet(PlayerControl pc)
{
if (Marks.Count == 2)
if (Marks.Count >= 2)
{
if (!PetToRemovePortals.GetBool()) return;
if (!PetToRemovePortals.GetBool() && Marks.Count == 2) return;
Marks.Clear();
CustomNetObject.AllObjects.OfType<Portal>().Do(x => x.Despawn());
pc.Notify(Translator.GetString("MarksCleared"));
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using static EHR.Translator;
namespace EHR.Roles;
@@ -78,15 +77,15 @@ internal class Benefactor : RoleBase
}
else
{
foreach (byte benefactorId in TaskIndex.Keys.ToArray())
foreach ((byte benefactorId, List<int> taskIndexes) in TaskIndex)
{
if (TaskIndex[benefactorId].Contains(task.Index))
if (taskIndexes.Contains(task.Index))
{
PlayerControl benefactorPC = Utils.GetPlayerById(benefactorId);
if (benefactorPC == null) continue;
if (!benefactorPC) continue;
player.Notify(GetString("BenefactorTargetGotShieldNotify"));
TaskIndex[benefactorId].Remove(task.Index);
taskIndexes.Remove(task.Index);
ShieldedPlayers.Add(playerId);
LateTask.New(() => ShieldedPlayers.Remove(playerId), ShieldDuration.GetInt());
Logger.Info($"{player.GetAllRoleName()} got a shield because the task was marked by {benefactorPC.GetNameWithRole()}", "Benefactor");
+2 -1
View File
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using AmongUs.GameOptions;
namespace EHR.Roles;
@@ -92,7 +93,7 @@ public class Crusader : RoleBase
return;
}
keepGameGoing = ForCrusade.ToValidPlayers().Exists(x => x.IsAlive());
keepGameGoing = ForCrusade.ToValidPlayers().Any(x => x.IsAlive());
countsAs = 1;
}
}
@@ -56,7 +56,7 @@ public class TimeManager : RoleBase
{
var sec = 0;
foreach (byte playerId in PlayerIdList.ToArray())
foreach (byte playerId in PlayerIdList)
{
if (Utils.GetPlayerById(playerId).Is(CustomRoles.Madmate))
sec -= AdditionalTime(playerId);
+4 -4
View File
@@ -123,12 +123,12 @@ internal class Haunter : IGhostRole
AllHauntedPlayers.UnionWith(targets);
var targetPcs = targets.ToValidPlayers();
var targetPcs = targets.ToValidPlayers().ToArray();
targetPcs.ForEach(x => x.Notify(Translator.GetString("HaunterRevealedYou"), 10f));
WarnedImps.ToValidPlayers().ForEach(x => x.Notify(Translator.GetString("HaunterFinishedTasks"), 10f));
targetPcs.Do(x => x.Notify(Translator.GetString("HaunterRevealedYou"), 10f));
WarnedImps.ToValidPlayers().Do(x => x.Notify(Translator.GetString("HaunterFinishedTasks"), 10f));
targetPcs.ForEach(x => Utils.NotifyRoles(SpecifyTarget: x));
targetPcs.Do(x => Utils.NotifyRoles(SpecifyTarget: x));
}
public void Update(PlayerControl pc)
@@ -76,7 +76,7 @@ public class Anonymous : RoleBase
public static void AddDeadBody(PlayerControl target)
{
if (target != null && !DeadBodyList.Contains(target.PlayerId)) DeadBodyList.Add(target.PlayerId);
if (target && !DeadBodyList.Contains(target.PlayerId)) DeadBodyList.Add(target.PlayerId);
}
public override bool OnShapeshift(PlayerControl pc, PlayerControl ssTarget, bool shapeshifting)
@@ -87,12 +87,14 @@ public class Anonymous : RoleBase
var targetId = byte.MaxValue;
foreach (byte db in DeadBodyList.ToArray())
foreach (byte db in DeadBodyList)
{
PlayerControl dp = Utils.GetPlayerById(db);
if (dp == null || dp.GetRealKiller() == null) continue;
if (!dp) continue;
PlayerControl realKiller = dp.GetRealKiller();
if (!realKiller) continue;
if (dp.GetRealKiller().PlayerId == pc.PlayerId) targetId = db;
if (realKiller.PlayerId == pc.PlayerId) targetId = db;
}
if (targetId == byte.MaxValue && DeadBodyList.Count > 0) targetId = DeadBodyList.RandomElement();
@@ -107,7 +107,7 @@ public class Lightning : RoleBase
public static bool CheckLightningMurder(PlayerControl killer, PlayerControl target, bool force = false)
{
if (killer == null || target == null || (!killer.Is(CustomRoles.Lightning) && !force)) return false;
if (!killer.Is(CustomRoles.Lightning) && !force) return false;
if (IsGhost(target)) return false;
@@ -155,11 +155,11 @@ public class Lightning : RoleBase
List<byte> deList = [];
foreach (byte ghost in GhostPlayer.ToArray())
foreach (byte ghost in GhostPlayer)
{
PlayerControl gs = Utils.GetPlayerById(ghost);
if (gs == null || !gs.IsAlive() || gs.Data.Disconnected)
if (!gs || !gs.IsAlive() || gs.Data.Disconnected)
{
//deList.Add(gs.PlayerId); // This will always result in a null reference exception
continue;
@@ -181,7 +181,7 @@ public class Lightning : RoleBase
{
GhostPlayer.RemoveAll(deList.Contains);
foreach (byte gs in deList.ToArray())
foreach (byte gs in deList)
{
SendRPC(gs);
Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(gs));
@@ -193,10 +193,10 @@ public class Lightning : RoleBase
{
if (!(IsEnable || CustomRoles.Lightning.IsEnable())) return;
foreach (byte ghost in GhostPlayer.ToArray())
foreach (byte ghost in GhostPlayer)
{
PlayerControl gs = Utils.GetPlayerById(ghost);
if (gs == null) continue;
if (!gs) continue;
CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Quantization, gs.PlayerId);
gs.SetRealKiller(RealKiller[gs.PlayerId]);
@@ -63,7 +63,7 @@ public sealed class Stealth : RoleBase
private IEnumerable<PlayerControl> FindPlayersInSameRoom(PlayerControl killedPlayer)
{
PlainShipRoom room = killedPlayer.GetPlainShipRoom();
if (room == null) return null;
if (!room) return null;
Collider2D roomArea = room.roomArea;
SystemTypes roomName = room.RoomId;
+11 -28
View File
@@ -123,13 +123,6 @@ public class Mafioso : RoleBase
writer.Write(MafiosoId);
writer.Write(Tier);
writer.Write(XP);
writer.Write(PreviouslyUsedVents.Count);
if (PreviouslyUsedVents.Count > 0)
{
foreach (int vent in PreviouslyUsedVents.ToArray())
writer.Write(vent);
}
AmongUsClient.Instance.FinishRpcImmediately(writer);
}
@@ -138,14 +131,6 @@ public class Mafioso : RoleBase
{
Tier = reader.ReadInt32();
XP = reader.ReadInt32();
PreviouslyUsedVents.Clear();
int elements = reader.ReadInt32();
if (elements > 0)
{
for (var i = 0; i < elements; i++)
PreviouslyUsedVents.Add(reader.ReadInt32());
}
}
private void SendRPCSyncPistolCD()
@@ -156,7 +141,6 @@ public class Mafioso : RoleBase
writer.Write(MafiosoId);
writer.Write(Pistol1CD);
writer.Write(Pistol2CD);
writer.Write(lastUpdate.ToString());
AmongUsClient.Instance.FinishRpcImmediately(writer);
}
@@ -164,7 +148,6 @@ public class Mafioso : RoleBase
{
Pistol1CD = reader.ReadInt32();
Pistol2CD = reader.ReadInt32();
lastUpdate = long.Parse(reader.ReadString());
}
public override void OnFixedUpdate(PlayerControl pc)
@@ -201,21 +184,21 @@ public class Mafioso : RoleBase
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
if (!GameStates.IsInTask || target == null || killer == null || !killer.Is(CustomRoles.Mafioso) || Tier < 3 || !IsEnable) return true;
if (!GameStates.IsInTask || !killer.Is(CustomRoles.Mafioso) || Tier < 3 || !IsEnable) return true;
if (Pistol1CD > 0 && Pistol2CD > 0) return false;
int KCD = Tier >= 4 ? (int)Math.Round(AdjustedDefaultKillCooldown) : (int)Math.Round(AdjustedDefaultKillCooldown * 1.5);
KCD++;
int kcd = Tier >= 4 ? (int)Math.Round(AdjustedDefaultKillCooldown) : (int)Math.Round(AdjustedDefaultKillCooldown * 1.5);
kcd++;
if (Pistol1CD <= 0)
{
Pistol1CD = KCD;
Pistol1CD = kcd;
NotifyRoles(SpecifySeer: killer, SpecifyTarget: killer);
}
else if (Pistol2CD <= 0)
{
Pistol2CD = KCD;
Pistol2CD = kcd;
NotifyRoles(SpecifySeer: killer, SpecifyTarget: killer);
}
@@ -224,13 +207,13 @@ public class Mafioso : RoleBase
LateTask.New(() =>
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalse ---- Can be null since it's a task that completes later
if (target != null && target.IsAlive() && GameStates.IsInTask) target.Suicide(PlayerState.DeathReason.Kill, killer);
if (target && target.IsAlive() && GameStates.IsInTask) target.Suicide(PlayerState.DeathReason.Kill, killer);
}, Delay.GetInt(), "Mafioso Tier 5 Kill Delay");
return false;
}
if (Pistol1CD > 1 && Pistol2CD > 1) LateTask.New(() => { killer.SetKillCooldown(Math.Min(Pistol1CD, Pistol2CD) - 1); }, 0.1f, "Mafioso SetKillCooldown");
if (Pistol1CD > 1 && Pistol2CD > 1) LateTask.New(() => killer.SetKillCooldown(Math.Min(Pistol1CD, Pistol2CD) - 1), 0.1f, "Mafioso SetKillCooldown");
return true;
}
@@ -240,10 +223,10 @@ public class Mafioso : RoleBase
if (!IsEnable) return;
PreviouslyUsedVents.Clear();
int KCD = Tier >= 4 ? (int)Math.Round(AdjustedDefaultKillCooldown) : (int)Math.Round(AdjustedDefaultKillCooldown * 1.5);
KCD++;
Pistol1CD = KCD;
Pistol2CD = KCD;
int kcd = Tier >= 4 ? (int)Math.Round(AdjustedDefaultKillCooldown) : (int)Math.Round(AdjustedDefaultKillCooldown * 1.5);
kcd++;
Pistol1CD = kcd;
Pistol2CD = kcd;
lastUpdate = TimeStamp;
SendRPC();
SendRPCSyncPistolCD();
@@ -115,8 +115,8 @@ public class EvilTracker : RoleBase
public static void OnAnyoneMurder(PlayerControl killer, PlayerControl target)
{
if (CanSeeKillFlash && killer != null && killer.Is(CustomRoleTypes.Impostor) && killer != target && !PlayerIdList.Contains(killer.PlayerId))
PlayerIdList.ToValidPlayers().ForEach(x => x.KillFlash());
if (CanSeeKillFlash && killer.Is(CustomRoleTypes.Impostor) && killer != target && !PlayerIdList.Contains(killer.PlayerId))
PlayerIdList.ToValidPlayers().Do(x => x.KillFlash());
}
public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting)
@@ -227,7 +227,7 @@ public class EvilTracker : RoleBase
string text = Utils.ColorString(Palette.ImpostorRed, TargetArrow.GetArrows(seer, target.PlayerId));
PlainShipRoom room = Main.PlayerStates[target.PlayerId].LastRoom;
if (room == null)
if (!room)
text += Utils.ColorString(Color.gray, "@" + GetString("FailToTrack"));
else
text += Utils.ColorString(Palette.ImpostorRed, "@" + GetString(room.RoomId.ToString()));
+4 -4
View File
@@ -107,18 +107,18 @@ public class Devourer : RoleBase
return false;
}
public static void OnDevourerDied(byte Devourer)
public static void OnDevourerDied(byte devourer)
{
if (Main.PlayerStates[Devourer].Role is not Devourer { IsEnable: true } dv) return;
if (Main.PlayerStates[devourer].Role is not Devourer { IsEnable: true } dv) return;
foreach (byte player in dv.PlayerSkinsCosumed.ToArray())
foreach (byte player in dv.PlayerSkinsCosumed)
{
Camouflage.PlayerSkins[player] = OriginalPlayerSkins[player];
if (!Camouflage.IsCamouflage)
{
PlayerControl pc = Main.EnumerateAlivePlayerControls().FirstOrDefault(a => a.PlayerId == player);
if (pc == null) continue;
if (!pc) continue;
SetSkin(pc, OriginalPlayerSkins[player]);
}
+3 -1
View File
@@ -76,7 +76,9 @@ internal class Workaholic : RoleBase
{
Logger.Info("Workaholic Tasks Finished", "Workaholic");
RPC.PlaySoundRPC(player.PlayerId, Sounds.KillSound);
foreach (PlayerControl pc in Main.EnumerateAlivePlayerControls().Where(pc => pc.PlayerId != player.PlayerId).ToArray()) pc.Suicide(pc.PlayerId == player.PlayerId ? PlayerState.DeathReason.Overtired : PlayerState.DeathReason.Ashamed, player);
foreach (PlayerControl pc in Main.EnumerateAlivePlayerControls())
pc.Suicide(pc.PlayerId == player.PlayerId ? PlayerState.DeathReason.Overtired : PlayerState.DeathReason.Ashamed, player);
CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Workaholic);
CustomWinnerHolder.WinnerIds.Add(player.PlayerId);
+1 -1
View File
@@ -81,7 +81,7 @@ public class HeadHunter : RoleBase
MessageWriter writer = Utils.CreateRPC(CustomRPC.SyncHeadHunter);
writer.Write(HeadHunterId);
writer.Write(Targets.Count);
foreach (byte target in Targets.ToArray()) writer.Write(target);
foreach (byte target in Targets) writer.Write(target);
Utils.EndRPC(writer);
}
+20 -11
View File
@@ -349,26 +349,35 @@ public class Magician : RoleBase
if (Bombs.Count > 0)
{
foreach (KeyValuePair<Vector2, long> bomb in Bombs.Where(bomb => bomb.Value + BombDelay.GetInt() < now).ToArray())
List<Vector2> toRemove = null;
foreach (KeyValuePair<Vector2, long> bomb in Bombs)
{
foreach (PlayerControl tg in FastVector2.GetPlayersInRange(bomb.Key, BombRadius.GetFloat()))
if (bomb.Value + BombDelay.GetInt() < now)
{
if (tg.PlayerId == pc.PlayerId)
foreach (PlayerControl tg in FastVector2.GetPlayersInRange(bomb.Key, BombRadius.GetFloat()))
{
LateTask.New(() =>
if (tg.PlayerId == pc.PlayerId)
{
if (!GameStates.IsEnded) pc.Suicide(PlayerState.DeathReason.Bombed);
}, 0.5f, "Magician Bomb Suicide");
continue;
LateTask.New(() =>
{
if (!GameStates.IsEnded)
pc.Suicide(PlayerState.DeathReason.Bombed);
}, 0.5f, "Magician Bomb Suicide");
continue;
}
tg.Suicide(PlayerState.DeathReason.Bombed, pc);
}
tg.Suicide(PlayerState.DeathReason.Bombed, pc);
toRemove ??= [];
toRemove.Add(bomb.Key);
pc.Notify(GetString("MagicianBombExploded"));
}
Bombs.Remove(bomb.Key);
pc.Notify(GetString("MagicianBombExploded"));
}
toRemove?.ForEach(x => Bombs.Remove(x));
var sb = new StringBuilder();
long[] list = [.. Bombs.Values];
foreach (long x in list) sb.Append(string.Format(GetString("MagicianBombExlodesIn"), BombDelay.GetInt() - (now - x) + 1));
+1 -1
View File
@@ -85,7 +85,7 @@ public class Pelican : RoleBase
if (playerId != byte.MaxValue)
{
writer.Write(EatenList[playerId].Count);
foreach (byte el in EatenList[playerId].ToArray()) writer.Write(el);
foreach (byte el in EatenList[playerId]) writer.Write(el);
}
AmongUsClient.Instance.FinishRpcImmediately(writer);
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using AmongUs.GameOptions;
using Hazel;
@@ -108,9 +109,9 @@ public class SoulCollector : RoleBase
{
var sender = CustomRpcSender.Create("SoulCollector Post Exile", SendOption.Reliable);
sender.StartMessage();
List<PlayerControl> toExile = ToExile.ToValidPlayers();
PlayerControl[] toExile = ToExile.ToValidPlayers().ToArray();
ToExile = [];
toExile.ForEach(x =>
toExile.Do(x =>
{
Main.AllPlayerSpeed[x.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod);
sender.RpcExiled(x, autoStartRpc: false);
+3 -2
View File
@@ -143,7 +143,7 @@ public class Spider : RoleBase
public override void OnFixedUpdate(PlayerControl pc)
{
long now = Utils.TimeStamp;
List<Vector2> toRemove = [];
List<Vector2> toRemove = null;
foreach ((Vector2 pos, Dictionary<byte, long> trapped) in Webs)
{
@@ -152,6 +152,7 @@ public class Spider : RoleBase
if (trapped.Values.Min() <= now)
{
toRemove ??= [];
toRemove.Add(pos);
LateTask.New(() =>
{
@@ -165,7 +166,7 @@ public class Spider : RoleBase
}
}
toRemove.ForEach(x =>
toRemove?.ForEach(x =>
{
Webs.Remove(x);
LocateArrow.Remove(pc.PlayerId, x);