Author SHA1 Message Date
Renovate Bot a53a1fae26 Update dependency Microsoft.NET.Test.Sdk to v18 2026-08-14 10:08:20 +00:00
code002lover f437fe9bae allow networkID as playername arg in SetCClass 2025-07-14 23:35:39 +02:00
code002lover ddb192a278 implement sane default extendedclass 2025-07-14 23:30:34 +02:00
code002lover 477dc4c297 change over more parts to the new system 2025-07-14 23:29:49 +02:00
code002lover 43f66370f3 WIP refactor v3 2025-07-14 09:38:20 +02:00
code002lover ccaf44d50b refactor config handling 2025-07-14 08:11:10 +02:00
code002lover d53d71c6d9 improve bloodfueled & serpents 2025-07-14 03:35:11 +02:00
code002lover f839619464 finish bloodfueled 2025-07-13 04:45:03 +02:00
code002lover c2a232d432 add deploy script 2025-07-13 03:37:30 +02:00
HoherGeist 017d966b83 Merge branch 'master' of https://git.ipost.rocks/code002lover/SecretPluginLaboratories 2025-07-13 03:36:27 +02:00
HoherGeist 6810309b46 Changes by code002lover
Blood Fueled Additions -HoherGeist
2025-07-13 03:35:31 +02:00
code002lover 6d620dfd85 properly add new effect 2025-07-13 02:32:41 +02:00
HoherGeist 2f483ca113 refactor + lessen stamina usage 2025-07-12 23:02:07 +02:00
HoherGeist a8063216cc Changes by code002lover 2025-07-12 22:53:13 +02:00
HoherGeist 14e32bc921 +Blood Fueled Added 2025-07-12 22:40:11 +02:00
code002lover 78fa56dce9 various fixes + new custom class shadowmancer 2025-07-12 20:18:02 +02:00
code002lover 0615f8aeea do a lot ig 2025-07-03 22:05:38 +02:00
code002lover 0aee847089 idk man 2025-06-09 22:13:12 +02:00
code002lover 326b99c464 various thingies 2025-06-08 00:54:06 +02:00
code002lover 67e3d6ceaa Refactor 2025-06-08 00:54:06 +02:00
code002lover 73a4da1edd Add Template, more custom classes & fix issues 2025-06-08 00:54:06 +02:00
Renovate Botandcode002lover c9bee028d9 Update dependency NUnit.Analyzers to 4.8.1 2025-06-05 17:01:11 +02:00
Renovate Bot 38c660a24f Update dependency Microsoft.NET.Test.Sdk to 17.14.1 2025-06-05 00:07:36 +00:00
Renovate Botandcode002lover 20583ea9f9 Update dependency coverlet.collector to 6.0.4 2025-06-05 01:20:22 +02:00
code002lover d3dbd1ec9d Switch to MEC 2025-06-05 01:19:33 +02:00
code002lover 3e51b1a6c8 delete invis unicode char 2025-06-05 01:16:12 +02:00
63 changed files with 4169 additions and 443 deletions
+13
View File
@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"csharpier": {
"version": "1.0.2",
"commands": [
"csharpier"
],
"rollForward": false
}
}
}
+2
View File
@@ -4,3 +4,5 @@ obj/
*.user
*.dll
fuchsbau/
testbau/
**/target/
+21 -3
View File
@@ -1,4 +1,4 @@
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Wrappers;
@@ -27,8 +27,26 @@ public class AfkSwap : Plugin
public override string Description => "Swaps AFK players with spectators after one minute.";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
PlayerEvents.Spawned += OnPlayerSpawned;
Timing.RunCoroutine(CheckAfkPlayers());
@@ -80,7 +98,7 @@ public class AfkSwap : Plugin
continue;
}
if (!_playerPositions[playerTime.Key].Equals(playerTime.Key.Position))
if ((_playerPositions[playerTime.Key] - playerTime.Key.Position).sqrMagnitude > 2)
{
_playerSpawnTimes.Remove(playerTime.Key);
_playerPositions.Remove(playerTime.Key);
@@ -131,4 +149,4 @@ public class AfkSwap : Plugin
afkPlayer.SendBroadcast($"You were swapped with {randomSpectator.DisplayName} due to inactivity.", 10);
randomSpectator.SendBroadcast($"You were swapped with {afkPlayer.DisplayName} due to them being AFK.", 5);
}
}
}
@@ -1,6 +1,7 @@
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Loader.Features.Plugins;
namespace CandySetting;
@@ -15,8 +16,26 @@ public class CandySetting : Plugin
public int MaxUses { get; set; } = 6;
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
PlayerEvents.InteractingScp330 += TakingCandy;
}
+36 -19
View File
@@ -1,4 +1,5 @@
using LabApi.Events.Arguments.PlayerEvents;
using CustomClasses;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Console;
@@ -17,9 +18,26 @@ public class CuffedFrenemies : Plugin
public override string Description => "Cuff your enemies";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
Logger.Debug("Loading");
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
PlayerEvents.Cuffed += OnCuff;
}
@@ -34,27 +52,26 @@ public class CuffedFrenemies : Plugin
if (ev.Target.Team == ev.Player.Team)
{
Logger.Debug("Same team, not changing role");
return;
}
if (ev.Target.Team is Team.SCPs or Team.Dead) return;
var newRole = ev.Target.Team == Team.ChaosInsurgency ? RoleTypeId.NtfPrivate : RoleTypeId.ChaosConscript;
if (SerpentsHandManager.IsSerpentsHand(ev.Player))
{
SerpentsHandManager.PreSpawn(ev.Target);
return;
}
if (ev.Target.Role == RoleTypeId.Tutorial)
{
return;
}
var newRole = ev.Player.Team is Team.ChaosInsurgency or Team.ClassD ? RoleTypeId.ChaosConscript : RoleTypeId.NtfPrivate;
Logger.Debug($"Setting role to {newRole}");
var newItems = new List<Item>();
ev.Target.Items.CopyTo(newItems);
newItems.Reverse();
var newPos = ev.Target.Position;
ev.Target.Inventory.UserInventory.Items.Clear();
ev.Target.SetRole(newRole);
ev.Target.ClearItems();
foreach (var newItem in newItems) ev.Target.Inventory.UserInventory.Items.Add(newItem.Serial, newItem.Base);
ev.Target.Position = newPos;
ev.Target.SetRole(newRole, RoleChangeReason.ItemUsage, RoleSpawnFlags.None);
}
}
}
+4
View File
@@ -34,4 +34,8 @@
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CustomClasses\CustomClasses.csproj" />
</ItemGroup>
</Project>
+150
View File
@@ -0,0 +1,150 @@
using CustomPlayerEffects;
using InventorySystem.Items.Usables.Scp330;
using LabApi.Events.Handlers;
using LabApi.Features.Wrappers;
using MEC;
using PlayerRoles.FirstPersonControl;
using PlayerRoles.PlayableScps.Scp939;
using static LabApi.Features.Wrappers.Server;
using Logger = LabApi.Features.Console.Logger;
using Random = System.Random;
namespace CustomClasses;
public class DisableStaminaRegenEffect : CustomPlayerEffect, IStaminaModifier
{
public bool StaminaModifierActive => IsEnabled;
public float StaminaUsageMultiplier => 1;
public float StaminaRegenMultiplier => 0;
public bool SprintingDisabled => false;
public override EffectClassification Classification => EffectClassification.Negative;
}
public class BloodFueledStaminaEffect : CustomPlayerEffect, IStaminaModifier
{
public bool StaminaModifierActive => IsEnabled;
public float StaminaUsageMultiplier => 0.2f;
public float StaminaRegenMultiplier => 1;
public bool SprintingDisabled => false;
public override EffectClassification Classification => EffectClassification.Positive;
}
public class BloodFueledManager
{
public static bool IsBloodFueled(Player player)
{
try
{
return player.CustomInfo.Contains("Blood Fueled");
}
catch
{
return false;
}
}
public BloodFueledManager()
{
PlayerEvents.Hurt += ev =>
{
if (ev.DamageHandler is not Scp939DamageHandler damageHandler) return;
var attacker = Player.Get(damageHandler.Attacker.Hub);
if (attacker == null) return;
if (!IsBloodFueled(attacker)) return;
var isAffectedByBloodCloud = Scp939AmnesticCloudInstance.ActiveInstances.Where(x=>x.Owner && x.Owner == ev.Attacker?.ReferenceHub).Any(x=>x.AffectedPlayers.Contains(ev.Player.ReferenceHub));
if (isAffectedByBloodCloud)
{
attacker.Heal(10);
attacker.StaminaRemaining += 0.1f;
}
if (ev.Player.Health <= 0)
{
attacker.Heal(25);
attacker.StaminaRemaining += 0.2f;
return;
}
attacker.Heal(15);
attacker.StaminaRemaining += 0.1f;
};
Timing.RunCoroutine(DrainBlood());
PlayerEvents.EnteringHazard += ev =>
{
if (ev.Hazard is not AmnesticCloudHazard amnesticCloud) return;
if (amnesticCloud.Owner == null || !IsBloodFueled(amnesticCloud.Owner)) return;
ev.Player.EnableEffect<Invigorated>(1, float.PositiveInfinity);
};
PlayerEvents.StayingInHazard += ev =>
{
if (ev.Hazard is not AmnesticCloudHazard amnesticCloud) return;
if (amnesticCloud.Owner == null || !IsBloodFueled(amnesticCloud.Owner)) return;
foreach (var affectedPlayer in ev.AffectedPlayers)
{
// ReSharper disable once PossibleLossOfFraction
affectedPlayer.Heal(10 / MaxTps);
}
};
PlayerEvents.LeavingHazard += ev =>
{
if (ev.Hazard is not AmnesticCloudHazard amnesticCloud) return;
if (amnesticCloud.Owner == null || !IsBloodFueled(amnesticCloud.Owner)) return;
ev.Player.DisableEffect<Invigorated>();
};
}
public static IEnumerator<float> DrainBlood()
{
while (true)
{
yield return Timing.WaitForSeconds(1);
foreach (var player in Player.ReadyList.Where(IsBloodFueled))
{
if (player.StaminaRemaining <= 0f)
{
player.Health = Math.Min(Math.Max(player.Health - 50, 500), player.Health);
continue;
}
player.StaminaRemaining -= 0.005f;
if (player.MaxHealth <= player.Health) continue;
player.Heal(5);
player.StaminaRemaining -= 0.001f;
}
}
// ReSharper disable once IteratorNeverReturns
}
}
public class BloodFueledHandler : CustomClassHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
player.MaxHumeShield = 0;
player.HumeShield = 0;
player.MaxHealth = 3500;
player.Health = 3500;
player.EnableEffect<DisableStaminaRegenEffect>(1, float.PositiveInfinity);
player.EnableEffect<BloodFueledStaminaEffect>(1, float.PositiveInfinity);
}
public override void SendSpawnMessage(Player player, CustomClassConfig config)
{
player.SendBroadcast("You are SCP-939-<color=#C50000>Blood Fueled</color> \n Your stamina bar has been replaced by a <color=#C50000>blood meter</color>. \n You refill it by <color=#C50000>damaging</color> or <color=#C50000>killing</color> Humans. \n <color=#C50000><b>Don't let it run out.</color></b>", CustomClasses.BroadcastDuration);
}
}
File diff suppressed because it is too large Load Diff
+12
View File
@@ -26,12 +26,24 @@
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="Pooling">
<HintPath>..\dependencies\Pooling.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Immutable">
<HintPath>..\dependencies\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
+53
View File
@@ -0,0 +1,53 @@
using CustomPlayerEffects;
using LabApi.Features.Wrappers;
using Mirror;
using UnityEngine;
using UnityEngine.SceneManagement;
using Logger = LabApi.Features.Console.Logger;
namespace CustomClasses;
public abstract class CustomPlayerEffect : StatusEffectBase
{
private static bool _isLoaded;
public Player Owner { get; private set; } = null!;
protected override void Start()
{
Owner = Player.Get(Hub);
base.Start();
}
public override string ToString() => $"{GetType().Name}: Owner ({Owner}) - Intensity ({Intensity}) - Duration {Duration}";
internal static void Initialize()
{
SceneManager.sceneLoaded += (_, _) =>
{
if (_isLoaded)
return;
_isLoaded = true;
Type[] toLoad =
[
typeof(DisableStaminaRegenEffect),
typeof(BloodFueledStaminaEffect)
];
var playerEffects = NetworkManager.singleton.playerPrefab.GetComponent<ReferenceHub>().playerEffectsController.effectsGameObject.transform;
foreach (var type in toLoad)
{
if (!typeof(StatusEffectBase).IsAssignableFrom(type))
{
Logger.Error($"[CustomPlayerEffect.Initialize] {type.FullName} is not a valid StatusEffectBase and thus could not be registered!");
return;
}
// register effect into prefab
new GameObject(type.Name, type).transform.parent = playerEffects;
}
};
}
}
+152
View File
@@ -0,0 +1,152 @@
using CustomPlayerEffects;
using LabApi.Events.Arguments.Scp049Events;
using LabApi.Events.Handlers;
using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using MEC;
using PlayerRoles;
using PlayerRoles.PlayableScps.Scp049;
using PlayerRoles.PlayableScps.Scp106;
namespace CustomClasses;
public class NegromancerHandler : CustomClassHandler
{
public override void SendSpawnMessage(Player player, CustomClassConfig config)
{
player.SendBroadcast("You are the <color=#6e2e99>Negromancer</color>! Revived players become your <color=#3c1361>Shadow</color>.", CustomClasses.BroadcastDuration);
}
}
public class NegromancerManager
{
private readonly CustomClasses _plugin;
public static bool IsNegromancer(Player player) => player.CustomInfo.Contains("Shadowmancer");
public static bool IsShadow(Player player) => player.CustomInfo.Contains("Shadow") && !IsNegromancer(player);
public NegromancerManager(CustomClasses plugin)
{
_plugin = plugin;
Scp049Events.ResurrectedBody += OnScp049ResurrectedBody;
Timing.RunCoroutine(HealNearbyShadows());
Scp106Events.TeleportingPlayer += ev =>
{
if (!IsShadow(ev.Player)) return;
ev.IsAllowed = false;
ev.Target.EnableEffect<CardiacArrest>(1, float.PositiveInfinity);
ev.Target.Damage(40f, ev.Player);
ev.Player.SendHitMarker();
};
Scp106Events.UsingHunterAtlas += ev =>
{
if (!IsShadow(ev.Player)) return;
ev.IsAllowed = false;
var position = ev.DestinationPosition;
var room = Room.GetRoomAtPosition(position);
if (room?.LightController == null)
{
return;
}
var stat = ev.Player.GetStatModule<VigorStat>();
stat.CurValue = 0;
room.LightController.FlickerLights(3);
};
Scp106Events.ChangingSubmersionStatus += ev =>
{
if (!IsShadow(ev.Player)) return;
ev.IsAllowed = false;
var stat = ev.Player.GetStatModule<VigorStat>();
ev.Player.DisableEffect<MovementBoost>();
var scaled = stat.CurValue * 40f;
var scaledByte = (byte)scaled;
if(scaledByte < 15) scaledByte = 15;
Logger.Debug($"Scaled {stat.CurValue} to {scaledByte}");
ev.Player.EnableEffect<MovementBoost>(scaledByte, 20);
stat.CurValue = 0;
Timing.CallDelayed(10, () =>
{
ev.Player.DisableEffect<MovementBoost>();
ev.Player.EnableEffect<MovementBoost>(10, float.PositiveInfinity);
});
};
Scp106Events.ChangingVigor += ev =>
{
if (!IsShadow(ev.Player)) return;
var delta = ev.Value - ev.OldValue;
delta *= 0.5f;
ev.Value = ev.OldValue + delta;
};
}
private static IEnumerator<float> HealNearbyShadows()
{
while (true)
{
yield return Timing.WaitForSeconds(1);
try
{
Player.ReadyList.Where(IsNegromancer).Where(x =>
{
var scp = x.RoleBase as Scp049Role;
if (scp == null)
{
Logger.Error("Negromancer has no Scp049Role");
return false;
}
scp.SubroutineModule.TryGetSubroutine(out Scp049CallAbility ability);
if (ability) return ability.IsMarkerShown;
Logger.Error("Negromancer has no Scp049CallAbility");
return false;
}).ToList().ForEach(player =>
{
Player.ReadyList.Where(IsShadow).Where(x =>
{
var distance = (x.Position - player.Position).SqrMagnitudeIgnoreY();
return distance < 64;
}
).ToList().ForEach(x => x.Heal(10));
});
}
catch (Exception e)
{
// ignored
}
}
// ReSharper disable once IteratorNeverReturns
}
private void OnScp049ResurrectedBody(Scp049ResurrectedBodyEventArgs ev)
{
var classManager = _plugin.ClassManager;
if (classManager == null ||
!IsNegromancer(ev.Player)) return;
ev.Target.SetRole(RoleTypeId.Scp106, RoleChangeReason.Respawn, RoleSpawnFlags.None);
classManager.ForceSpawn(ev.Target, classManager.GetConfig<NegromancerShadowConfig>(), typeof(NegromancerShadowConfig), null);
}
}
+17
View File
@@ -0,0 +1,17 @@
using LabApi.Features.Wrappers;
namespace CustomClasses;
public static class PlayerExtensions
{
public static string GetExtendedClass(this Player player)
{
foreach (var classManagerConfig in CustomClasses.Instance.ClassManager.Configs.Where(classManagerConfig => player.CustomInfo == classManagerConfig.Value.FullCustomInfo &&
!classManagerConfig.Value.Name.IsEmpty()))
{
return classManagerConfig.Value.Name;
}
return player.Role.ToString();
}
}
+324
View File
@@ -0,0 +1,324 @@
using AdminToys;
using CommandSystem;
using CustomPlayerEffects;
using HintServiceMeow.Core.Enum;
using HintServiceMeow.Core.Models.Hints;
using HintServiceMeow.Core.Utilities;
using Interactables.Interobjects.DoorUtils;
using InventorySystem.Items.Firearms.Modules;
using LabApi.Events.Handlers;
using LabApi.Features.Permissions;
using LabApi.Features.Wrappers;
using MEC;
using PlayerRoles;
using UnityEngine;
using LightSourceToy = LabApi.Features.Wrappers.LightSourceToy;
using Logger = LabApi.Features.Console.Logger;
using PrimitiveObjectToy = LabApi.Features.Wrappers.PrimitiveObjectToy;
using Random = System.Random;
namespace CustomClasses;
public class SerpentsHandManager
{
public static bool IsSerpentsHand(Player player)
{
try
{
return player.CustomInfo.Contains("SerpentsHand");
}
catch
{
return false;
}
}
private readonly CustomClasses _customClasses;
public SerpentsHandManager(CustomClasses customClasses)
{
_customClasses = customClasses;
PlayerEvents.Escaping += ev =>
{
if (!IsSerpentsHand(ev.Player)) return;
var state = (SerpentsHandState) _customClasses.ClassManager.GetSpawnState(typeof(SerpentsHandConfig));
var hadItem = false;
foreach (var playerItem in ev.Player.Items)
{
switch (playerItem.Type)
{
case ItemType.SCP018 or ItemType.SCP207 or ItemType.SCP244a or ItemType.SCP244b
or ItemType.SCP268 or ItemType.SCP330 or ItemType.SCP500 or ItemType.SCP1344 or ItemType.SCP1576
or ItemType.SCP1853 or ItemType.SCP2176 or ItemType.AntiSCP207:
state.Points += 1;
hadItem = true;
break;
case ItemType.GunSCP127:
state.Points += 2;
hadItem = true;
break;
}
}
if (Warhead.IsDetonationInProgress || Warhead.IsDetonated)
{
hadItem = true;
state.Points += 1;
}
if (!hadItem) return;
ev.Player.SendBroadcast("You brought back the SCP items...", 5);
ev.Player.SetRole(RoleTypeId.Spectator);
Logger.Info($"New SH Points: {state.Points}");
};
ServerEvents.RoundEnding += ev =>
{
var factions =
Player.ReadyList.Select(x =>
{
return x.Team switch
{
Team.ChaosInsurgency or Team.ClassD => Faction.FoundationEnemy,
Team.FoundationForces or Team.Scientists => Faction.FoundationStaff,
Team.Flamingos => Faction.Flamingos,
Team.SCPs => Faction.SCP,
Team.OtherAlive when IsSerpentsHand(x) => (Faction)35,
_ => Faction.Unclassified
};
}).Where(x=>x!=Faction.Unclassified).GroupBy(x=>x).Select(x=>x.Key).ToArray();
if (factions.Length > 1)
{
ev.IsAllowed = false;
return;
}
var state = (SerpentsHandState) _customClasses.ClassManager.GetSpawnState(typeof(SerpentsHandConfig));
if (state.Points >= 10)
{
ev.LeadingTeam = RoundSummary.LeadingTeam.Flamingos;
}
};
}
public static void PreSpawn(Player player)
{
player.SetRole(RoleTypeId.Tutorial, RoleChangeReason.RespawnMiniwave, RoleSpawnFlags.None);
}
public IEnumerator<float> UpdateSerpentsHandHint()
{
while (true)
{
yield return Timing.WaitForSeconds(1);
if (_customClasses.ClassManager.GetSpawnState(typeof(SerpentsHandConfig)) is not SerpentsHandState state) continue;
foreach (var player in Player.ReadyList)
{
if (!_customClasses.Hints.ContainsKey(player))
{
_customClasses.Hints[player] = new Hint
{
Text = "", Alignment = HintAlignment.Center, YCoordinate = 900, Hide = true
};
var playerDisplay = PlayerDisplay.Get(player);
playerDisplay.AddHint(_customClasses.Hints[player]);
}
_customClasses.Hints[player].Text =
$"Serpents Hand Chance: {state.ExtraChance + SerpentsHandConfig.BaseChance:0.00}%";
_customClasses.Hints[player].Hide = state.HasSpawned || player.Role != RoleTypeId.Spectator;
}
}
// ReSharper disable once IteratorNeverReturns
}
public static void SpawnSerpentWave()
{
var state = (SerpentsHandState)CustomClasses.Instance.ClassManager.GetSpawnState(typeof(SerpentsHandConfig));
var serpentsHandConfig = CustomClasses.Instance.ClassManager.GetConfig<SerpentsHandConfig>();
state.SetSpawned();
state.SetWillSpawn();
var possibleLocations = (Vector3[])serpentsHandConfig.SpawnLocations.Clone();
possibleLocations.ShuffleListSecure();
var spawnLocation = possibleLocations[0];
foreach (var possibleLocation in possibleLocations)
{
if (Player.ReadyList.Any(p => (p.Position - possibleLocation).SqrMagnitudeIgnoreY() < 400))
{
continue;
}
spawnLocation = possibleLocation;
break;
}
state.SpawnLocation = spawnLocation;
var light = LightSourceToy.Create(spawnLocation + new Vector3(0, 2, 0), Quaternion.identity);
light.Color = Color.blue;
light.Intensity = 3;
light.Range = 40;
var spaceTimeHole = PrimitiveObjectToy.Create(spawnLocation + new Vector3(0, 2, 0), Quaternion.identity);
spaceTimeHole.Color = new Color(44.7f, 73.7f, 83.1f, 0.5f);
spaceTimeHole.Flags = PrimitiveFlags.Visible;
spaceTimeHole.Scale *= 2;
Timing.CallDelayed(20, () =>
{
spaceTimeHole.Destroy();
light.Destroy();
});
}
}
public sealed record SerpentsHandState: SpawnState
{
public bool HasSpawned => _hasSpawned || Warhead.IsDetonated;
public float ExtraChance;
public int Points;
public bool WillSpawn => _willSpawn && !Warhead.IsDetonated;
private bool _hasSpawned;
private bool _willSpawn;
public Vector3? SpawnLocation;
public override void Reset()
{
base.Reset();
_hasSpawned = false;
ExtraChance = 0f;
Points = 0;
_willSpawn = false;
SpawnLocation = null;
}
public void SetSpawned()
{
_hasSpawned = true;
}
public void SetWillSpawn()
{
_willSpawn = true;
}
public void SetWillNotSpawn()
{
_willSpawn = false;
}
}
public sealed class SerpentsHandConfig : CustomClassConfig
{
public override double ChancePerPlayer { get; set; } = 1.0;
public override int MaxSpawns { get; set; } = int.MaxValue;
public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Tutorial;
public override ItemType[] Items { get; set; } = [ItemType.Painkillers, ItemType.Medkit, ItemType.ArmorCombat];
public const float BaseChance = 90f;
public readonly Vector3[] SpawnLocations = [new(0.22f, 300.96f, -0.31f), new(123.921f, 288.792f, 20.929f)];
public override string Color { get; init; } = "#32CD32";
public override string Name { get; init; } = "Serpents Hand";
}
public class SerpentsHandHandler : SimpleAddItemHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
base.HandleSpawn(player, config, random);
var spawnLocation = ((SerpentsHandState)CustomClasses.Instance.ClassManager.GetSpawnState(
typeof(SerpentsHandConfig))).SpawnLocation;
if (spawnLocation !=
null)
CustomClasses.Instance.ClassManager.TeleportPlayerToAround(player, (Vector3)spawnLocation);
ItemType[] guns = [ItemType.GunAK, ItemType.GunE11SR, ItemType.GunCrossvec];
var gun = guns[random.Next(0, guns.Length-1)];
var gunPickup = Pickup.Create(gun, Vector3.one);
if (gunPickup is FirearmPickup firearm)
{
if (firearm.Base.Template.TryGetModule(out MagazineModule magazine))
{
magazine.ServerSetInstanceAmmo(firearm.Serial, magazine.AmmoMax);
player.SetAmmo(magazine.AmmoType, 120);
}
else
{
Logger.Error("Failed to get magazine module for Serpents Hand firearm.");
}
}
else
{
Logger.Error("Failed to get firearm from pickup for Serpents Hand.");
}
player.AddItem(gunPickup!);
KeycardItem.CreateCustomKeycardTaskForce(
player,
"Serpent's Hand Keycard",
$"SH. {player.Nickname}",
new KeycardLevels(3, 3, 2),
Color.black,
new Color(0.271f, 0.271f, 0.271f),
"SH",
3
);
player.EnableEffect<MovementBoost>(20, 30);
player.EnableEffect<SpawnProtected>(1, 20f);
}
public override void SendSpawnMessage(Player player, CustomClassConfig config)
{
player.SendBroadcast("You're a <color=#2E8B57>Serpent's Hand</color> member!", CustomClasses.BroadcastDuration);
}
}
[CommandHandler(typeof(RemoteAdminCommandHandler))]
[CommandHandler(typeof(ClientCommandHandler))]
public class SpawnSerpentsCommand : ICommand
{
public string Command => "spsh";
public string[] Aliases => [];
public string Description => "Makes sure serpents hand spawns";
public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out string response)
{
var executor = Player.Get(sender);
if (executor == null)
{
response = "You must be a player to use this command!";
return false;
}
if (!executor.HasPermissions("customclasses.setcustomclass"))
{
response = "You do not have permission to use this command!";
return false;
}
SerpentsHandManager.SpawnSerpentWave();
response = "success";
return true;
}
}
+76
View File
@@ -0,0 +1,76 @@
using CommandSystem;
using LabApi.Features.Permissions;
using LabApi.Features.Wrappers;
namespace CustomClasses;
[CommandHandler(typeof(RemoteAdminCommandHandler))]
[CommandHandler(typeof(ClientCommandHandler))]
public class SetCClassCommand : ICommand
{
public string Command => "setcclass";
public string[] Aliases => ["scc"];
public string Description => "Forces a player to become a specific custom class";
public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out string response)
{
var executor = Player.Get(sender);
if (executor == null)
{
response = "You must be a player to use this command!";
return false;
}
if (!executor.HasPermissions("customclasses.setcustomclass"))
{
response = "You do not have permission to use this command!";
return false;
}
var args = arguments.Array!;
if (arguments.Count < 2)
{
response = "Usage: setcclass <playerName> <className>";
return false;
}
var className = args[arguments.Offset + arguments.Count - 1].ToLower();
var playerName = string.Join(" ", args.Skip(arguments.Offset).Take(arguments.Count - 1));
var player = Player.ReadyList.FirstOrDefault(x => x.Nickname == playerName || x.UserId == playerName || x.NetworkId.ToString() == playerName);
if (player == null)
{
response = $"Player {playerName} not found";
return false;
}
var manager = CustomClasses.Instance.ClassManager;
var success = className switch
{
"janitor" => manager.ForceSpawn(player, manager.GetConfig<JanitorConfig>(), typeof(JanitorConfig), null),
"subject" or "researchsubject" => manager.ForceSpawn(player, manager.GetConfig<ResearchSubjectConfig>(), typeof(ResearchSubjectConfig), null),
"headguard" => manager.ForceSpawn(player, manager.GetConfig<HeadGuardConfig>(), typeof(HeadGuardConfig), null),
"medic" => manager.ForceSpawn(player, manager.GetConfig<MedicConfig>(), typeof(MedicConfig), null),
"gambler" => manager.ForceSpawn(player, manager.GetConfig<GamblerConfig>(), typeof(GamblerConfig), null),
"shadowstepper" => manager.ForceSpawn(player, manager.GetConfig<ShadowStepperConfig>(), typeof(ShadowStepperConfig), null),
"demolitionist" => manager.ForceSpawn(player, manager.GetConfig<MtfDemolitionistConfig>(), typeof(MtfDemolitionistConfig), null),
"scout" => manager.ForceSpawn(player, manager.GetConfig<ScoutConfig>(), typeof(ScoutConfig), null),
"explosivemaster" => manager.ForceSpawn(player, manager.GetConfig<ExplosiveMasterConfig>(), typeof(ExplosiveMasterConfig), null),
"flashmaster" => manager.ForceSpawn(player, manager.GetConfig<FlashMasterConfig>(), typeof(FlashMasterConfig), null),
"serpentshand" => manager.ForceSpawn(player, manager.GetConfig<SerpentsHandConfig>(), typeof(SerpentsHandConfig),
() => SerpentsHandManager.PreSpawn(player)),
"negromancer" => manager.ForceSpawn(player, manager.GetConfig<NegromancerConfig>(), typeof(NegromancerConfig), null),
"bloodfueled" => manager.ForceSpawn(player, manager.GetConfig<BloodFueledConfig>(), typeof(BloodFueledConfig), null),
_ => false
};
if (!success)
{
response = $"Failed to set {playerName} to {className}. Make sure the player has the correct base role for the custom class.";
return false;
}
response = $"Successfully set {playerName} to {className}";
return true;
}
}
+18
View File
@@ -18,8 +18,26 @@ public class CustomItemSpawn : Plugin<ItemConfig>
public override string Description => "Spawns items in a custom location.";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
_singleton = this;
ServerEvents.RoundStarted += OnRoundStart;
}
+18 -1
View File
@@ -28,9 +28,26 @@ public class Plugin : LabApi.Loader.Features.Plugins.Plugin
ConfigChances = this.LoadConfig<GamblingCoinChancesConfig>("chances.yml");
}
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
Logger.Debug("starting...");
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
Singleton = this;
_eventHandler = new GamblingCoinEventHandler();
PlayerEvents.FlippedCoin += _eventHandler.OnFlippedCoin;
+4 -1
View File
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
@@ -23,6 +23,9 @@
<Reference Include="Assembly-CSharp">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll</HintPath>
</Reference>
+2
View File
@@ -23,6 +23,7 @@ public class GamblingCoinChancesConfig
public int AdvancedPositiveEffectChance { get; set; } = 150;
public int AdvancedNegativeEffectChance { get; set; } = 250;
public int RemoveCoinChance { get; set; } = 300;
public int SpawnZombieChance { get; set; } = 100;
}
public class GamblingCoinMessages
@@ -44,6 +45,7 @@ public class GamblingCoinMessages
public string NegativeEffectMessage { get; set; } = "You feel worse";
public string AdvancedNegativeEffectMessage { get; set; } = "You feel like you could die any second";
public string SwitchInventoryMessage { get; set; } = "Whoops... looks like something happened to your items!";
public string SpawnZombieMessage { get; set; } = "You spawned as a Zombie!";
}
public class GamblingCoinGameplayConfig
+42 -23
View File
@@ -1,11 +1,11 @@
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Features.Wrappers;
using MapGeneration;
using MEC;
using Mirror;
using PlayerRoles;
using Respawning;
using UnityEngine;
using MicroHIDItem = InventorySystem.Items.MicroHID.MicroHIDItem;
using Random = UnityEngine.Random;
namespace GamblingCoin;
@@ -23,10 +23,15 @@ public class GamblingCoinEventHandler
_executor = new WeightedRandomExecutor<PlayerFlippedCoinEventArgs>();
_executor
.AddAction(_ =>
.AddAction(x =>
{
Warhead.DetonationTime += configGameplay.WarheadTimeIncrease;
Warhead.Start(suppressSubtitles: true);
foreach (var player in Player.ReadyList)
{
player.SendBroadcast($"{x.Player.Nickname} activated the Alpha Warhead through gambling! (coin)", 10);
}
}, configChances.NukeChance)
.AddAction(x =>
{
@@ -64,23 +69,10 @@ public class GamblingCoinEventHandler
var itemIndex = Random.Range(0, items.Length);
var item = items[itemIndex];
var pickup = Pickup.Create(item, player.Position + new Vector3(0, 1, 0));
if (pickup == null) return;
if (item is ItemType.MicroHID)
{
var host = Player.List.First(player1 => player1.IsHost);
var micro = host.AddItem(pickup)!.Base;
var microItem = (MicroHIDItem)micro;
microItem.EnergyManager.ServerSetEnergy(microItem.ItemId.SerialNumber, 100);
var newPickup = host.DropItem(micro);
newPickup.Position = player.Position + new Vector3(0, 1, 0);
return;
}
pickup.Spawn();
}, configChances.LegendaryItemChance)
.AddAction(x =>
@@ -110,7 +102,6 @@ public class GamblingCoinEventHandler
var newPos = randomRoom.Position;
x.Player.Position = newPos + new Vector3(0, configGameplay.TeleportHeightOffset, 0);
;
}, configChances.RandomTeleportChance)
.AddAction(x =>
{
@@ -130,7 +121,11 @@ public class GamblingCoinEventHandler
{
x.Player.SendBroadcast(configMessages.AntiMicroMessage, configGameplay.BroadcastDuration);
GetPlayers().ForEach(p => { p.RemoveItem(ItemType.MicroHID, configGameplay.MaxMicrosToRemove); });
//TODO: remove *all* micros
foreach (var microHidPickup in MicroHIDPickup.List)
{
microHidPickup.Destroy();
}
}, configChances.AntiMicroChance)
.AddAction(x =>
{
@@ -167,27 +162,51 @@ public class GamblingCoinEventHandler
}, configChances.AdvancedNegativeEffectChance)
.AddAction(x =>
{
var players = GetPlayers();
var players = GetPlayers().Where(player=>player.Team is not Team.Dead and not Team.SCPs).ToArray();
var randomPlayer = players[Random.Range(0, GetPlayers().Length)];
while (randomPlayer.RoleBase.Team is Team.Dead or Team.SCPs)
randomPlayer = players[Random.Range(0, GetPlayers().Length)];
x.Player.SendBroadcast(configMessages.SwitchInventoryMessage, configGameplay.BroadcastDuration);
randomPlayer.SendBroadcast(configMessages.SwitchInventoryMessage, configGameplay.BroadcastDuration);
var randomPlayerItems = new List<Item>();
randomPlayer.Items.CopyTo(randomPlayerItems);
List<KeyValuePair<ItemType, ushort>> randomPlayerAmmo = new();
randomPlayer.Ammo.CopyTo(randomPlayerAmmo);
var items = x.Player.Items;
var ammoCount = x.Player.Ammo;
randomPlayer.ClearInventory();
foreach (var itemBase in items) randomPlayer.AddItem(itemBase.Type);
foreach (var ammo in ammoCount) randomPlayer.AddAmmo(ammo.Key, ammo.Value);
x.Player.ClearInventory();
foreach (var randomPlayerItem in randomPlayerItems) x.Player.AddItem(randomPlayerItem.Type);
foreach (var randomPlayerAmmoItem in randomPlayerAmmo) x.Player.AddAmmo(randomPlayerAmmoItem.Key, randomPlayerAmmoItem.Value);
}, configChances.SwitchInventoryChance)
.AddAction(x => { x.Player.CurrentItem?.DropItem().Destroy(); }, configChances.RemoveCoinChance);
.AddAction(x => { x.Player.CurrentItem?.DropItem().Destroy(); }, configChances.RemoveCoinChance)
.AddAction(x =>
{
var spectators = Player.List.Where(player => player.Role == RoleTypeId.Spectator).ToArray();
if(spectators.Length == 0) return;
var spectator = spectators[Random.Range(0, spectators.Length-1)];
x.Player.SendBroadcast(configMessages.SpawnZombieMessage, configGameplay.BroadcastDuration);
spectator.SetRole(RoleTypeId.Scp0492);
Timing.CallDelayed(0.5f, () =>
{
var spawnRoom = Map.Rooms.First(room => room.Name == RoomName.HczWarhead);
if (Warhead.IsDetonated)
{
spawnRoom = Map.Rooms.First(room => room.Name == RoomName.Outside);
}
spectator.Position = spawnRoom.Position + new Vector3(0, 1, 0);
});
}, configChances.SpawnZombieChance);
return;
+49 -17
View File
@@ -1,10 +1,11 @@
using CustomPlayerEffects;
using CustomPlayerEffects;
using InventorySystem.Items.Usables.Scp330;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Arguments.Scp0492Events;
using LabApi.Events.Arguments.ServerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
@@ -12,18 +13,40 @@ namespace GrowingZombies;
public class GrowingZombies : Plugin
{
private readonly Dictionary<Player, int> _zombieCorpseCount = new();
public readonly Dictionary<Player, int> ZombieCorpseCount = new();
public static GrowingZombies Instance { get; set; }
public override string Name => "GrowingZombies";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Makes zombies grow stronger as they eat more";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
Scp0492Events.ConsumedCorpse += OnZombieEat;
ServerEvents.RoundEnded += OnRoundEnd;
PlayerEvents.Left += OnPlayerLeave;
Instance = this;
}
public override void Disable()
@@ -31,17 +54,18 @@ public class GrowingZombies : Plugin
Scp0492Events.ConsumedCorpse -= OnZombieEat;
ServerEvents.RoundEnded -= OnRoundEnd;
PlayerEvents.Left -= OnPlayerLeave;
_zombieCorpseCount.Clear();
ZombieCorpseCount.Clear();
Instance = null;
}
private void OnRoundEnd(RoundEndedEventArgs ev)
{
_zombieCorpseCount.Clear();
ZombieCorpseCount.Clear();
}
private void OnPlayerLeave(PlayerLeftEventArgs ev)
{
_zombieCorpseCount.Remove(ev.Player);
ZombieCorpseCount.Remove(ev.Player);
}
private void OnZombieEat(Scp0492ConsumedCorpseEventArgs ev)
@@ -49,27 +73,35 @@ public class GrowingZombies : Plugin
if (!ev?.Player.ReferenceHub.playerEffectsController)
return;
// Increment corpse count for this zombie
if (!_zombieCorpseCount.ContainsKey(ev.Player))
_zombieCorpseCount[ev.Player] = 0;
_zombieCorpseCount[ev.Player]++;
AteCorpse(ev.Player);
}
var corpsesEaten = _zombieCorpseCount[ev.Player];
public void AteCorpse(Player player)
{
if (!ZombieCorpseCount.ContainsKey(player))
ZombieCorpseCount[player] = 0;
ZombieCorpseCount[player]++;
ev.Player.MaxHealth += 50;
var corpsesEaten = ZombieCorpseCount[player];
var movementBoostIntensity = (byte)Math.Min(1 + corpsesEaten * 0.1f, 3f);
ev.Player.ReferenceHub.playerEffectsController.ChangeState<MovementBoost>(movementBoostIntensity, 30);
player.MaxHealth = Math.Min(1000, player.MaxHealth + 50);
player.MaxHumeShield += 10;
var movementBoostIntensity = (byte)Math.Min(1 + corpsesEaten * 0.5f, 5f);
player.ReferenceHub.playerEffectsController.ChangeState<MovementBoost>(movementBoostIntensity, 120);
// Add damage resistance after eating multiple corpses
var damageResistance = (byte)Math.Min(0.5 - corpsesEaten * 0.5f, 2f);
if (corpsesEaten >= 3)
ev.Player.ReferenceHub.playerEffectsController.ChangeState<DamageReduction>(damageResistance, 20);
{
var damageReductionIntensity = (byte)Math.Min(corpsesEaten * 2, 100); // Half-Percent
player.ReferenceHub.playerEffectsController.ChangeState<DamageReduction>(damageReductionIntensity,
float.MaxValue);
}
// Add regeneration effect after eating multiple corpses
if (corpsesEaten < 5) return;
var regenIntensity = Math.Min(1 + corpsesEaten * 0.2f, 3f);
Scp330Bag.AddSimpleRegeneration(ev.Player.ReferenceHub, regenIntensity, 15f);
Scp330Bag.AddSimpleRegeneration(player.ReferenceHub, regenIntensity, 15f);
}
}
}
+6
View File
@@ -23,6 +23,12 @@
<Reference Include="Assembly-CSharp">
<HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
+56
View File
@@ -0,0 +1,56 @@
using CommandSystem;
using LabApi.Features.Wrappers;
using PlayerRoles;
using ICommand = CommandSystem.ICommand;
namespace GrowingZombies;
[CommandHandler(typeof(ClientCommandHandler))]
public class SacrificeCommand: ICommand
{
public string Command => "sacrifice";
public string[] Aliases => ["sac"];
public string Description => "Sacrifice yourself to give another zombie an extra corpse count";
public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out string response)
{
if (!Player.TryGet(sender, out var player))
{
response = "You must be a player to use this command!";
return false;
}
if (player.Role != RoleTypeId.Scp0492)
{
response = "You must be a zombie to use this command!";
return false;
}
var zombies = Player.List.Where(p => p.Role == RoleTypeId.Scp0492 && p != player).ToList();
if (!zombies.Any())
{
response = "There are no other zombies to receive your sacrifice!";
return false;
}
// Select random zombie to receive the bonus
var luckyZombie = zombies[UnityEngine.Random.Range(0, zombies.Count)];
// Add corpse count to the lucky zombie
GrowingZombies.Instance.AteCorpse(luckyZombie);
// Remove corpse count from the sacrificing player
GrowingZombies.Instance.ZombieCorpseCount[player] = 0;
// Kill the sacrificing player
player.Kill("Sacrificed themselves for their zombie brethren");
luckyZombie.SendHint($"You received the sacrifice of {player.Nickname}!", 5);
response = "You sacrificed yourself to give another zombie an extra corpse count!";
var scp049 = Player.List.FirstOrDefault(p => p.Role == RoleTypeId.Scp049);
scp049?.SendHint($"Your zombie {player.Nickname} sacrificed themselves to give {luckyZombie.Nickname} another corpse they ate!", 5);
return true;
}
}
+20 -1
View File
@@ -129,6 +129,8 @@ public class Plugin : LabApi.Loader.Features.Plugins.Plugin
if (!doorButton) doorButton = hit.collider.GetComponent<SimpleButton>();
if (!doorButton) continue;
ev.Player.SendHitMarker();
var doorVariant = DoorVariant.AllDoors.AsEnumerable()!.First(x =>
x.Buttons.Any(c => c.GetInstanceID() == doorButton.GetInstanceID()));
@@ -140,9 +142,26 @@ public class Plugin : LabApi.Loader.Features.Plugins.Plugin
}
}
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
Logger.Debug("starting...");
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
Singleton = this;
PlayerEvents.InteractingDoor += OnInteractingDoor;
+1 -1
View File
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
+179
View File
@@ -0,0 +1,179 @@
using CommandSystem.Commands.RemoteAdmin;
using GameCore;
using LabApi.Events.Arguments.ServerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Loader.Features.Plugins;
using MEC;
using Interactables.Interobjects.DoorUtils;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Features.Wrappers;
using MapGeneration;
using PlayerRoles;
using UnityEngine;
using Logger = LabApi.Features.Console.Logger;
using Version = System.Version;
namespace LobbyGame
{
public class LobbyGame: Plugin
{
public override string Name => "LobbyGame";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Adds a lobby minigame";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public int RoundTimer { get; set; } = 20;
public static LobbyGame Singleton { get; private set; }
private bool _isStarted;
private Room _randomRoom;
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
ServerEvents.WaitingForPlayers += WaitingForPlayers;
PlayerEvents.Joined += PlayerJoined;
Singleton = this;
}
private static void PlayerJoined(PlayerJoinedEventArgs ev)
{
Timing.RunCoroutine(ContinuouslyTrySpawning());
return;
IEnumerator<float> ContinuouslyTrySpawning()
{
while (!RoundStart.RoundStarted)
{
GameObject.Find("StartRound").transform.localScale = Vector3.zero;
yield return Timing.WaitForSeconds(0.5f);
if(Singleton._randomRoom == null) continue;
if(!Singleton._isStarted) continue;
ev.Player.SetRole(RoleTypeId.ChaosRifleman, RoleChangeReason.None, RoleSpawnFlags.None);
ev.Player.Position = Singleton._randomRoom.Position + new Vector3(0, 1, 0);
break;
}
}
}
private static void WaitingForPlayers()
{
Timing.CallDelayed(15, () =>
{
if (Singleton._isStarted) return;
var randomRoom = Map.GetRandomRoom(FacilityZone.Entrance);
while (randomRoom is not { Zone: FacilityZone.Entrance })
{
randomRoom = Map.GetRandomRoom(FacilityZone.Entrance);
}
Logger.Debug($"Random entrance room: {randomRoom.Name}");
RoundStart.LobbyLock = true;
Singleton._randomRoom = randomRoom;
Singleton._isStarted = true;
foreach (var player in Player.ReadyList)
{
player.SetRole(RoleTypeId.ChaosRifleman, RoleChangeReason.None, RoleSpawnFlags.None);
try
{
player.Position = randomRoom.Position + new Vector3(0, 1, 0);
}
catch (Exception _)
{
player.SetRole(RoleTypeId.Spectator);
}
}
Timing.RunCoroutine(ContinuouslyUpdateRoundTimer());
GameObject.Find("StartRound").transform.localScale = Vector3.zero;
foreach (var door in Map.Doors)
{
door.IsOpened = false;
door.Lock(DoorLockReason.Lockdown2176, true);
}
return;
IEnumerator<float> ContinuouslyUpdateRoundTimer()
{
while (true)
{
yield return Timing.WaitForSeconds(1);
foreach (var player in Player.ReadyList)
{
player.SendBroadcast(
$"<size=30><color=grey>Round starts in</color> <color=red>{Singleton.RoundTimer}</color> <color=grey>seconds</color></size>",
10, Broadcast.BroadcastFlags.Normal, true);
}
Logger.Debug($"Round starts in {Singleton.RoundTimer} seconds");
if (Player.ReadyList.Count() <= 1)
{
Singleton.RoundTimer = 20;
continue;
}
Singleton.RoundTimer--;
if (Singleton.RoundTimer >= -1) continue;
Singleton.RoundTimer = 20;
foreach (var player in Player.ReadyList)
{
player.ClearInventory();
player.SetRole(RoleTypeId.Spectator);
}
foreach (var door in Map.Doors)
{
door.Lock(DoorLockReason.Lockdown2176, false);
}
foreach (var player in Player.ReadyList)
{
player.SendBroadcast(
"",
1, Broadcast.BroadcastFlags.Normal, true);
}
CharacterClassManager.ForceRoundStart();
break;
}
}
});
}
public override void Disable()
{
ServerEvents.WaitingForPlayers -= WaitingForPlayers;
PlayerEvents.Joined -= PlayerJoined;
Singleton = null;
}
}
}
+57
View File
@@ -0,0 +1,57 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>..\dependencies\0Harmony.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="Pooling">
<HintPath>..\dependencies\Pooling.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
+97
View File
@@ -0,0 +1,97 @@
using HintServiceMeow.Core.Enum;
using HintServiceMeow.Core.Models.Hints;
using HintServiceMeow.Core.Utilities;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Loader.Features.Plugins;
using LabApi.Features.Wrappers;
using MEC;
namespace ModInfo
{
public class ModInfo : Plugin
{
public override string Name => "ModInfo";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Shows some extra info for moderators";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private readonly Dictionary<Player, Hint> _spectatorHints = new();
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
Timing.RunCoroutine(GodmodeHintLoop());
Scp096Events.AddingTarget += ev =>
{
if (ev.Target.IsGodModeEnabled || ev.Target.IsNoclipEnabled) ev.IsAllowed = false;
};
Scp173Events.AddingObserver += ev =>
{
if (ev.Target.IsGodModeEnabled || ev.Target.IsNoclipEnabled) ev.IsAllowed = false;
};
}
public override void Disable()
{
}
private IEnumerator<float> GodmodeHintLoop()
{
while(true)
{
yield return Timing.WaitForSeconds(1);
UpdateHints();
}
// ReSharper disable once IteratorNeverReturns
}
private void UpdateHints()
{
foreach (var player in Player.ReadyList) UpdateHint(player);
}
private void UpdateHint(Player player)
{
var hint = _spectatorHints.TryGetValue(player, out var hintValue) ? hintValue : AddPlayerHint(player);
hint.Hide = !player.IsGodModeEnabled;
}
private Hint AddPlayerHint(Player player)
{
var hint = new Hint
{
Text = "<size=40><color=#50C878>GODMODE</color></size>",
Alignment = HintAlignment.Left,
YCoordinate = 800,
Hide = true
};
var playerDisplay = PlayerDisplay.Get(player);
playerDisplay.AddHint(hint);
_spectatorHints[player] = hint;
return hint;
}
}
}
+57
View File
@@ -0,0 +1,57 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>..\dependencies\0Harmony.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="Pooling">
<HintPath>..\dependencies\Pooling.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
+3 -3
View File
@@ -9,10 +9,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0"/>
<PackageReference Include="coverlet.collector" Version="6.0.4"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0"/>
<PackageReference Include="NUnit" Version="4.3.2"/>
<PackageReference Include="NUnit.Analyzers" Version="4.4.0"/>
<PackageReference Include="NUnit.Analyzers" Version="4.8.1"/>
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0"/>
</ItemGroup>
+20 -3
View File
@@ -1,4 +1,4 @@
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Console;
@@ -14,9 +14,26 @@ public class RangeBan : Plugin<RangeBanConfig>
public override string Description => "Ban IP Ranges with ease";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
Logger.Debug("Loading...");
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
PlayerEvents.PreAuthenticating += OnAuth;
}
@@ -86,4 +103,4 @@ public class RangeBan : Plugin<RangeBanConfig>
public class RangeBanConfig
{
public string[] IpRanges { get; set; } = { };
}
}
+76
View File
@@ -0,0 +1,76 @@
using CustomPlayerEffects;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Loader.Features.Plugins;
using MEC;
using PlayerRoles;
using PlayerRoles.PlayableScps.Scp3114;
using Logger = LabApi.Features.Console.Logger;
namespace SCPBalance;
public class ScpBalance : Plugin
{
public override string Name => "SCPBalance";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Rethink SCP balance";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
PlayerEvents.Spawned += HandleSpawn;
PlayerEvents.Hurting += OnPlayerHurting;
}
public override void Disable()
{
PlayerEvents.Spawned -= HandleSpawn;
PlayerEvents.Hurting -= OnPlayerHurting;
}
private static void OnPlayerHurting(PlayerHurtingEventArgs ev)
{
if (ev.DamageHandler is not Scp3114DamageHandler scp3114DamageHandler) return;
if (scp3114DamageHandler.Subtype != Scp3114DamageHandler.HandlerType.Slap) return;
if (ev.Attacker != null) ev.Attacker.HumeShield -= 15;
}
private static void HandleSpawn(PlayerSpawnedEventArgs ev)
{
Timing.CallDelayed(1f, () =>
{
Logger.Debug("Handling Balance");
if (ev.Role.RoleTypeId == RoleTypeId.Scp049)
{
ev.Player.ReferenceHub.playerEffectsController.ChangeState<MovementBoost>(5, float.MaxValue);
}
if (ev.Role.RoleTypeId == RoleTypeId.Scp3114)
{
ev.Player.ReferenceHub.playerEffectsController.ChangeState<Slowness>(6, float.MaxValue);
}
});
}
}
+57
View File
@@ -0,0 +1,57 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>..\dependencies\0Harmony.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="Pooling">
<HintPath>..\dependencies\Pooling.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
+139 -69
View File
@@ -6,11 +6,13 @@ using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using MapGeneration;
using PlayerRoles;
using PlayerRoles.PlayableScps.Scp079;
using PlayerRoles.PlayableScps.Scp096;
using PlayerRoles.PlayableScps.Scp3114;
using Timer = System.Timers.Timer;
using MEC;
using PlayerRoles.PlayableScps.Scp049.Zombies;
namespace SCPTeamHint;
@@ -19,121 +21,189 @@ public class Plugin : LabApi.Loader.Features.Plugins.Plugin
private readonly object _hintsLock = new();
private readonly Dictionary<Player, Hint> _spectatorHints = new();
private Timer _timer;
public override string Name => "SCPTeamHint";
public override string Author => "HoherGeist, Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Displays information about your SCP Teammates";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
Logger.Debug("Apple juice");
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
PlayerEvents.Joined += OnJoin;
PlayerEvents.Left += OnLeft;
_timer = new Timer(1000);
_timer.Elapsed += (_, _) => UpdateHints();
_timer.Start();
Timing.RunCoroutine(ContinuouslyUpdateHints());
}
private IEnumerator<float> ContinuouslyUpdateHints()
{
while (true)
{
yield return Timing.WaitForSeconds(1);
try
{
UpdateHints();
}
catch (Exception e)
{
Logger.Error(e);
}
}
// ReSharper disable once IteratorNeverReturns
}
public override void Disable()
{
PlayerEvents.Joined -= OnJoin;
PlayerEvents.Left -= OnLeft;
_timer?.Stop();
_timer?.Dispose();
_timer = null;
}
private static string CollectHint()
{
var hintTexts = new List<string>();
foreach (var player in Player.ReadyList.Where(x => !x.IsDummy && (x.IsSCP || x.Role is RoleTypeId.Scp0492)))
{
var zone = player.Zone == FacilityZone.None ? Map.Elevators.Any(x=>x.Base.WorldspaceBounds.Contains(player.Position)) ? "Elevator" : "None" : player.Zone.ToString();
var text =
$" <size=25><color=red>{player.RoleBase.RoleName}</color> | <color=#6761cd>{(int)player.HumeShield}</color> | <color=#da0101>{(int)player.Health}</color> | <color=grey>{zone}</color></size> ";
switch (player.RoleBase)
{
case Scp096Role scp:
text += "\n";
scp.SubroutineModule.TryGetSubroutine(out Scp096TargetsTracker tracker);
if (!tracker) break;
var targetColor = tracker.Targets.Count > 0 ? "red" : "grey";
text += $"<color=grey>Targets:</color> <color={targetColor}>{tracker.Targets.Count}</color>";
break;
case Scp3114Role scp3114:
text += "\n";
var stolenRole = scp3114.CurIdentity.StolenRole;
if (scp3114.Disguised)
{
text += $" {stolenRole}";
}
else
{
text += " None";
}
break;
case Scp079Role scp079:
text =
$" <size=25><color=red>{player.RoleBase.RoleName}</color> | <color=grey>{scp079.CurrentCamera.Room.Zone}</color></size> ";
text += "\n";
scp079.SubroutineModule.TryGetSubroutine(out Scp079AuxManager auxManager);
scp079.SubroutineModule.TryGetSubroutine(out Scp079TierManager tierManager);
if (!auxManager || !tierManager) break;
text +=
$" <color=#FFEF00>AUX: {auxManager.CurrentAuxFloored}</color> / {auxManager.MaxAux} | <color=#FFD700>Level {tierManager.AccessTierLevel}</color>";
break;
case ZombieRole:
if (!GrowingZombies.GrowingZombies.Instance.ZombieCorpseCount.TryGetValue(player, out var count))
break;
const string corpseColor = "E68A8A";
text += "\n";
text += $" <color=#{corpseColor}>Corpses eaten: {count}</color>";
break;
}
hintTexts.Add(text);
}
return string.Join("\n", hintTexts);
}
private void UpdateHints()
{
var hintTexts = new List<string>();
lock (_hintsLock)
{
foreach (var player in Player.ReadyList.Where(x => !x.IsHost && !x.IsDummy && x.IsSCP))
var hintText = CollectHint();
foreach (var player in Player.ReadyList.Where(x => !x.IsDummy))
{
var text =
$" <size=25><color=red>{player.RoleBase.RoleName}</color> | <color=#6761cd>{player.HumeShield}</color> | <color=#da0101>{player.Health}</color> | <color=grey>{player.Zone}</color></size> ";
switch (player.RoleBase)
try
{
case Scp096Role scp:
text += "\n";
scp.SubroutineModule.TryGetSubroutine(out Scp096TargetsTracker tracker);
if (!tracker) break;
text += $"Targets: {tracker.Targets.Count}";
break;
case Scp3114Role scp3114:
{
text += "\n";
var stolenRole = scp3114.CurIdentity.StolenRole;
text += $" {stolenRole}";
break;
}
case Scp079Role scp079:
text =
$" <size=25><color=red>{player.RoleBase.RoleName}</color> | <color=grey>{scp079.CurrentCamera.Room.Zone}</color></size> ";
text += "\n";
scp079.SubroutineModule.TryGetSubroutine(out Scp079AuxManager auxManager);
scp079.SubroutineModule.TryGetSubroutine(out Scp079TierManager tierManager);
if (!auxManager || !tierManager) break;
text +=
$" <color=grey>AUX: {auxManager.CurrentAuxFloored} / {auxManager.MaxAux} | Level {tierManager.AccessTierLevel}</color>";
break;
UpdateHint(player, hintText);
} catch (Exception e)
{
Logger.Warn("Caught exception while updating hint for player");
Logger.Error(e);
}
hintTexts.Add(text);
}
var hintText = string.Join("\n", hintTexts);
foreach (var player in Player.ReadyList.Where(x => !x.IsHost && !x.IsDummy))
{
Logger.Debug($"Updating hint for {player.DisplayName}");
UpdateHint(player, hintText);
}
}
}
private void UpdateHint(Player player, string hintText)
{
if (!_spectatorHints.TryGetValue(player, out var hint))
bool isContained;
lock (_hintsLock)
{
Logger.Debug($"No hint found for player {player.DisplayName}");
return;
isContained = _spectatorHints.ContainsKey(player);
}
if (!isContained)
{
CreateHint(player);
}
Logger.Debug(
$"Player {player.Nickname} is on team {player.RoleBase.Team} | hide: {player.RoleBase.Team != Team.SCPs}");
hint.Hide = player.RoleBase.Team != Team.SCPs;
if (!hint.Hide) hint.Text = hintText;
if (_spectatorHints == null) return;
lock (_hintsLock)
{
var hint = _spectatorHints[player];
hint.Hide = player.RoleBase.Team != Team.SCPs && player.Role != RoleTypeId.Scp0492 && player.Role != RoleTypeId.Overwatch;
if (!hint.Hide) hint.Text = hintText;
}
}
private void OnJoin(PlayerJoinedEventArgs ev)
{
if (ev.Player.IsDummy || ev.Player.IsHost) return;
CreateHint(ev.Player);
}
private void CreateHint(Player player)
{
var hint = new Hint
{
Text = "", Alignment = HintAlignment.Left, YCoordinate = 100, Hide = true
};
var playerDisplay = PlayerDisplay.Get(ev.Player);
var playerDisplay = PlayerDisplay.Get(player);
playerDisplay.AddHint(hint);
lock (_hintsLock)
{
_spectatorHints[ev.Player] = hint;
_spectatorHints[player] = hint;
}
}
+8
View File
@@ -29,6 +29,9 @@
<Reference Include="Assembly-CSharp">
<HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
@@ -42,4 +45,9 @@
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GrowingZombies\GrowingZombies.csproj" />
</ItemGroup>
</Project>
+42 -2
View File
@@ -1,5 +1,9 @@
using LabApi.Features;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
using PlayerRoles;
namespace ScpSwap;
@@ -11,11 +15,47 @@ public class ScpSwap : Plugin
public override string Description => "Swap SCPs.";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
PlayerEvents.Spawned += ev =>
{
if (ev.Role.Team != Team.SCPs)
{
return;
}
if (Round.Duration.TotalSeconds > 100) {
return;
}
if (ev.Role.RoleTypeId == RoleTypeId.Scp0492)
{
return;
}
ev.Player.SendBroadcast("Willst du dein SCP wechseln? Drücke Ö und gebe .scpswap <SCP-NUMMER> ein.", 10);
};
}
public override void Disable()
{
}
}
}
+6
View File
@@ -33,6 +33,12 @@ public class SwapCommand : ICommand
return false;
}
if (player.Role == RoleTypeId.Scp0492)
{
response = "You can't swap SCPs while you're a zombie!";
return false;
}
List<string> validScp =
[
"049",
+42
View File
@@ -34,6 +34,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServerHints", "ServerHints\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrowingZombies", "GrowingZombies\GrowingZombies.csproj", "{5751F8D6-7A8D-4C2C-B7E9-A8A3DB324329}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemplateProject", "TemplateProject\TemplateProject.csproj", "{E5A28D1C-638F-4849-9784-240D50A6DA29}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LobbyGame", "LobbyGame\LobbyGame.csproj", "{E02243D5-0229-47BB-88A7-252EC753C8CC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StatsTracker", "StatsTracker\StatsTracker.csproj", "{DA17C0F1-9C99-4F80-9871-38D6EB00EA95}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModInfo", "ModInfo\ModInfo.csproj", "{8C55C629-FFB9-41AC-8F5C-1BF715110766}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VIPTreatment", "VIPTreatment\VIPTreatment.csproj", "{BAB7582A-FC51-4FCA-9166-BBAF7A6D1170}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SCPBalance", "SCPBalance\SCPBalance.csproj", "{CD7F5276-58D2-4DAB-A476-F5B61069AA62}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrollTK", "TrollTK\TrollTK.csproj", "{F2071139-1B13-4B9E-9A27-A7999CEE2DC9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -108,5 +122,33 @@ Global
{5751F8D6-7A8D-4C2C-B7E9-A8A3DB324329}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5751F8D6-7A8D-4C2C-B7E9-A8A3DB324329}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5751F8D6-7A8D-4C2C-B7E9-A8A3DB324329}.Release|Any CPU.Build.0 = Release|Any CPU
{E5A28D1C-638F-4849-9784-240D50A6DA29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5A28D1C-638F-4849-9784-240D50A6DA29}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5A28D1C-638F-4849-9784-240D50A6DA29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5A28D1C-638F-4849-9784-240D50A6DA29}.Release|Any CPU.Build.0 = Release|Any CPU
{E02243D5-0229-47BB-88A7-252EC753C8CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E02243D5-0229-47BB-88A7-252EC753C8CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E02243D5-0229-47BB-88A7-252EC753C8CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E02243D5-0229-47BB-88A7-252EC753C8CC}.Release|Any CPU.Build.0 = Release|Any CPU
{DA17C0F1-9C99-4F80-9871-38D6EB00EA95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DA17C0F1-9C99-4F80-9871-38D6EB00EA95}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DA17C0F1-9C99-4F80-9871-38D6EB00EA95}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA17C0F1-9C99-4F80-9871-38D6EB00EA95}.Release|Any CPU.Build.0 = Release|Any CPU
{8C55C629-FFB9-41AC-8F5C-1BF715110766}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C55C629-FFB9-41AC-8F5C-1BF715110766}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C55C629-FFB9-41AC-8F5C-1BF715110766}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C55C629-FFB9-41AC-8F5C-1BF715110766}.Release|Any CPU.Build.0 = Release|Any CPU
{BAB7582A-FC51-4FCA-9166-BBAF7A6D1170}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BAB7582A-FC51-4FCA-9166-BBAF7A6D1170}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BAB7582A-FC51-4FCA-9166-BBAF7A6D1170}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BAB7582A-FC51-4FCA-9166-BBAF7A6D1170}.Release|Any CPU.Build.0 = Release|Any CPU
{CD7F5276-58D2-4DAB-A476-F5B61069AA62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CD7F5276-58D2-4DAB-A476-F5B61069AA62}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD7F5276-58D2-4DAB-A476-F5B61069AA62}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD7F5276-58D2-4DAB-A476-F5B61069AA62}.Release|Any CPU.Build.0 = Release|Any CPU
{F2071139-1B13-4B9E-9A27-A7999CEE2DC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2071139-1B13-4B9E-9A27-A7999CEE2DC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2071139-1B13-4B9E-9A27-A7999CEE2DC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2071139-1B13-4B9E-9A27-A7999CEE2DC9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
+41 -1
View File
@@ -1,5 +1,6 @@
using InventorySystem.Items.Pickups;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Arguments.ServerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Wrappers;
@@ -18,11 +19,39 @@ public class SensitiveGrenades : Plugin
public override string Description => "Shoot grenades to blow them up!";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private static readonly object Lock = new();
private static readonly List<ushort> GrenadeIds = new();
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
Logger.Debug("starting...");
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
PlayerEvents.PlacedBulletHole += ShotWeapon;
ServerEvents.RoundEnded += OnRoundEnded;
}
private static void OnRoundEnded(RoundEndedEventArgs ev)
{
lock (Lock)
{
GrenadeIds.Clear();
}
}
private static void ShotWeapon(PlayerPlacedBulletHoleEventArgs ev)
@@ -39,6 +68,17 @@ public class SensitiveGrenades : Plugin
if (!grenade) continue;
lock (Lock)
{
if (GrenadeIds.Contains(grenade.ItemId.SerialNumber))
{
break;
}
GrenadeIds.Add(grenade.ItemId.SerialNumber);
}
Logger.Info($"Grenade shot by {ev.Player.Nickname}, exploding!");
itemPickup.DestroySelf();
TimedGrenadeProjectile.SpawnActive(itemPickup.Position, itemPickup.Info.ItemId, ev.Player);
break;
+20 -1
View File
@@ -1,5 +1,6 @@
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
using MEC;
@@ -26,8 +27,26 @@ public class ServerHints : Plugin
"Wenn man Granaten anschießt, explodieren sie sofort."
];
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
ServerEvents.RoundStarted += OnRoundStarted;
}
@@ -43,7 +62,7 @@ public class ServerHints : Plugin
var hint = Hints[random.Next(Hints.Length)];
Timing.CallDelayed(1, () =>
{
foreach (var player in Player.ReadyList) player.SendBroadcast($"<color=grey>{hint}</color>", 3);
foreach (var player in Player.ReadyList) player.SendBroadcast($"<color=grey>{hint}</color>", 5);
});
}
}
+97
View File
@@ -0,0 +1,97 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bincode"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
dependencies = [
"bincode_derive",
"serde",
"unty",
]
[[package]]
name = "bincode_derive"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09"
dependencies = [
"virtue",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "stats_tracker"
version = "0.1.0"
dependencies = [
"bincode",
]
[[package]]
name = "syn"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unty"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]]
name = "virtue"
version = "0.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
+18
View File
@@ -0,0 +1,18 @@
[package]
name = "stats_tracker"
version = "0.1.0"
edition = "2024"
[dependencies]
bincode = "2.0"
[lib]
crate-type = ["cdylib"]
name = "stats_tracker"
[profile.release]
opt-level = "z"
codegen-units = 1
lto = "fat"
overflow-checks = false
panic = "abort"
+98
View File
@@ -0,0 +1,98 @@
use std::{
ffi::{CStr, c_char},
fs::File,
};
use bincode::{Decode, Encode};
#[derive(Decode, Encode, Clone)]
#[repr(C)]
pub struct PlayerStat {
kills: u32,
deaths: u32,
team_damage: u32,
}
#[derive(Decode, Encode, Clone, Default)]
#[repr(C)]
pub struct ItemStat {
item_name: String,
item_count: u32,
}
#[derive(Decode, Encode, Clone)]
#[repr(C)]
pub struct Player {
player_id: String,
player_stats: PlayerStat,
player_items: [ItemStat; 256],
}
impl Default for Player {
fn default() -> Self {
Self {
player_id: String::new(),
player_stats: PlayerStat {
kills: 0,
deaths: 0,
team_damage: 0,
},
player_items: std::array::from_fn(|_| ItemStat::default()),
}
}
}
#[unsafe(no_mangle)]
/// # Safety
/// `player_id` must be a valid, null-terminated C string pointer.
pub unsafe extern "C" fn get_player_stats(player_id: *const c_char) -> *const Player {
let player_id = unsafe { CStr::from_ptr(player_id) }
.to_string_lossy()
.into_owned();
let db_location = "./stats.data";
let player_stats: Vec<Player> = match File::open(db_location) {
Ok(mut data) => bincode::decode_from_std_read(&mut data, bincode::config::standard())
.unwrap_or_default(),
Err(_) => Vec::new(),
};
player_stats
.iter()
.find(|p| p.player_id == player_id)
.unwrap_or(&Player::default())
}
#[unsafe(no_mangle)]
/// # Safety
/// `player` must be a valid pointer to a `Player` struct.
pub unsafe extern "C" fn save_player_stats(player: *const Player) -> bool {
let player = unsafe { &*player };
let db_location = "./stats.data";
let mut player_stats: Vec<Player> = match File::open(db_location) {
Ok(mut data) => bincode::decode_from_std_read(&mut data, bincode::config::standard())
.unwrap_or_default(),
Err(_) => Vec::new(),
};
// Find and update existing player or add new player
if let Some(existing_player) = player_stats
.iter_mut()
.find(|p| p.player_id == player.player_id)
{
*existing_player = player.clone();
} else {
player_stats.push(player.clone());
}
// Save updated stats
match File::create(db_location) {
Ok(mut file) => {
bincode::encode_into_std_write(&player_stats, &mut file, bincode::config::standard())
.is_ok()
}
Err(_) => false,
}
}
+229
View File
@@ -0,0 +1,229 @@
using LabApi.Features;
using LabApi.Loader.Features.Plugins;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Handlers;
using LabApi.Features.Console;
namespace StatsTracker
{
[SuppressMessage("ReSharper", "InconsistentNaming")]
public struct PlayerStat
{
public uint kills;
public uint deaths;
public uint team_damage;
}
[SuppressMessage("ReSharper", "InconsistentNaming")]
public struct ItemStat
{
public string item_name;
public uint item_count;
}
[SuppressMessage("ReSharper", "InconsistentNaming")]
public struct Player
{
public string player_id;
public PlayerStat player_stats;
public ItemStat[] player_items;
}
[SuppressMessage("ReSharper", "InconsistentNaming")]
public class StatsTracker : Plugin
{
public override string Name => "StatsTracker";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Tracks stats for players.";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string RustDllName = "stats_tracker";
[DllImport(RustDllName, EntryPoint="get_player_stats", CallingConvention = CallingConvention.Cdecl)]
private static extern ref Player GetPlayerStats(ref string player_id);
[DllImport(RustDllName, EntryPoint="save_player_stats", CallingConvention = CallingConvention.Cdecl)]
private static extern bool SavePlayerStats(ref Player player);
public override void Enable()
{
var pathVariable = Environment.GetEnvironmentVariable("PATH");
Logger.Debug($"PATH: {pathVariable}");
var extractedDllPath = ExtractRustDll(); // Call extraction
if (string.IsNullOrEmpty(extractedDllPath))
{
Logger.Error("Failed to extract Rust DLL. Exiting.");
return;
}
var libDirectory = Path.GetDirectoryName(extractedDllPath);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
SetDllDirectory(libDirectory);
Logger.Info($"Windows: Added '{libDirectory}' to DLL search path.");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Environment.SetEnvironmentVariable("LD_LIBRARY_PATH", libDirectory + ":" + Environment.GetEnvironmentVariable("LD_LIBRARY_PATH"));
Logger.Info($"Linux: Extracted library to '{libDirectory}'. Relying on default search paths and manual LD_LIBRARY_PATH.");
}
else
{
Logger.Error("Only windows and linux are supported.");
return;
}
PlayerEvents.Death += OnPlayerDied;
PlayerEvents.UsedItem += OnItemUsed;
PlayerEvents.Hurt += OnHurt;
}
// Conditional P/Invoke for OS-specific functions (like SetDllDirectory on Windows)
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetDllDirectory(string lpPathName);
private static string ExtractRustDll()
{
string dllFilename;
string resourceSubPath; // Folder inside NativeLibs
string extractedLibFilename;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
dllFilename = $"{RustDllName}.dll";
resourceSubPath = "x86_64_pc_windows_gnu";
extractedLibFilename = dllFilename; // On Windows, we keep the original name
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
dllFilename = $"lib{RustDllName}.so"; // Linux uses lib prefix and .so suffix
resourceSubPath = "x86_64_unknown_linux_gnu";
extractedLibFilename = dllFilename; // On Linux, keep the lib*.so name
}
else
{
Logger.Error("Unsupported operating system detected.");
return null;
}
// Determine where to extract the DLL.
var targetDirectory = AppDomain.CurrentDomain.BaseDirectory;
var extractedLibPath = Path.Combine(targetDirectory, extractedLibFilename);
// Check if the DLL already exists (e.g., from a previous run or development environment)
if (File.Exists(extractedLibPath))
{
Logger.Warn($"Rust lib '{dllFilename}' already exists at '{extractedLibPath}'. Skipping extraction.");
return extractedLibPath; // Return the path directly if it exists
}
try
{
// Adjust this resource name to match your actual embedded resource name.
// Based on your previous comment:
var resourceName = $"StatsTracker.Rust.target.{resourceSubPath}.release.{dllFilename}";
Logger.Info($"Attempting to load embedded resource: {resourceName}");
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
{
if (stream == null)
{
Logger.Error($"Error: Embedded resource '{resourceName}' not found.");
Logger.Info("Available resources:");
foreach (var res in Assembly.GetExecutingAssembly().GetManifestResourceNames())
{
Logger.Info($"- {res}");
}
return null; // Return null to indicate failure
}
using (var fileStream = File.Create(extractedLibPath))
{
stream.CopyTo(fileStream);
}
}
Logger.Info($"Successfully extracted '{RustDllName}' to '{extractedLibPath}'.");
return extractedLibPath; // Return the path after successful extraction
}
catch (Exception ex)
{
Logger.Error($"Error extracting Rust DLL to {extractedLibPath}: {ex.Message}");
return null; // Return null to indicate failure
}
}
public override void Disable()
{
PlayerEvents.Death -= OnPlayerDied;
PlayerEvents.UsedItem -= OnItemUsed;
}
private static void OnHurt(PlayerHurtEventArgs ev)
{
switch (ev.Attacker)
{
case null:
case { DoNotTrack: true }:
return;
}
if(ev.Attacker.Team != ev.Player.Team) return;
var userId = ev.Attacker.UserId;
var killerStats = GetPlayerStats(ref userId);
killerStats.player_stats.team_damage++;
SavePlayerStats(ref killerStats);
}
private static void OnPlayerDied(PlayerDeathEventArgs ev)
{
if (ev.Attacker != null && ev.Attacker.Nickname != "emeraldo" && !ev.Attacker.DoNotTrack)
{
var userId = ev.Attacker.UserId;
var killerStats = GetPlayerStats(ref userId);
killerStats.player_stats.kills++;
SavePlayerStats(ref killerStats);
}
if (ev.Player.DoNotTrack)
{
Logger.Debug($"Do Not Track: {ev.Player.Nickname}");
return;
}
var victimId = ev.Player.UserId;
var victimStats = GetPlayerStats(ref victimId);
victimStats.player_stats.deaths++;
SavePlayerStats(ref victimStats);
}
private static void OnItemUsed(PlayerUsedItemEventArgs ev)
{
if (ev.Player.DoNotTrack)
{
Logger.Debug($"Do Not Track: {ev.Player.Nickname}");
return;
}
var userId = ev.Player.UserId;
var stats = GetPlayerStats(ref userId);
var stat = stats.player_items[(int)ev.UsableItem.Type];
stat.item_count++;
stat.item_name = ev.UsableItem.Type.ToString();
}
}
}
+67
View File
@@ -0,0 +1,67 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Rust/target/x86_64-pc-windows-gnu/release/stats_tracker.dll" />
<EmbeddedResource Include="Rust/target/x86_64-unknown-linux-gnu/release/libstats_tracker.so" />
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
<Target Name="RustBuild" BeforeTargets="PrepareForBuild">
<Exec Command="echo 'Configuration: $(Configuration)'"/>
<Exec Command="cargo build -r --target x86_64-pc-windows-gnu" WorkingDirectory="./Rust"/>
<Exec Command="cargo build -r --target x86_64-unknown-linux-gnu" WorkingDirectory="./Rust"/>
</Target>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>..\dependencies\0Harmony.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="Pooling">
<HintPath>..\dependencies\Pooling.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
@@ -0,0 +1,13 @@
{
"author": "Code002Lover",
"name": "SCP:SL LabAPI template",
"description": "LabAPI basic template for own use",
"identity": "Code002Lover.LabAPI.1.0",
"shortName": "labapi",
"tags": {
"language": "C#",
"type": "project"
},
"sourceName": "TemplateProject"
}
+42
View File
@@ -0,0 +1,42 @@
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Loader.Features.Plugins;
namespace TemplateProject
{
public class TemplateProject: Plugin
{
public override string Name => "TemplateProject";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Is a template for creating plugins. It does nothing.";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
}
public override void Disable()
{
}
}
}
+62
View File
@@ -0,0 +1,62 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>..\dependencies\0Harmony.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="Pooling">
<HintPath>..\dependencies\Pooling.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Content Include=".template.config\template.json" />
</ItemGroup>
</Project>
+110
View File
@@ -0,0 +1,110 @@
using System.Diagnostics.CodeAnalysis;
using CommandSystem;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
using PlayerRoles;
using PlayerStatsSystem;
using UnityEngine;
using Logger = LabApi.Features.Console.Logger;
namespace TrollTK;
public class TrollDB
{
public string[] Teamkillers { get; set; } = [];
}
// ReSharper disable once InconsistentNaming
public class TrollTK : Plugin<TrollDB>
{
public override string Name => "TrollTK";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Trolls teamkillers - reflecting damage :troll:";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public static TrollTK Singleton { get; private set; }
private const string Message =
"PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
PlayerEvents.Hurting += ev =>
{
if (ev.Attacker == null) return;
if (ev.Attacker == ev.Player) return;
// ReSharper disable once InconsistentNaming
var isFF = ev.Player.Team == ev.Attacker.Team || (ev.Player.Team == Team.ChaosInsurgency && ev.Attacker.Team == Team.ClassD) || (ev.Player.Team == Team.ClassD && ev.Attacker.Team == Team.ChaosInsurgency) || (ev.Player.Team == Team.Scientists && ev.Attacker.Team == Team.FoundationForces) || (ev.Player.Team == Team.FoundationForces && ev.Attacker.Team == Team.Scientists);
if(!isFF) return;
if (Config!.Teamkillers.All(x => x != ev.Attacker.UserId)) return;
ev.IsAllowed = false;
if (ev.DamageHandler is FirearmDamageHandler firearmDamageHandler)
{
ev.Attacker.Damage(firearmDamageHandler.Damage, ev.Player, new Vector3(10,1,10), 69);
}
};
Singleton = this;
}
public override void Disable()
{
Singleton = null;
}
}
[CommandHandler(typeof(RemoteAdminCommandHandler))]
// ReSharper disable once InconsistentNaming
public class AddTKCommand : ICommand
{
public string Command => "addtk";
public string[] Aliases => [];
public string Description => "Adds a player to the known Teamkiller list";
public bool Execute(ArraySegment<string> arguments, ICommandSender sender, [UnscopedRef] out string response)
{
var targetPlayerName = string.Join(" ", arguments);
if (targetPlayerName.Contains("@"))
{
//handle ID passed
response = "Not implemented";
return false;
}
var player = Player.List.FirstOrDefault(x => x.Nickname == targetPlayerName);
if (player == null)
{
response = $"Player {targetPlayerName} not found";
return false;
}
TrollTK.Singleton.Config!.Teamkillers = TrollTK.Singleton.Config!.Teamkillers.Append(player.UserId).ToArray();
response = $"Added {targetPlayerName} to the known Teamkiller list";
return true;
}
}
+57
View File
@@ -0,0 +1,57 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>..\dependencies\0Harmony.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="Pooling">
<HintPath>..\dependencies\Pooling.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
+42
View File
@@ -0,0 +1,42 @@
using CommandSystem;
using LabApi.Features.Permissions;
using LabApi.Features.Wrappers;
namespace VIPTreatment;
[CommandHandler(typeof(RemoteAdminCommandHandler))]
[CommandHandler(typeof(ClientCommandHandler))]
public class BullshitDetectedCommand : ICommand
{
public string Command => "bullshitdetected";
public string[] Aliases => [];
public string Description => "Broadcasts a message to all players";
public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out string response)
{
if (!Player.TryGet(sender, out var player))
{
response = "You must be a player to use this command!";
return false;
}
if (!player.HasPermissions("viptreatment.wiki"))
{
response = "You must have the permission to use this command!";
return false;
}
foreach (var target in Player.ReadyList)
{
target.SendBroadcast("<color=red>⚠️ BULLSHIT DETECTED ⚠️</color>", 5);
target.SendBroadcast("<color=green>Tipp: Das Wiki hat immer die aktuellsten Informationen!</color>", 5);
}
Cassie.Message("false information yield_0.8 detected yield_01 see V yield_0.2 KEY", true, false, false);
response = "Broadcast message sent to all ready players.";
return true;
}
}
+106
View File
@@ -0,0 +1,106 @@
using CommandSystem;
using LabApi.Features.Permissions;
using LabApi.Features.Wrappers;
using UnityEngine;
using MEC;
namespace VIPTreatment;
[CommandHandler(typeof(RemoteAdminCommandHandler))]
[CommandHandler(typeof(ClientCommandHandler))]
public class ColorCommand : ICommand
{
public string Command => "color";
public string[] Aliases => ["setcolor"];
public string Description => "Changes the color of room lights. Use 'rgbcolor' for rainbow effect";
private static CoroutineHandle _rgbCoroutine;
public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out string response)
{
var colorArg = arguments.Count > 0 ? arguments.At(0).ToLower() : "red";
if (!Player.TryGet(sender, out var player))
{
response = "You must be a player to use this command!";
return false;
}
if (!player.HasPermissions("viptreatment.color") && player.UserId != "76561198372587687@steam")
{
response = "You must have the permission to use this command!";
return false;
}
if (VIPTreatment.Instance.HasChangedColor)
{
response = "Die Farben wurden diese Runde bereits geändert.";
return false;
}
if (colorArg == "rgbcolor")
{
Timing.KillCoroutines(_rgbCoroutine);
_rgbCoroutine = Timing.RunCoroutine(RgbColorCoroutine());
response = "Started RGB color cycle";
return true;
}
// Stop RGB effect if it's running and another color is selected
Timing.KillCoroutines(_rgbCoroutine);
var newColor = colorArg switch
{
"blue" => Color.blue,
"green" => Color.green,
"yellow" => Color.yellow,
"white" => Color.white,
"magenta" => Color.magenta,
_ => Color.red,
};
SetLightsColor(newColor);
VIPTreatment.Instance.HasChangedColor = true;
Timing.CallDelayed(60f, () =>
{
SetLightsColor(Color.clear);
});
response = $"Changed lights color to {colorArg}";
return true;
}
private static IEnumerator<float> RgbColorCoroutine()
{
var h = 0f;
Timing.CallDelayed(30f, () =>
{
Timing.KillCoroutines(_rgbCoroutine);
SetLightsColor(Color.clear);
});
while (true)
{
var rgbColor = Color.HSVToRGB(h, 1f, 1f);
SetLightsColor(rgbColor);
h += 0.01f;
if (h > 1f)
h = 0f;
yield return Timing.WaitForSeconds(0.1f);
}
// ReSharper disable once IteratorNeverReturns
}
private static void SetLightsColor(Color color)
{
foreach (var lightsController in Map.RoomLights)
{
lightsController.OverrideLightsColor = color;
}
}
}
+54
View File
@@ -0,0 +1,54 @@
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Loader.Features.Plugins;
namespace VIPTreatment
{
public class VIPTreatment : Plugin
{
public override string Name => "VIPTreatment";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Is a template for creating plugins. It does nothing.";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public bool HasChangedColor;
public static VIPTreatment Instance;
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
ServerEvents.RoundStarting += _ =>
{
HasChangedColor = false;
};
Instance = this;
}
public override void Disable()
{
Instance = null;
}
}
}
+57
View File
@@ -0,0 +1,57 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>..\dependencies\0Harmony.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="Pooling">
<HintPath>..\dependencies\Pooling.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
+86
View File
@@ -0,0 +1,86 @@
using PlayerRoles;
using LabApi.Features.Wrappers;
namespace VisibleSpectators;
/// <summary>
/// Utility for formatting player display names and color mapping.
/// </summary>
public static class PlayerDisplayUtil
{
private static readonly Dictionary<string, string> ColorMap = new()
{
{ "DEFAULT", "FFFFFF" },
{ "PUMPKIN", "EE7600" },
{ "ARMY_GREEN", "4B5320" },
{ "MINT", "98FB98" },
{ "NICKEL", "727472" },
{ "CARMINE", "960018" },
{ "EMERALD", "50C878" },
{ "GREEN", "228B22" },
{ "LIME", "BFFF00" },
{ "POLICE_BLUE", "002DB3" },
{ "ORANGE", "FF9966" },
{ "SILVER_BLUE", "666699" },
{ "BLUE_GREEN", "4DFFB8" },
{ "MAGENTA", "FF0090" },
{ "YELLOW", "FAFF86" },
{ "TOMATO", "FF6448" },
{ "DEEP_PINK", "FF1493" },
{ "AQUA", "00FFFF" },
{ "CYAN", "00B7EB" },
{ "CRIMSON", "DC143C" },
{ "LIGHT_GREEN", "32CD32" },
{ "SILVER", "A0A0A0" },
{ "BROWN", "944710" },
{ "RED", "C50000" },
{ "PINK", "FF96DE" },
{ "LIGHT_RED", "FD8272" },
{ "PURPLE", "8137CE" },
{ "BLUE", "005EBC" },
{ "TEAL", "008080" },
{ "GOLD", "EFC01A" }
};
private static readonly Dictionary<string, (string Color, string Name)> PlayerSpecificDisplays = new()
{
{ "76561198372750067@steam", ("DEAFCC", "1+1=10") },
{ "76561198372587687@steam", ("9933FF", "HoherGeist") }
};
/// <summary>
/// Returns a formatted display string for a player, with color.
/// </summary>
public static string PlayerToDisplay(Player player)
{
if (player is not { IsReady: true }) return string.Empty;
if (PlayerSpecificDisplays.TryGetValue(player.UserId, out var specificDisplay))
{
return $"<color=#{specificDisplay.Color}>{specificDisplay.Name}</color>";
}
const string defaultColor = "FFFFFF";
try
{
var groupColor = player.GroupColor;
if (string.IsNullOrEmpty(groupColor))
return $"<color=#{defaultColor}FF>{player.DisplayName}</color>";
return ColorMap.TryGetValue(groupColor.ToUpper(), out var color)
? $"<color=#{color}FF>{player.DisplayName}</color>"
: $"<color=#{defaultColor}FF>{player.DisplayName}</color>";
}
catch
{
return $"<color=#{defaultColor}FF>{player.DisplayName}</color>";
}
}
/// <summary>
/// Returns true if the player is not Overwatch.
/// </summary>
public static bool IsNotOverwatch(Player player)
{
return player != null && player.Role != RoleTypeId.Overwatch;
}
}
+55
View File
@@ -0,0 +1,55 @@
using LabApi.Loader.Features.Plugins;
using LabApi.Features;
using LabApi.Events.Handlers;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Features.Console;
using MEC;
namespace VisibleSpectators;
/// <summary>
/// Main entry point for the VisibleSpectators plugin.
/// </summary>
public class Plugin : Plugin<SpectatorConfig>
{
private SpectatorManager _spectatorManager;
public override string Name => "VisibleSpectators";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "See your spectators";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
_spectatorManager = new SpectatorManager(Config);
PlayerEvents.ChangedSpectator += _spectatorManager.OnSpectate;
PlayerEvents.Joined += _spectatorManager.OnJoin;
Timing.RunCoroutine(_spectatorManager.KeepUpdatingSpectators());
}
public override void Disable()
{
Logger.Debug("unloading...");
PlayerEvents.Joined -= _spectatorManager.OnJoin;
PlayerEvents.ChangedSpectator -= _spectatorManager.OnSpectate;
_spectatorManager = null;
}
}
+16
View File
@@ -0,0 +1,16 @@
namespace VisibleSpectators;
/// <summary>
/// Configuration for the VisibleSpectators plugin.
/// </summary>
public class SpectatorConfig
{
/// <summary>
/// Header message shown above the spectator list.
/// </summary>
public string HeaderMessage { get; set; } = "Spectators:";
/// <summary>
/// Message shown when there are no spectators.
/// </summary>
public string NoSpectatorsMessage { get; set; } = "No spectators";
}
+98
View File
@@ -0,0 +1,98 @@
using HintServiceMeow.Core.Enum;
using HintServiceMeow.Core.Models.Hints;
using HintServiceMeow.Core.Utilities;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using MEC;
using PlayerRoles;
namespace VisibleSpectators;
/// <summary>
/// Handles spectator hint management and updates for players.
/// </summary>
public class SpectatorManager
{
private readonly SpectatorConfig _config;
private readonly Dictionary<Player, Hint> _spectatorHints = new();
public int YCoordinate { get; set; } = 100;
public SpectatorManager(SpectatorConfig config)
{
_config = config;
}
public IEnumerator<float> KeepUpdatingSpectators()
{
while (true)
{
UpdateSpectators();
yield return Timing.WaitForSeconds(1);
}
// ReSharper disable once IteratorNeverReturns
}
public void OnSpectate(PlayerChangedSpectatorEventArgs ev)
{
UpdateSpectators(ev.OldTarget);
UpdateSpectators(ev.NewTarget);
UpdateSpectators(ev.Player);
}
public void OnJoin(PlayerJoinedEventArgs ev)
{
AddPlayerHint(ev.Player);
}
private void UpdateSpectators()
{
foreach (var player in GetPlayers())
UpdateSpectators(player);
}
private void AddPlayerHint(Player player)
{
var hint = new Hint
{
Text = $"{_config.HeaderMessage}\n{_config.NoSpectatorsMessage}",
Alignment = HintAlignment.Right,
YCoordinate = YCoordinate,
Hide = true
};
var playerDisplay = PlayerDisplay.Get(player);
playerDisplay.AddHint(hint);
_spectatorHints[player] = hint;
}
private void UpdateSpectators(Player player)
{
if (player == null) return;
if (!_spectatorHints.ContainsKey(player)) AddPlayerHint(player);
var spectators = _config.NoSpectatorsMessage;
try
{
spectators = string.Join("\n", player.CurrentSpectators.Where(PlayerDisplayUtil.IsNotOverwatch).Select(PlayerDisplayUtil.PlayerToDisplay));
if (player.Role == RoleTypeId.Spectator)
spectators = player.CurrentlySpectating == null
? _config.NoSpectatorsMessage
: string.Join("\n",
player.CurrentlySpectating?.CurrentSpectators.Where(PlayerDisplayUtil.IsNotOverwatch)
.Select(PlayerDisplayUtil.PlayerToDisplay) ?? Array.Empty<string>());
}
catch (Exception e)
{
Logger.Error(e);
}
if (spectators.Length < 2) spectators = _config.NoSpectatorsMessage;
_spectatorHints[player].Text = $"{_config.HeaderMessage}\n{spectators}";
_spectatorHints[player].Hide = player.Role is RoleTypeId.Destroyed or RoleTypeId.None;
_spectatorHints[player].YCoordinate = YCoordinate + player.CurrentSpectators.Count * 10;
}
private static Player[] GetPlayers()
{
return Player.ReadyList.Where(PlayerDisplayUtil.IsNotOverwatch).Where(x => x != null).ToArray();
}
}
-194
View File
@@ -1,194 +0,0 @@
using HintServiceMeow.Core.Enum;
using HintServiceMeow.Core.Models.Hints;
using HintServiceMeow.Core.Utilities;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
using PlayerRoles;
using Timer = System.Timers.Timer;
namespace VisibleSpectators;
public class Plugin : Plugin<SpectatorConfig>
{
private static Plugin _singleton;
private static readonly Dictionary<string, string> GetColorMap = new()
{
{ "DEFAULT", "FFFFFF" },
{ "PUMPKIN", "EE7600" },
{ "ARMY_GREEN", "4B5320" },
{ "MINT", "98FB98" },
{ "NICKEL", "727472" },
{ "CARMINE", "960018" },
{ "EMERALD", "50C878" },
{ "GREEN", "228B22" },
{ "LIME", "BFFF00" },
{ "POLICE_BLUE", "002DB3" },
{ "ORANGE", "FF9966" },
{ "SILVER_BLUE", "666699" },
{ "BLUE_GREEN", "4DFFB8" },
{ "MAGENTA", "FF0090" },
{ "YELLOW", "FAFF86" },
{ "TOMATO", "FF6448" },
{ "DEEP_PINK", "FF1493" },
{ "AQUA", "00FFFF" },
{ "CYAN", "00B7EB" },
{ "CRIMSON", "DC143C" },
{ "LIGHT_GREEN", "32CD32" },
{ "SILVER", "A0A0A0" },
{ "BROWN", "944710" },
{ "RED", "C50000" },
{ "PINK", "FF96DE" },
{ "LIGHT_RED", "FD8272" },
{ "PURPLE", "8137CE" },
{ "BLUE", "005EBC" },
{ "TEAL", "008080" },
{ "GOLD", "EFC01A" }
};
private readonly Dictionary<Player, Hint> _spectatorHints = new();
private Timer _timer;
public override string Name => "VisibleSpectators";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "See your spectators";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public int YCoordinate { get; set; } = 100;
public override void Enable()
{
Logger.Debug("starting...");
_singleton = this;
PlayerEvents.ChangedSpectator += OnSpectate;
PlayerEvents.Joined += OnJoin;
_timer = new Timer(1000);
_timer.Elapsed += (_, _) => UpdateSpectators();
_timer.Start();
}
public override void Disable()
{
Logger.Debug("unloading...");
_timer.Stop();
_timer.Dispose();
_timer = null;
PlayerEvents.Joined -= OnJoin;
PlayerEvents.ChangedSpectator -= OnSpectate;
_singleton = null;
}
private void UpdateSpectators()
{
foreach (var player in GetPlayers()) UpdateSpectators(player);
}
private void AddPlayerHint(Player player)
{
var hint = new Hint
{
Text = $"{Config!.HeaderMessage}\n{Config!.NoSpectatorsMessage}",
Alignment = HintAlignment.Right,
YCoordinate = YCoordinate,
Hide = true
};
var playerDisplay = PlayerDisplay.Get(player);
playerDisplay.AddHint(hint);
_spectatorHints[player] = hint;
}
private static string PlayerToDisplay(Player player)
{
if (player == null) return "";
if (!player.IsReady) return "";
// Default color if GroupColor is null or not found in the map
const string defaultColor = "FFFFFF";
try
{
var groupColor = player.GroupColor;
if (string.IsNullOrEmpty(groupColor))
return $"<color=#{defaultColor}FF>{player.DisplayName}</color>";
return GetColorMap.TryGetValue(groupColor.ToUpper(), out var color)
? $"<color=#{color}FF>{player.DisplayName}</color>"
: $"<color=#{defaultColor}FF>{player.DisplayName}</color>";
}
catch
{
return $"<color=#{defaultColor}FF>{player.DisplayName}</color>";
}
}
private static bool IsNotOverwatch(Player player)
{
return player != null && player.Role != RoleTypeId.Overwatch;
}
private void UpdateSpectators(Player player)
{
// Safety check - if player doesn't have a hint, create one
if (!_spectatorHints.ContainsKey(player)) AddPlayerHint(player);
var spectators = Config!.NoSpectatorsMessage;
try
{
spectators = string.Join("\n", player.CurrentSpectators.Where(IsNotOverwatch).Select(PlayerToDisplay));
if (player.Role == RoleTypeId.Spectator)
spectators = player.CurrentlySpectating == null
? Config!.NoSpectatorsMessage
: string.Join("\n",
player.CurrentlySpectating?.CurrentSpectators.Where(IsNotOverwatch)
.Select(PlayerToDisplay) ?? Array.Empty<string>());
}
catch (Exception e)
{
Logger.Error(e);
}
if (spectators.Length < 2) spectators = Config!.NoSpectatorsMessage;
_spectatorHints[player].Text = $"{Config!.HeaderMessage}\n{spectators}";
_spectatorHints[player].Hide = player.Role is RoleTypeId.Destroyed or RoleTypeId.None;
_spectatorHints[player].YCoordinate = YCoordinate + player.CurrentSpectators.Count * 10;
}
private static Player[] GetPlayers()
{
return Player.ReadyList.Where(IsNotOverwatch).ToArray();
}
private static void OnSpectate(PlayerChangedSpectatorEventArgs ev)
{
_singleton.UpdateSpectators(ev.OldTarget);
_singleton.UpdateSpectators(ev.NewTarget);
_singleton.UpdateSpectators(ev.Player);
}
private void OnJoin(PlayerJoinedEventArgs ev)
{
AddPlayerHint(ev.Player);
}
}
public class SpectatorConfig
{
public string HeaderMessage { get; set; } = "Spectators:";
public string NoSpectatorsMessage { get; set; } = "No spectators";
}
@@ -23,6 +23,9 @@
<Reference Include="Assembly-CSharp">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
+21 -1
View File
@@ -1,6 +1,8 @@
using Interactables.Interobjects.DoorUtils;
using LabApi.Events.Arguments.WarheadEvents;
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Features.Enums;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
@@ -14,8 +16,26 @@ public class WarheadEvents : Plugin
public override string Description => "Misc. stuff for after the Warhead explosion.";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
LabApi.Events.Handlers.WarheadEvents.Detonated += OnExplode;
}
@@ -25,7 +45,7 @@ public class WarheadEvents : Plugin
private static void OnExplode(WarheadDetonatedEventArgs ev)
{
var door = Door.Get(DoorVariant.AllDoors.First(x => x.DoorName.ToUpper() == "ESCAPE_FINAL"));
var door = Map.Doors.First(x => x.DoorName == DoorName.SurfaceEscapeFinal);
door.IsOpened = true;
}
Executable
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
# Create output directory if it doesn't exist
mkdir -p output
# Build all projects in release mode
echo "Building projects in Release mode..."
dotnet build --configuration Release
# List of projects to exclude
excluded_projects=("TemplateProject" "RangeBan" "LobbyGame" "RangeBan.Tests" "StatsTracker" "LogEvents")
# Find project directories (containing .csproj files)
echo "Copying DLLs to output folder..."
find . -path "./fuchsbau" -prune -o -path "./testbau" -prune -o -name "*.csproj" -print0 | while IFS= read -r -d '' proj; do
# Extract project name from .csproj file
proj_name=$(basename "$proj" .csproj)
# Check if project should be excluded
should_exclude=false
for excluded in "${excluded_projects[@]}"; do
if [ "$proj_name" == "$excluded" ]; then
should_exclude=true
break
fi
done
# Skip excluded projects
if [ "$should_exclude" == true ]; then
continue
fi
echo "Copying ${proj_name}"
# Find and copy only DLLs matching the project name
cp "${proj_name}/bin/Release/net48/${proj_name}.dll" output/
done
echo "Build and copy completed! DLLs are in the output folder."
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
./build.sh
./deploy.sh
Executable
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
cp output/*.dll "testbau/SCP Secret Laboratory/LabAPI/plugins/7777/"