Merge remote-tracking branch 'origin/dev' into feat/autowarden

This commit is contained in:
ShookEagle
2025-08-08 09:47:22 -05:00
20 changed files with 912 additions and 68 deletions

View File

@@ -64,30 +64,30 @@ public class LastRequestManager(ILRLocale messages, IServiceProvider provider)
private ILastRequestFactory? factory;
public bool IsLREnabledForRound { get; set; } = true;
public bool ShouldBlockDamage(CCSPlayerController player,
public bool ShouldBlockDamage(CCSPlayerController victim,
CCSPlayerController? attacker) {
if (!IsLREnabled) return false;
if (attacker == null || !attacker.IsReal()) return false;
var playerLR = ((ILastRequestManager)this).GetActiveLR(player);
var victimLR = ((ILastRequestManager)this).GetActiveLR(victim);
var attackerLR = ((ILastRequestManager)this).GetActiveLR(attacker);
if (playerLR == null && attackerLR == null)
if (victimLR == null && attackerLR == null)
// Neither of them is in an LR
return false;
if (playerLR == null != (attackerLR == null)) {
if (victimLR == null != (attackerLR == null)) {
// One of them is in an LR
messages.DamageBlockedInsideLastRequest.ToCenter(attacker);
return true;
}
// Both of them are in LR, verify they're in same LR
if (playerLR == null) return false;
if (victimLR == null) return false;
if (playerLR.Prisoner.Equals(attacker) || playerLR.Guard.Equals(attacker))
// Same LR, allow damage
if (victimLR.Prisoner.Equals(attacker) || victimLR.Guard.Equals(attacker))
// The person attacking is the victim's LR participant, allow damage
return false;
messages.DamageBlockedNotInSameLR.ToCenter(attacker);

View File

@@ -94,6 +94,15 @@ public class MuteSystem(IServiceProvider provider)
return HookResult.Continue;
}
[GameEventHandler]
public HookResult OnDeath(EventPlayerDeath @event, GameEventInfo info) {
var player = @event.Userid;
if (player == null || !player.IsReal()) return HookResult.Continue;
mute(player);
return HookResult.Continue;
}
private void unmuteGuards() {
foreach (var player in Utilities.GetPlayers()
.Where(player => player is {

View File

@@ -17,6 +17,7 @@ using Jailbreak.Public.Behaviors;
using Jailbreak.Public.Extensions;
using Jailbreak.Public.Mod.LastRequest;
using Jailbreak.Public.Mod.Rebel;
using Jailbreak.Public.Utils;
using Microsoft.Extensions.DependencyInjection;
using MStatsShared;
using Timer = CounterStrikeSharp.API.Modules.Timers.Timer;
@@ -46,12 +47,6 @@ public class C4Behavior(IC4Locale ic4Locale, IRebelService rebelService,
private int roundStart = 0;
// EmitSound(CBaseEntity* pEnt, const char* sSoundName, int nPitch, float flVolume, float flDelay)
private readonly MemoryFunctionVoid<CBaseEntity, string, int, float, float>
// ReSharper disable once InconsistentNaming
CBaseEntity_EmitSoundParamsLinux = new(
"48 B8 ? ? ? ? ? ? ? ? 55 48 89 E5 41 55 41 54 49 89 FC 53 48 89 F3"); // LINUX ONLY.
private readonly Dictionary<int, int> deathToKiller = new();
private bool giveNextRound = true;
@@ -78,14 +73,13 @@ public class C4Behavior(IC4Locale ic4Locale, IRebelService rebelService,
API.Stats?.PushStat(new ServerStat("JB_BOMB_ATTEMPT",
$"{pos.X:F2} {pos.Y:F2} {pos.Z:F2}"));
tryEmitSound(player, "jb.jihad", 1, 1f, 0f);
bombs[bombEntity].IsDetonating = true;
rebelService.MarkRebel(player);
Server.RunOnTick(Server.TickCount + (int)(64 * delay),
() => detonate(player, bombEntity));
player.EmitSound("jb.jihad");
}
public void TryGiveC4ToRandomTerrorist() {
@@ -111,18 +105,21 @@ public class C4Behavior(IC4Locale ic4Locale, IRebelService rebelService,
public void Start(BasePlugin basePlugin) {
plugin = basePlugin;
plugin.RegisterListener<Listeners.OnTick>(playerUseC4ListenerCallback);
plugin.RegisterListener<Listeners.OnPlayerButtonsChanged>(
playerButtonsChanged);
}
private void playerUseC4ListenerCallback() {
private void playerButtonsChanged(CCSPlayerController player,
PlayerButtons pressed, PlayerButtons released) {
if ((pressed & PlayerButtons.Use) == 0) return;
foreach (var (bomb, meta) in bombs) {
if (!bomb.IsValid) continue;
if (meta.IsDetonating) continue;
if (!bomb.IsValid || meta.IsDetonating) continue;
var bombCarrier = bomb.OwnerEntity.Value?.As<CCSPlayerPawn>()
.Controller.Value?.As<CCSPlayerController>();
if (bombCarrier == null || !bombCarrier.IsValid
|| (bombCarrier.Buttons & PlayerButtons.Use) == 0)
|| bombCarrier.Slot != player.Slot)
continue;
var activeWeapon = bombCarrier.PlayerPawn.Value?.WeaponServices
@@ -239,17 +236,6 @@ public class C4Behavior(IC4Locale ic4Locale, IRebelService rebelService,
if (Server.TickCount - roundStart < CV_C4_DELAY.Value * 64) return;
tryEmitSound(player, "jb.jihadExplosion", 1, 1f, 0f);
var particleSystemEntity =
Utilities.CreateEntityByName<CParticleSystem>("info_particle_system")!;
particleSystemEntity.EffectName =
"particles/explosions_fx/explosion_c4_500.vpcf";
particleSystemEntity.StartActive = true;
particleSystemEntity.Teleport(player.PlayerPawn.Value!.AbsOrigin!,
new QAngle(), new Vector());
particleSystemEntity.DispatchSpawn();
var killed = 0;
/* Calculate damage here, only applies to alive CTs. */
var lrs = provider.GetRequiredService<ILastRequestManager>();
@@ -280,6 +266,10 @@ public class C4Behavior(IC4Locale ic4Locale, IRebelService rebelService,
}
}
// If they didn't have the C4 make sure to remove it.
player.CommitSuicide(true, true);
bombs.Remove(bomb);
if (API.Gangs != null && killed > 0) {
var eco = API.Gangs.Services.GetService<IEcoManager>();
if (eco != null) {
@@ -289,17 +279,18 @@ public class C4Behavior(IC4Locale ic4Locale, IRebelService rebelService,
}
}
player.EmitSound("jb.jihadExplosion");
var particleSystemEntity =
Utilities.CreateEntityByName<CParticleSystem>("info_particle_system")!;
particleSystemEntity.EffectName =
"particles/explosions_fx/explosion_c4_500.vpcf";
particleSystemEntity.StartActive = true;
particleSystemEntity.Teleport(player.PlayerPawn.Value!.AbsOrigin!,
new QAngle(), new Vector());
particleSystemEntity.DispatchSpawn();
API.Stats?.PushStat(new ServerStat("JB_BOMB_EXPLODED", killed.ToString()));
// If they didn't have the C4 make sure to remove it.
player.CommitSuicide(true, true);
bombs.Remove(bomb);
}
private void tryEmitSound(CBaseEntity entity, string soundEventName,
int pitch, float volume, float delay) {
CBaseEntity_EmitSoundParamsLinux.Invoke(entity, soundEventName, pitch,
volume, delay);
}
private class C4Metadata(bool isDetonating) {

View File

@@ -4,6 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>

View File

@@ -12,18 +12,22 @@ public class SpecialDayFactory(IServiceProvider provider) : ISpecialDayFactory {
public AbstractSpecialDay CreateSpecialDay(SDType type) {
return type switch {
SDType.BHOP => new BHopDay(plugin, provider),
SDType.CUSTOM => new CustomDay(plugin, provider),
SDType.FFA => new FFADay(plugin, provider),
SDType.GUNGAME => new GunGameDay(plugin, provider),
SDType.HNS => new HideAndSeekDay(plugin, provider),
// SDType.INFECTION => new InfectionDay(plugin, provider),
SDType.NOSCOPE => new NoScopeDay(plugin, provider),
SDType.OITC => new OneInTheChamberDay(plugin, provider),
SDType.SPEEDRUN => new SpeedrunDay(plugin, provider),
SDType.TELEPORT => new TeleportDay(plugin, provider),
SDType.WARDAY => new WardayDay(plugin, provider),
_ => throw new NotImplementedException()
SDType.BHOP => new BHopDay(plugin, provider),
SDType.CUSTOM => new CustomDay(plugin, provider),
SDType.FFA => new FFADay(plugin, provider),
SDType.FOG => new FogDay(plugin, provider),
SDType.GUNGAME => new GunGameDay(plugin, provider),
SDType.GHOST => new GhostDay(plugin, provider),
SDType.HE => new HEDay(plugin, provider),
SDType.HNS => new HideAndSeekDay(plugin, provider),
SDType.INFECTION => new InfectionDay(plugin, provider),
SDType.NOSCOPE => new NoScopeDay(plugin, provider),
SDType.OITC => new OneInTheChamberDay(plugin, provider),
SDType.ROCKETJUMP => new RocketJumpDay(plugin, provider),
SDType.SPEEDRUN => new SpeedrunDay(plugin, provider),
SDType.TELEPORT => new TeleportDay(plugin, provider),
SDType.WARDAY => new WardayDay(plugin, provider),
_ => throw new NotImplementedException()
};
}

View File

@@ -0,0 +1,206 @@
using System.Drawing;
using System.Runtime.CompilerServices;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory;
using Jailbreak.English.SpecialDay;
using Jailbreak.Formatting.Extensions;
using Jailbreak.Formatting.Views.SpecialDay;
using Jailbreak.Public.Mod.SpecialDay;
using Jailbreak.Public.Mod.SpecialDay.Enums;
namespace Jailbreak.SpecialDay.SpecialDays;
public class FogDay(BasePlugin plugin, IServiceProvider provider)
: AbstractSpecialDay(plugin, provider), ISpecialDayMessageProvider {
public override SDType Type => SDType.FOG;
private FogDayLocale Msg => (FogDayLocale)Locale;
public ISDInstanceLocale Locale => new FogDayLocale();
public override SpecialDaySettings Settings => new FogSettings();
private CFogController? fogController;
private nint originalSkyMat;
private nint originalSkyMatLighting;
private CEnvSky? skyEntity;
private float targetFogEnd = 2000f;
private float fogStepPerTick;
private bool shouldGrowFog;
public override void Setup() {
Plugin.RegisterListener<Listeners.OnTick>(onTick);
Plugin.RegisterEventHandler<EventPlayerDeath>(onDeath);
cacheOriginalSky();
setFog(true);
setSky(true);
Timers[20] += () => Locale.BeginsIn(20).ToAllChat();
Timers[25] += () => {
Msg.FogComingIn().ToAllChat();
setTargetFogDistance(300, 10);
};
Timers[30] += () => Msg.BeginsIn(10).ToAllChat();
Timers[35] += () => Msg.BeginsIn(5).ToAllChat();
Timers[40] += () => {
Execute();
Msg.BeginsIn(0).ToAllChat();
};
Timers[97] += () => Msg.FogExpandsIn(3).ToAllChat();
Timers[98] += () => Msg.FogExpandsIn(2).ToAllChat();
Timers[99] += () => Msg.FogExpandsIn(1).ToAllChat();
Timers[100] += () => {
setTargetFogDistance(3000, 180);
Msg.FogExpandsIn(0).ToAllChat();
};
base.Setup();
}
private unsafe void setSky(bool set) {
if (skyEntity == null) return;
var mat = set ? nint.Zero : originalSkyMat;
var matLit = set ? nint.Zero : originalSkyMatLighting;
Unsafe.Write((void*)skyEntity.SkyMaterial.Handle, mat);
Unsafe.Write((void*)skyEntity.SkyMaterialLightingOnly.Handle, matLit);
Utilities.SetStateChanged(skyEntity, "CEnvSky", "m_hSkyMaterial");
Utilities.SetStateChanged(skyEntity, "CEnvSky",
"m_hSkyMaterialLightingOnly");
skyEntity.BrightnessScale = 1.0f;
Utilities.SetStateChanged(skyEntity, "CEnvSky", "m_flBrightnessScale");
}
private void setFog(bool set) {
if (!set) {
fogController?.Remove();
fogController = null;
return;
}
fogController ??=
Utilities.CreateEntityByName<CFogController>("env_fog_controller");
fogController?.DispatchSpawn();
if (fogController == null) return;
var fog = fogController.Fog;
fog.Enable = true;
fog.Blend = true;
fog.ColorPrimary = Color.Black;
fog.Exponent = 0.1f;
fog.Maxdensity = 1f;
fog.Start = 0;
fog.End = targetFogEnd;
fog.Farz = targetFogEnd * 1.04f;
foreach (var field in new[] {
"colorPrimary", "start", "end", "farz", "maxdensity", "exponent",
"enable", "blend",
}) {
Utilities.SetStateChanged(fogController, "CFogController", "m_fog",
Schema.GetSchemaOffset("fogparams_t", field));
}
var vis = Utilities
.FindAllEntitiesByDesignerName<CPlayerVisibility>("env_player_visibility")
.FirstOrDefault();
if (vis != null) {
vis.FogMaxDensityMultiplier = 1f;
Utilities.SetStateChanged(vis, "CPlayerVisibility",
"m_flFogMaxDensityMultiplier");
}
foreach (var pawn in Utilities.GetPlayers()
.Select(p => p.Pawn.Value)
.OfType<CBasePlayerPawn>())
pawn.AcceptInput("SetFogController", activator: fogController,
value: "!activator");
}
private void onTick() {
if (!shouldGrowFog || fogController == null) return;
var fog = fogController.Fog;
if (Math.Abs(fog.End - targetFogEnd) < Math.Abs(fogStepPerTick)) {
fog.End = targetFogEnd;
fog.Farz = targetFogEnd * 1.04f;
shouldGrowFog = false;
} else {
fog.End += fogStepPerTick;
fog.Farz = fog.End + 10f;
}
Utilities.SetStateChanged(fogController, "CFogController", "m_fog",
Schema.GetSchemaOffset("fogparams_t", "end"));
Utilities.SetStateChanged(fogController, "CFogController", "m_fog",
Schema.GetSchemaOffset("fogparams_t", "farz"));
}
private HookResult onDeath(EventPlayerDeath @event, GameEventInfo info) {
var pawn = @event.Userid?.PlayerPawn.Value;
if (pawn == null) return HookResult.Continue;
Server.NextFrame(() => {
pawn.AcceptInput("SetFogController", activator: fogController,
value: "!activator");
});
return HookResult.Continue;
}
override protected HookResult OnEnd(EventRoundEnd @event,
GameEventInfo info) {
setSky(false);
setFog(false);
if (fogController != null) fogController.Remove();
Plugin.RemoveListener<Listeners.OnTick>(onTick);
Plugin.DeregisterEventHandler<EventPlayerDeath>(onDeath);
return base.OnEnd(@event, info);
}
private unsafe void cacheOriginalSky() {
skyEntity = Utilities.FindAllEntitiesByDesignerName<CEnvSky>("env_sky")
.FirstOrDefault();
if (skyEntity == null) return;
originalSkyMat = Unsafe.Read<nint>((void*)skyEntity.SkyMaterial.Handle);
originalSkyMatLighting =
Unsafe.Read<nint>((void*)skyEntity.SkyMaterialLightingOnly.Handle);
}
private void setTargetFogDistance(int distance, float durationInSeconds) {
if (fogController == null) return;
var currentEnd = fogController.Fog.End;
var totalTicks = durationInSeconds * 64f;
fogStepPerTick = (distance - currentEnd) / totalTicks;
targetFogEnd = distance;
shouldGrowFog = true;
}
public class FogSettings : SpecialDaySettings {
private readonly Random rng;
public FogSettings() {
TTeleport = TeleportType.ARMORY;
CtTeleport = TeleportType.ARMORY;
OpenCells = true;
StripToKnife = false;
rng = new Random();
WithFriendlyFire();
}
public override float FreezeTime(CCSPlayerController player) {
return rng.NextSingle() * 5 + 2;
}
}
}

View File

@@ -0,0 +1,153 @@
using System.Runtime.InteropServices;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Timers;
using CounterStrikeSharp.API.Modules.Utils;
using Jailbreak.English.SpecialDay;
using Jailbreak.Formatting.Views.SpecialDay;
using Jailbreak.Public.Extensions;
using Jailbreak.Public.Mod.SpecialDay;
using Jailbreak.Public.Mod.SpecialDay.Enums;
using Jailbreak.Public.Utils;
using Jailbreak.Validator;
using Timer = CounterStrikeSharp.API.Modules.Timers.Timer;
namespace Jailbreak.SpecialDay.SpecialDays;
public class GhostDay(BasePlugin plugin, IServiceProvider provider)
: FFADay(plugin, provider), ISpecialDayMessageProvider {
public static readonly FakeConVar<float> CV_VISIBLE_DURATION = new(
"css_jb_sd_ghost_visible_duration",
"Amount of time players spend visible per cycle", 5f,
ConVarFlags.FCVAR_NONE, new NonZeroRangeValidator<float>(1f, 30f));
public static readonly FakeConVar<float> CV_INVISIBLE_DURATION = new(
"css_jb_sd_ghost_invisible_duration",
"Amount of time players spend invisible per cycle", 5f,
ConVarFlags.FCVAR_NONE, new NonZeroRangeValidator<float>(1f, 30f));
private static readonly
MemoryFunctionVoid<nint, nint, int, nint, nint, nint, int, bool>
CHECK_TRANSMIT = new(GameData.GetSignature("CheckTransmit"));
private static readonly int CHECK_TRANSMIT_PLAYER_SLOT_CACHE =
GameData.GetOffset("CheckTransmitPlayerSlot");
private static float CycleDuration
=> CV_VISIBLE_DURATION.Value + CV_INVISIBLE_DURATION.Value;
private bool allPlayersVisible;
private float timeElapsed;
private Timer? ghostTimer;
[StructLayout(LayoutKind.Sequential)]
public struct CCheckTransmitInfo
{
public CFixedBitVecBase m_pTransmitEntity;
}
[StructLayout(LayoutKind.Sequential)]
public readonly unsafe struct CFixedBitVecBase
{
private const int LOG2_BITS_PER_INT = 5;
private const int MAX_EDICT_BITS = 14;
private const int BITS_PER_INT = 32;
private const int MAX_EDICTS = 1 << MAX_EDICT_BITS;
private readonly uint* m_Ints;
public void Clear(int bitNum)
{
if (bitNum is < 0 or >= MAX_EDICTS)
return;
var pInt = m_Ints + (bitNum >> LOG2_BITS_PER_INT);
*pInt &= ~(uint)(1 << (bitNum & BITS_PER_INT - 1));
}
}
public override SDType Type => SDType.GHOST;
public override ISDInstanceLocale Locale
=> new SoloDayLocale("Ghost War",
"Now you see me… now you dont! Fight through flickering visibility!");
public override SpecialDaySettings Settings => new GhostSettings();
public override void Setup() {
CHECK_TRANSMIT.Hook(onTransmit, HookMode.Post);
Server.NextFrameAsync(() => { setVisibility(false); });
base.Setup();
}
public override void Execute() {
base.Execute();
timeElapsed = 0f;
setVisibility(true);
ghostTimer = Plugin.AddTimer(1f, () => {
timeElapsed += 1f;
var mod = timeElapsed % CycleDuration;
var shouldBeVisible = mod < CV_VISIBLE_DURATION.Value;
var timeLeft = (int)((shouldBeVisible ?
CV_VISIBLE_DURATION.Value : CycleDuration) - mod);
if (shouldBeVisible != allPlayersVisible)
setVisibility(shouldBeVisible);
foreach (var player in PlayerUtil.GetAlive()
.Where(p => p.IsValid))
player.PrintToCenter($"{(allPlayersVisible
? "Visible" : "Hidden")} for: {timeLeft}s");
}, TimerFlags.REPEAT);
}
private unsafe HookResult onTransmit(DynamicHook hook) {
if (allPlayersVisible) return HookResult.Continue;
var ppInfoList = (nint*)hook.GetParam<nint>(1);
var infoCount = hook.GetParam<int>(2);
var players = Utilities.GetPlayers()
.Where(p => p is { IsValid: true, PawnIsAlive: true })
.ToList();
for (var i = 0; i < infoCount; i++) {
var pInfo = ppInfoList[i];
var slot = *(byte*)(pInfo + CHECK_TRANSMIT_PLAYER_SLOT_CACHE);
var player = Utilities.GetPlayerFromSlot(slot);
if (player == null || !player.IsValid) continue;
var info = Marshal.PtrToStructure<CCheckTransmitInfo>(pInfo);
foreach (var target in
players.Where(target => target.Index != player.Index)) {
info.m_pTransmitEntity.Clear((int)target.PlayerPawn.Value.Index);
}
}
return HookResult.Continue;
}
override protected HookResult OnEnd(EventRoundEnd ev, GameEventInfo info) {
ghostTimer?.Kill();
CHECK_TRANSMIT.Unhook(onTransmit, HookMode.Post);
return base.OnEnd(ev, info);
}
private void setVisibility(bool state) {
allPlayersVisible = state;
Server.ExecuteCommand($"mp_footsteps_serverside {(state ? "1" : "0")}");
if (state) EnableDamage(); else DisableDamage();
foreach (var player in PlayerUtil.GetAlive()) {
player.ExecuteClientCommand(
$"play {(state ? "\"sounds/buttons/bell1.vsnd\"" : "\"sounds/ui/counter_beep.vsnd\"")}");
}
}
public class GhostSettings : FFASettings {
public GhostSettings() {
ConVarValues["mp_footsteps_serverside"] = false;
}
}
}

View File

@@ -0,0 +1,66 @@
using CounterStrikeSharp.API.Core;
using Jailbreak.English.SpecialDay;
using Jailbreak.Formatting.Extensions;
using Jailbreak.Formatting.Views.SpecialDay;
using Jailbreak.Public.Extensions;
using Jailbreak.Public.Mod.SpecialDay;
using Jailbreak.Public.Mod.SpecialDay.Enums;
using Jailbreak.Public.Utils;
namespace Jailbreak.SpecialDay.SpecialDays;
public class HEDay(BasePlugin plugin, IServiceProvider provider)
: AbstractSpecialDay(plugin, provider), ISpecialDayMessageProvider {
public override SDType Type => SDType.HE;
public override SpecialDaySettings Settings => new HESettings();
public ISDInstanceLocale Locale
=> new SoloDayLocale("HE Only",
"Grenades Only—No guns. Fight against everyone else. No Camping!");
public override void Setup() {
Plugin.RegisterEventHandler<EventGrenadeThrown>(onThrow);
Timers[10] += () => Locale.BeginsIn(10).ToAllChat();
Timers[15] += () => Locale.BeginsIn(5).ToAllChat();
Timers[20] += Execute;
base.Setup();
}
public override void Execute() {
foreach (var player in PlayerUtil.GetAlive())
player.GiveNamedItem("weapon_hegrenade");
base.Execute();
Locale.BeginsIn(0).ToAllChat();
}
private HookResult onThrow(EventGrenadeThrown @event, GameEventInfo info) {
var player = @event.Userid;
if (player == null || !player.IsReal() || !player.PawnIsAlive)
return HookResult.Continue;
player.GiveNamedItem("weapon_hegrenade");
return HookResult.Continue;
}
override protected HookResult
OnEnd(EventRoundEnd @event, GameEventInfo info) {
var result = base.OnEnd(@event, info);
Plugin.DeregisterEventHandler<EventGrenadeThrown>(onThrow);
return result;
}
public class HESettings : SpecialDaySettings {
public HESettings() {
CtTeleport = TeleportType.RANDOM;
TTeleport = TeleportType.RANDOM;
StripToKnife = true;
WithFriendlyFire();
}
public override float FreezeTime(CCSPlayerController player) { return 1; }
public override ISet<string>? AllowedWeapons(CCSPlayerController player) {
return new HashSet<string> {"weapon_hegrenade"};
}
}
}

View File

@@ -120,12 +120,10 @@ public class InfectionDay(BasePlugin plugin, IServiceProvider provider)
Plugin.DeregisterEventHandler<EventPlayerDeath>(onPlayerDeath);
Plugin.DeregisterEventHandler<EventPlayerSpawn>(onRespawn);
Plugin.AddTimer(0.5f, () => {
foreach (var player in swappedPrisoners) {
if (player == null || !player.IsValid) continue;
player.SwitchTeam(CsTeam.Terrorist);
}
});
foreach (var player in swappedPrisoners) {
if (!player.IsValid) continue;
player.SwitchTeam(CsTeam.Terrorist);
}
return result;
}

View File

@@ -0,0 +1,314 @@
using System.Numerics;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Cvars.Validators;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.UserMessages;
using CounterStrikeSharp.API.Modules.Utils;
using Jailbreak.English.SpecialDay;
using Jailbreak.Formatting.Extensions;
using Jailbreak.Formatting.Views.SpecialDay;
using Jailbreak.Public.Extensions;
using Jailbreak.Public.Mod.SpecialDay;
using Jailbreak.Public.Mod.SpecialDay.Enums;
using Jailbreak.Public.Utils;
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
namespace Jailbreak.SpecialDay.SpecialDays;
public class RocketJumpDay(BasePlugin plugin, IServiceProvider provider)
: AbstractSpecialDay(plugin, provider), ISpecialDayMessageProvider {
public static readonly FakeConVar<float> CV_BULLET_SPEED = new(
"css_jb_rj_bullet_speed", "Speed of the projectile bullet.", 1250.0f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(100f, 5000f));
public static readonly FakeConVar<float> CV_MAX_DISTANCE = new(
"css_jb_rj_max_distance", "Max distance to apply rocketjump.", 160.0f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(1f, 1000f));
public static readonly FakeConVar<float> CV_CLOSE_JUMP_DISTANCE = new(
"css_jb_rj_close_jump_distance",
"Max distance that causes a full force jump.", 37.0f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(1f, 1000f));
public static readonly FakeConVar<float> CV_JUMP_FORCE_MAIN = new(
"css_jb_rj_jump_force_main", "Base jump push strength.", 270.0f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0f, 2000f));
public static readonly FakeConVar<float> CV_JUMP_FORCE_UP = new(
"css_jb_rj_jump_force_up", "Vertical boost on rocketjump.", 8.0f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0f, 500f));
public static readonly FakeConVar<float> CV_JUMP_FORCE_FORWARD = new(
"css_jb_rj_jump_force_forward", "Forward scale on rocketjump.", 1.2f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0f, 10f));
public static readonly FakeConVar<float> CV_JUMP_FORCE_BACKWARD = new(
"css_jb_rj_jump_force_backward", "Backward scale on rocketjump.", 1.25f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0f, 10f));
public static readonly FakeConVar<float> CV_RUN_FORCE_MAIN = new(
"css_jb_rj_run_force_main", "Extra boost if running.", 0.8f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0f, 10f));
public static readonly FakeConVar<bool> CV_PROJ_INHERIT_PLAYER_VELOCITY = new(
"css_jb_rj_proj_inherit_player_velocity",
"Whether the projectile inherits player velocity. "
+ "True allows for easier rocket jumps at the cost of 'funky' shot paths when trying to shoot a player");
public static readonly FakeConVar<float> CV_PROJ_DAMAGE = new(
"css_jb_rj_proj_damage", "The damage caused by projectile explosion", 65f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(1f, 200f));
public static readonly FakeConVar<float> CV_PROJ_DAMAGE_RADIUS = new(
"css_jb_rj_proj_damage_radius",
"The radius of the explosion caused by projectile", 225f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(1f, 1000f));
public static readonly FakeConVar<float> CV_PROJ_GRAVITY = new(
"css_jb_rj_proj_gravity", "The gravity of the projectile.", 0.001f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0.001f, 2000f));
private const int GE_FIRE_BULLETS_ID = 452;
// Thank you https://github.com/ipsvn/cs2-Rocketjump/tree/master
private readonly MemoryFunctionVoid<nint, nint> touch =
new("55 48 89 E5 41 54 49 89 F4 53 48 8B 87");
private readonly HashSet<CCSPlayerPawn> jumping = [];
public override SDType Type => SDType.ROCKETJUMP;
public override SpecialDaySettings Settings => new RocketJumpSettings();
public ISDInstanceLocale Locale
=> new SoloDayLocale("Rocket Jump",
"Your shotgun is now an RPG that fires grenades! "
+ "Shoot the ground to launch! " + "Mid-air knives Insta-kill!");
public override void Setup() {
Plugin.HookUserMessage(GE_FIRE_BULLETS_ID, fireBulletsUmHook);
touch.Hook(CBaseEntity_Touch, HookMode.Pre);
Plugin.RegisterEventHandler<EventWeaponFire>(onWeaponFire);
VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook(onHurt, HookMode.Pre);
Plugin.RegisterListener<Listeners.OnTick>(onTick);
Timers[10] += () => Locale.BeginsIn(10).ToAllChat();
Timers[15] += () => Locale.BeginsIn(5).ToAllChat();
Timers[20] += () => {
Execute();
Locale.BeginsIn(0).ToAllChat();
};
base.Setup();
}
public override void Execute() {
foreach (var player in PlayerUtil.GetAlive()) {
player.RemoveWeapons();
player.SetArmor(0);
player.GiveNamedItem("weapon_knife");
player.GiveNamedItem("weapon_nova");
var novaData = player.GetWeaponBase("weapon_nova")?
.As<CCSWeaponBase>()
.VData;
if (novaData == null) continue;
novaData.CannotShootUnderwater = false;
}
base.Execute();
}
override protected HookResult OnEnd(EventRoundEnd ev, GameEventInfo info) {
Plugin.UnhookUserMessage(GE_FIRE_BULLETS_ID, fireBulletsUmHook);
touch.Unhook(CBaseEntity_Touch, HookMode.Pre);
Plugin.DeregisterEventHandler<EventWeaponFire>(onWeaponFire);
VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Unhook(onHurt, HookMode.Pre);
Plugin.RemoveListener<Listeners.OnTick>(onTick);
// Delay to avoid mutation during hook execution
Server.NextFrameAsync(() => { jumping.Clear(); });
return base.OnEnd(ev, info);
}
/// <summary>
/// Clears recipients of the Nova bullet pellets to hide it from everyone.
/// Give cleaner shot effect and removes unecessary rendering
/// </summary>
private HookResult fireBulletsUmHook(UserMessage um) {
um.Recipients.Clear();
return HookResult.Continue;
}
/// <summary>
/// Handles when the grenade touches something.
/// Triggers a rocket jump for its owner if nearby.
/// Uses CHEGrenadeProjectile for built-in AoE, visibility, and raycast-like behavior.
/// This is prefered b/c using a raycast would require custom logic for:
/// -Damage radius simulation, Entity filtering, Visual/audio, feedback Manual hit registration
/// </summary>
private HookResult CBaseEntity_Touch(DynamicHook hook) {
var projectile = hook.GetParam<CHEGrenadeProjectile>(0);
if (projectile.DesignerName != "hegrenade_projectile")
return HookResult.Continue;
var owner = projectile.OwnerEntity.Value?.As<CCSPlayerPawn>();
if (owner == null || owner.DesignerName != "player")
return HookResult.Continue;
var bulletOrigin = projectile.AbsOrigin;
var pawnOrigin = owner.AbsOrigin;
if (bulletOrigin == null || pawnOrigin == null) return HookResult.Continue;
var eyeOrigin = owner.GetEyeOrigin();
var distance = Vector3.Distance(bulletOrigin.ToVec3(), pawnOrigin.ToVec3());
projectile.DetonateTime = 0f;
doJump(owner, distance, bulletOrigin.ToVec3(), eyeOrigin);
return HookResult.Handled;
}
/// <summary>
/// Detects Nova shots and spawns a grenade projectile in the direction the player is aiming.
/// </summary>
private HookResult onWeaponFire(EventWeaponFire @event, GameEventInfo info) {
var controller = @event.Userid;
if (controller == null) return HookResult.Continue;
var weapon = @event.Weapon;
if (weapon != "weapon_nova") return HookResult.Continue;
var pawn = controller.PlayerPawn.Value;
var origin = pawn?.AbsOrigin;
if (pawn == null || origin == null) return HookResult.Continue;
pawn.GetEyeForward(10.0f, out var forwardDir, out var targetPos);
var realBulletVelocity = forwardDir * CV_BULLET_SPEED.Value;
var addedBulletVelocity = CV_PROJ_INHERIT_PLAYER_VELOCITY.Value ?
pawn.AbsVelocity.ToVec3() + realBulletVelocity :
realBulletVelocity;
shootBullet(controller, targetPos, addedBulletVelocity,
new Vector3(pawn.EyeAngles.X, pawn.EyeAngles.Y, pawn.EyeAngles.Z));
return HookResult.Continue;
}
/// <summary>
/// Makes knife hits lethal only if the attacker is airborne via rocket jump.
/// Nullifies Nova Pellet Damage
/// Passes Grenades Per Usual
/// </summary>
private HookResult onHurt(DynamicHook hook) {
var info = hook.GetParam<CTakeDamageInfo>(1);
var attacker = info.Attacker.Value?.As<CCSPlayerPawn>();
var weaponName = info.Ability.Value?.As<CCSWeaponBase>().VData?.Name;
if (attacker == null || weaponName == null) return HookResult.Continue;
if (weaponName.Contains("grenade")) return HookResult.Continue;
if (!weaponName.Contains("knife") && !weaponName.Contains("bayonet"))
return HookResult.Handled;
if (jumping.Contains(attacker)) info.Damage = 200f;
return HookResult.Continue;
}
/// <summary>
/// Continuously removes players from the "jumping" list once they land.
/// </summary>
private void onTick() {
foreach (var player in jumping.Where(p => p.OnGroundLastTick).ToList())
jumping.Remove(player);
}
/// <summary>
/// Spawns and launches a CHEGrenadeProjectile with explosive properties like radius and damage.
/// </summary>
private void shootBullet(CCSPlayerController controller, Vector3 origin,
Vector3 velocity, Vector3 angle) {
var pawn = controller.PlayerPawn.Value;
if (pawn == null) return;
var projectile =
Utilities
.CreateEntityByName<CHEGrenadeProjectile>("hegrenade_projectile");
if (projectile == null) return;
projectile.OwnerEntity.Raw = pawn.EntityHandle.Raw;
projectile.Damage = CV_PROJ_DAMAGE.Value;
projectile.DmgRadius = CV_PROJ_DAMAGE_RADIUS.Value;
projectile.DispatchSpawn();
projectile.AcceptInput("InitializeSpawnFromWorld", pawn, pawn);
Schema.SetSchemaValue(projectile.Handle, "CBaseGrenade", "m_hThrower",
pawn.EntityHandle.Raw);
projectile.GravityScale = CV_PROJ_GRAVITY.Value;
projectile.DetonateTime = 9999f;
unsafe {
projectile.Teleport(new Vector((nint)(&origin)), new QAngle((nint)(&angle)),
new Vector((nint)(&velocity)));
}
}
/// <summary>
/// Calculates and applies rocket jump force based on distance and direction.
/// Combines player velocity with a directional push, scaled by angle and proximity.
/// Adds upward force and modifies Z for vertical boost.
/// </summary>
private void doJump(CCSPlayerPawn pawn, float distance, Vector3 bulletOrigin,
Vector3 pawnOrigin) {
if (distance >= CV_MAX_DISTANCE.Value) return;
var down = false;
var direction = Vector3.Normalize(pawnOrigin - bulletOrigin);
if (direction.Z < 0) down = true;
var pawnVelocity = pawn.AbsVelocity;
var movementDir =
Vector3.Normalize(new Vector3(pawnVelocity.X, pawnVelocity.Y, 0));
var dot = Vector3.Dot(direction, movementDir);
var scale = dot >= 0 ?
CV_JUMP_FORCE_FORWARD.Value :
CV_JUMP_FORCE_BACKWARD.Value;
var velocity = direction * CV_JUMP_FORCE_MAIN.Value;
var totalVelocity = (pawnVelocity.ToVec3() + velocity) * scale;
pawnVelocity.Z = 0.0f;
totalVelocity.Z = 0.0f;
if (pawn.OnGroundLastTick) totalVelocity *= CV_RUN_FORCE_MAIN.Value;
var forceUp = CV_JUMP_FORCE_UP.Value * (CV_MAX_DISTANCE.Value - distance);
if (distance > CV_CLOSE_JUMP_DISTANCE.Value)
if (totalVelocity.Z > 0.0f)
totalVelocity.Z = 1000.0f + forceUp;
else
totalVelocity.Z += forceUp;
else
totalVelocity.Z += forceUp / 1.37f;
if (down) velocity.Z *= -1.0f;
unsafe { pawn.Teleport(null, null, new Vector((nint)(&totalVelocity))); }
jumping.Add(pawn);
}
public class RocketJumpSettings : SpecialDaySettings {
public RocketJumpSettings() {
CtTeleport = TeleportType.RANDOM;
TTeleport = TeleportType.RANDOM;
StripToKnife = true;
WithFriendlyFire();
ConVarValues["sv_infinite_ammo"] = 1;
ConVarValues["mp_death_drop_gun"] = 0;
ConVarValues["ff_damage_reduction_grenade_self"] = 0f;
ConVarValues["sv_falldamage_scale"] = 0f;
}
public override float FreezeTime(CCSPlayerController player) { return 1; }
}
}

View File

@@ -31,6 +31,12 @@ public class SpecialTreatmentCommandsBehavior(IWardenService warden,
// TODO: Pop up menu of prisoners to toggle ST for
return;
// FIXME: Remove after @aim is fixed in CSS
if (command.ArgByIndex(1).ToLower().Contains("@aim")) {
command.ReplyToCommand("@aim is currently not supported due to a bug in CSS.");
return;
}
var targets = command.GetArgTargetResult(1);
var eligible = targets
.Where(p => p is { Team: CsTeam.Terrorist, PawnIsAlive: true })

View File

@@ -19,14 +19,14 @@ public static class RayTrace {
private static readonly nint TraceFunc = NativeAPI.FindSignature(
Addresses.ServerPath,
Environment.OSVersion.Platform == PlatformID.Unix ?
"48 B8 ? ? ? ? ? ? ? ? 55 48 89 E5 41 57 41 56 49 89 D6 41 55" :
"4C 8B DC 49 89 5B ? 49 89 6B ? 49 89 73 ? 57 41 56 41 57 48 81 EC ? ? ? ? 0F 57 C0");
"48 B8 ? ? ? ? ? ? ? ? 55 66 0F EF C0 48 89 E5 41 57 41 56 49 89 D6" :
"4C 8B DC 49 89 5B ? 49 89 6B ? 49 89 73 ? 57 41 56 41 57 48 81 EC");
private static readonly nint GameTraceManager = NativeAPI.FindSignature(
Addresses.ServerPath,
Environment.OSVersion.Platform == PlatformID.Unix ?
"48 8D 05 ? ? ? ? F3 0F 58 8D ? ? ? ? 31 FF" :
"48 8B 0D ? ? ? ? 48 8D 45 ? 48 89 44 24 ? 4C 8D 44 24 ? C7 44 24 ? ? ? ? ? 48 8D 54 24 ? 4C 8B CB");
"4C 8D 05 ? ? ? ? BB" :
"48 8B 0D ? ? ? ? 0C");
public static Vector TraceShape(Vector _origin, QAngle _viewangles,
bool fromPlayer = false) {