Cleaup/aug (#290)

* Reformat

* Add cvar config for HNS

* Add more HNS customization support

* Add customizations to noscope

* Remove unused class var

* More tidying up

* Remove config infra

* Add configurability to C4

* Add contributing, add config explanation in readme
This commit is contained in:
Isaac
2024-08-27 17:57:20 -07:00
committed by GitHub
parent 5ac3d053e1
commit 9710049643
49 changed files with 737 additions and 566 deletions

20
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,20 @@
## Contributing
The jail plugin is currently in heavy development and all contributions are welcome!
Please make sure all contributions use the dependency injection system, or ask to have your contribution
ported if you don't know how.
> [!TIP]
> Microsoft has some good documentation on dependency injection here:
> [Overview](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection),
> [Using Dependency Injection](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-usage),
> [Dependency Injection Guidelines](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines).
All event handlers should derive from `IPluginBehavior` and be registered using
`IServiceCollection.AddPluginBehavior<T>`. If your behavior also acts as a service,
make sure to use `IServiceCollection.AddPluginBehavior<TInterface, T>`. All `IPluginBehavior` objects
have their event handlers automatically registered.
Code style should follow .NET conventions and use the formatting settings specified
in [Jailbreak.sln.DotSettings](./Jailbreak.sln.DotSettings)
(if you need help, make sure to check "enable edits from maintainers" and ask for a format)

View File

@@ -9,17 +9,20 @@ The classic Jail gamemode, ported to Counter-Strike 2.
[![Release](https://img.shields.io/badge/Release-mediumseagreen?style=for-the-badge&logo=onlyoffice
)](https://github.com/edgegamers/Jailbreak/releases/)
[![Stable](https://img.shields.io/badge/Stable-orangered?style=for-the-badge&logo=onlyoffice)](https://nightly.link/edgegamers/Jailbreak/workflows/nightly/main/jailbreak-nightly)
[![Stable](https://img.shields.io/badge/Stable-orangered?style=for-the-badge&logo=onlyoffice)](https://nightly.link/edgegamers/Jailbreak/workflows/nightly/main/jailbreak-nightly)
[![Dev](https://img.shields.io/badge/Nightly-slateblue?style=for-the-badge&logo=onlyoffice
)](https://nightly.link/edgegamers/Jailbreak/workflows/nightly/dev/jailbreak-nightly)
**Release** builds are our full releases. We try to keep these high-quality and bug-free, when we can.
Our **Stable** builds run on EdgeGamers' own Jailbreak servers.
Our **Stable** builds run on EdgeGamers' own Jailbreak servers.
Our **Nightly** builds are used exclusively for development and staging, and are likely to have problems.
## Versioning
Our release tags starting from 'v2.0.0' follow the [Semantic Versioning 2.0.0](https://semver.org/) standard,
where `MAJOR.MINOR.PATCH` are incremented based on the following:
- `MAJOR` when we make incompatible API changes,
- `MINOR` when we add functionality in a backwards-compatible manner.
- `PATCH` when we make backwards-compatible bug fixes.
@@ -27,46 +30,29 @@ where `MAJOR.MINOR.PATCH` are incremented based on the following:
## Status
- **⚙️ Server**
- [ ] Stats/Analytics Sinks
- [ ] Error reporting
- [x] Configuration system
- Note: Passable, but in a terrible state. Needs TLC.
- [x] Stats/Analytics Sinks
- [x] Error reporting
- [x] Logging
- [x] Zones
- **👮 Guards**
- [x] Warden Selection
- [x] Warden Laser and Paint
- [ ] Special Days
- [x] Ratio Enforcement
- [ ] Bans/Punishments
- [x] Special Days
- **🎃 Prisoners**
- [x] Last Request
- [x] Rebel System
- **🛕 Maps**
- [x] Automagic Cell Opening
- [ ] Custom Entities
- [ ] Custom I/O
- [ ] Warden/Guard/Prisoner Filters
## Contributing
## Configuration
The jail plugin is currently in heavy development and all contributions are welcome!
Please make sure all contributions use the dependency injection system, or ask to have your contribution
ported if you don't know how.
Configuration is done through CS#'s [FakeConVars](https://docs.cssharp.dev/examples/WithFakeConvars.html?q=fakeconvar).
Ports to DI containers that have more verbose scoping systems for round-based or game-based scoping are welcome.
> [!TIP]
> Microsoft has some good documentation on dependency injection here:
> [Overview](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection),
> [Using Dependency Injection](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-usage),
> [Dependency Injection Guidelines](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines).
All event handlers should derive from `IPluginBehavior` and be registered using
`IServiceCollection.AddPluginBehavior<T>`. If your behavior also acts as a service,
make sure to use `IServiceCollection.AddPluginBehavior<TInterface, T>`. All `IPluginBehavior` objects
have their event handlers automatically registered.
Code style should follow .NET conventions
(if you need help, make sure to check "enable edits from maintainers" and ask for a format)
You can search for the list of configurable
convars [like so](https://github.com/search?q=repo%3Aedgegamers%2FJailbreak%20fakeconvar&type=code).
## Modding
@@ -97,7 +83,8 @@ foreach (IPluginBehavior extension in _extensions)
## Building
The jailbreak plugin automatically builds to `build/Jailbreak` when using `dotnet publish src/Jailbreak/Jailbreak.csproj`.
The jailbreak plugin automatically builds to `build/Jailbreak` when
using `dotnet publish src/Jailbreak/Jailbreak.csproj`.
Please use [SDK 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) or higher.
Note that only the `src/Jailbreak` project is intended to be built directly.

View File

@@ -4,6 +4,7 @@ using Jailbreak.Formatting.Core;
using Jailbreak.Formatting.Logistics;
using Jailbreak.Formatting.Objects;
using Jailbreak.Formatting.Views;
using Jailbreak.Public.Extensions;
namespace Jailbreak.English.Generic;
@@ -36,21 +37,23 @@ public class GenericCmdLocale : IGenericCmdLocale,
PREFIX,
$"{ChatColors.Grey}Command is on cooldown for",
seconds,
$"{ChatColors.Grey}seconds!"
$"{ChatColors.Grey}second" + (seconds == 1 ? "" : "s") + "."
};
}
public IView InvalidParameter(string parameter, string expected) {
return new SimpleView {
PREFIX,
$"{ChatColors.Red}Invalid parameter '{ChatColors.LightBlue}{parameter}{ChatColors.Red}', expected a(n) {ChatColors.White}{expected}{ChatColors.Red}."
$"{ChatColors.Red}Invalid parameter '{ChatColors.LightBlue}{parameter}{ChatColors.Red}',",
"expected a" + (expected[0].IsVowel() ? "n" : ""),
$"{ChatColors.White}{expected}{ChatColors.Red}."
};
}
public IView NoPermissionMessage(string permission) {
return new SimpleView {
PREFIX,
$"{ChatColors.Red}This command requires the {ChatColors.White}{permission}{ChatColors.Red} permission."
$"{ChatColors.DarkRed}This requires the {ChatColors.White}{permission}{ChatColors.Red} permission."
};
}

View File

@@ -26,7 +26,9 @@ public class CoinflipLocale : LastRequestLocale, ILRCFLocale {
public IView CoinLandsOn(bool heads) {
return new SimpleView {
PREFIX, "The coin lands on" + ChatColors.Green, heads ? "Heads" : "Tails"
PREFIX,
"The coin landed on" + ChatColors.Green,
heads ? "Heads" : "Tails" + ChatColors.White + "."
};
}
}

View File

@@ -15,7 +15,7 @@ public class LastRequestLocale : ILRLocale,
ILanguage<Formatting.Languages.English> {
public static readonly FormatObject PREFIX =
new HiddenFormatObject(
$" {ChatColors.DarkRed}[{ChatColors.LightRed}LR{ChatColors.DarkRed}]") {
$" {ChatColors.Green}[{ChatColors.Lime}LR{ChatColors.Green}]") {
// Hide in panorama and center text
Plain = false, Panorama = false, Chat = true
};
@@ -24,7 +24,7 @@ public class LastRequestLocale : ILRLocale,
return new SimpleView {
{
PREFIX,
$"Last Request has been enabled. {ChatColors.Grey}Type {ChatColors.LightBlue}!lr{ChatColors.Grey} to start a last request."
$"Last Request activated. {ChatColors.Grey}Type {ChatColors.LightBlue}!lr{ChatColors.Grey} to start a last request."
}
};
}
@@ -33,7 +33,7 @@ public class LastRequestLocale : ILRLocale,
return new SimpleView {
{
PREFIX,
$"{ChatColors.Grey}Last Request has been {ChatColors.Red}disabled{ChatColors.Grey}."
$"{ChatColors.Grey}Last Request {ChatColors.Red}disabled{ChatColors.Grey}."
}
};
}
@@ -52,9 +52,9 @@ public class LastRequestLocale : ILRLocale,
return new SimpleView {
PREFIX,
lr.Prisoner,
ChatColors.Grey + "is preparing a",
ChatColors.Grey + "is starting a",
ChatColors.White + lr.Type.ToFriendlyString(),
ChatColors.Grey + "Last Request against",
ChatColors.Grey + "LR against",
lr.Guard
};
}
@@ -74,36 +74,29 @@ public class LastRequestLocale : ILRLocale,
var tNull = !lr.Prisoner.IsReal();
var gNull = !lr.Guard.IsReal();
if (tNull && gNull)
return new SimpleView { PREFIX, "Last Request has been decided." };
return new SimpleView { PREFIX, "Last Request decided." };
if (tNull && result == LRResult.PRISONER_WIN)
return new SimpleView {
PREFIX, lr.Guard, "lost the LR, but the prisoner left the game."
PREFIX, lr.Guard, "lost the LR, but the prisoner left the game?"
};
if (gNull && result == LRResult.GUARD_WIN)
return new SimpleView {
PREFIX, lr.Prisoner, "lost the LR, but the guard left the game."
PREFIX, lr.Prisoner, "lost the LR, but the guard left the game?"
};
switch (result) {
case LRResult.TIMED_OUT:
return new SimpleView {
PREFIX, ChatColors.Grey.ToString(), "Last Request has timed out."
};
case LRResult.INTERRUPTED:
return new SimpleView {
PREFIX,
ChatColors.Grey.ToString(),
"Last Request has been interrupted."
};
default:
return new SimpleView {
PREFIX,
result == LRResult.PRISONER_WIN ? lr.Prisoner : lr.Guard,
"won the LR."
};
}
return result switch {
LRResult.TIMED_OUT => new SimpleView {
PREFIX, ChatColors.Grey.ToString(), "Last Request timed out."
},
LRResult.INTERRUPTED => new SimpleView {
PREFIX, ChatColors.Grey.ToString(), "Last Request interrupted."
},
_ => new SimpleView {
PREFIX, result == LRResult.PRISONER_WIN ? lr.Prisoner : lr.Guard, "won."
}
};
}
public IView CannotLR(string reason) {
@@ -149,14 +142,4 @@ public class LastRequestLocale : ILRLocale,
=> new SimpleView {
PREFIX, "You are not in the same LR as them, damage blocked."
};
public IView InvalidPlayerChoice(CCSPlayerController player, string reason) {
return new SimpleView {
PREFIX,
"Invalid player choice: ",
player,
" Reason: ",
reason
};
}
}

View File

@@ -11,7 +11,7 @@ public class RPSLocale : LastRequestLocale, ILRRPSLocale {
public IView BothPlayersMadeChoice() {
return new SimpleView {
PREFIX, "Both players have rocked, papered, and scissored! (ew)"
PREFIX, "Both players rocked, papered, and scissored! (ew)"
};
}
@@ -23,13 +23,13 @@ public class RPSLocale : LastRequestLocale, ILRRPSLocale {
int guardPick, int prisonerPick) {
return new SimpleView {
PREFIX,
"Results: ",
"Results:",
guard,
" picked ",
"picked",
toRPS(guardPick),
" and ",
"and",
prisoner,
" picked ",
"picked",
toRPS(prisonerPick)
};
}

View File

@@ -20,15 +20,14 @@ public class RaceLocale : LastRequestLocale, ILRRaceLocale {
SimpleView.NEWLINE, {
PREFIX,
$"Type {ChatColors.Blue}!endrace{ChatColors.White} to set the end point!"
},
SimpleView.NEWLINE
}
};
public IView RaceStartingMessage(CCSPlayerController prisoner) {
return new SimpleView {
{
PREFIX, prisoner,
" is starting a race. Pay attention to where they set the end point!"
"is racing you. Pay attention to where they set the end point!"
}
};
}
@@ -37,14 +36,17 @@ public class RaceLocale : LastRequestLocale, ILRRaceLocale {
return new SimpleView {
{
PREFIX,
$"You must be in a race {ChatColors.Blue + "!lr" + ChatColors.White} to use this command"
$"{ChatColors.Red}You must be in a race {ChatColors.Blue + "!lr" + ChatColors.Red} to use this."
}
};
}
public IView NotInPendingState() {
return new SimpleView {
{ PREFIX, "You must be in the pending state to use this command." }
{
PREFIX,
ChatColors.Red + "You must be in the pending state to use this command."
}
};
}
}

View File

@@ -4,6 +4,7 @@ using Jailbreak.Formatting.Core;
using Jailbreak.Formatting.Logistics;
using Jailbreak.Formatting.Objects;
using Jailbreak.Formatting.Views.Warden;
using Microsoft.Extensions.Primitives;
namespace Jailbreak.English.Mute;
@@ -17,32 +18,40 @@ public class WardenPeaceLocale : IWardenPeaceLocale,
public IView PeaceEnactedByAdmin(int seconds) {
return new SimpleView {
PREFIX, "An admin enacted peace for", seconds, "seconds."
PREFIX,
$"{ChatColors.Red}An admin {ChatColors.White}enacted peace for",
seconds,
"second" + (seconds == 1 ? "" : "s") + "."
};
}
public IView WardenEnactedPeace(int seconds) {
return new SimpleView {
PREFIX, "Warden enacted peace for", seconds, "seconds."
PREFIX,
$"{ChatColors.Blue}The warden {ChatColors.White}enacted peace for",
seconds,
"seconds."
};
}
public IView GeneralPeaceEnacted(int seconds) {
return new SimpleView {
PREFIX, "Peace has been enacted for", seconds, "seconds."
PREFIX,
"Peace was enacted for",
seconds,
"second" + (seconds == 1 ? "" : "s") + "."
};
}
public IView UnmutedGuards
=> new SimpleView {
{ PREFIX, $"{ChatColors.Blue}Guards {ChatColors.Grey}have been unmuted." }
{ PREFIX, $"{ChatColors.Blue}Guards {ChatColors.Grey}were unmuted." }
};
public IView UnmutedPrisoners
=> new SimpleView {
{
PREFIX,
$"{ChatColors.LightRed}Prisoners {ChatColors.Grey}have been unmuted."
PREFIX, $"{ChatColors.LightRed}Prisoners {ChatColors.Grey}were unmuted."
}
};
@@ -61,7 +70,7 @@ public class WardenPeaceLocale : IWardenPeaceLocale,
public IView DeadReminder
=> new SimpleView {
{ PREFIX, $"{ChatColors.Red}You are dead and cannot speak!" }
{ PREFIX, $"{ChatColors.Red}You are dead and cannot speak." }
};
public IView AdminDeadReminder

View File

@@ -17,5 +17,5 @@ public class RebelLocale : IRebelLocale,
};
public IView NoLongerRebel
=> new SimpleView { PREFIX, "You are no longer a rebel." };
=> new SimpleView { PREFIX, "You are no longer red." };
}

View File

@@ -19,15 +19,12 @@ public class GunDayLocale() : SoloDayLocale("Gun Game",
public IView PromotedTo(string weapon, int weaponsLeft) {
if (weaponsLeft == 1)
return new SimpleView {
PREFIX,
"You were promoted to",
weapon + ".",
ChatColors.Green + "LAST WEAPON!"
PREFIX, "Promoted to", weapon + ".", ChatColors.Green + "LAST WEAPON!"
};
return new SimpleView {
PREFIX,
"You were promoted to",
"Promoted to",
weapon + ".",
weaponsLeft,
"weapons left."
@@ -38,7 +35,7 @@ public class GunDayLocale() : SoloDayLocale("Gun Game",
return new SimpleView {
PREFIX,
player,
"is on their last weapon!",
"is on the last weapon!",
ChatColors.LightRed + "Watch out!"
};
}

View File

@@ -3,7 +3,7 @@
namespace Jailbreak.English.SpecialDay;
public class HNSDayLocale() : TeamDayLocale("Hide and Seek",
"CTs must hide while the Ts seek!", "Ts have 250 HP!") {
"CTs must hide while the Ts seek!", "Ts have increased HP!") {
public IView StayInArmory
=> new SimpleView { PREFIX, "Today is", Name, ", stay in the armory!" };
@@ -15,13 +15,16 @@ public class HNSDayLocale() : TeamDayLocale("Hide and Seek",
Name,
"begins in",
seconds,
"seconds."
"second" + (seconds == 1 ? "" : "s") + "."
};
}
public IView DamageWarning(int seconds) {
return new SimpleView {
PREFIX, "You will be vulnerable to damage in", seconds, "seconds."
PREFIX,
"You will be vulnerable to damage in",
seconds,
"second" + (seconds == 1 ? "" : "s") + "."
};
}
}

View File

@@ -25,13 +25,13 @@ public class InfectionDayLocale() : TeamDayLocale("Infection",
return player == null || !player.IsValid ?
new SimpleView {
PREFIX,
$"{ChatColors.Red}You were {ChatColors.DarkRed}infected{ChatColors.Red}! You are now a zombie!"
$"{ChatColors.Red}You were {ChatColors.DarkRed}infected{ChatColors.Red}!"
} :
new SimpleView {
PREFIX,
$"{ChatColors.Red}You were {ChatColors.DarkRed}infected{ChatColors.Red} by",
player,
"! You are now a zombie!"
"!"
};
}

View File

@@ -16,11 +16,19 @@ public class SDLocale : ISDLocale, ILanguage<Formatting.Languages.English> {
};
public IView SpecialDayRunning(string name) {
return new SimpleView { PREFIX, name, "is currently running!" };
return new SimpleView {
PREFIX,
ChatColors.DarkRed + name,
ChatColors.Red + "is currently running."
};
}
public IView InvalidSpecialDay(string name) {
return new SimpleView { PREFIX, name, "is not a valid special day!" };
return new SimpleView {
PREFIX,
ChatColors.DarkRed + name,
ChatColors.Red + "is not a valid special day."
};
}
public IView SpecialDayCooldown(int rounds) {
@@ -28,7 +36,8 @@ public class SDLocale : ISDLocale, ILanguage<Formatting.Languages.English> {
PREFIX,
"You must wait",
rounds,
"more rounds before starting a special day."
"more round" + (rounds == 1 ? "" : "s")
+ " before starting a special day."
};
}
@@ -37,7 +46,7 @@ public class SDLocale : ISDLocale, ILanguage<Formatting.Languages.English> {
PREFIX,
"You must start a special day within",
maxTime,
"seconds of the round start."
"second" + (maxTime == 1 ? "" : "s") + " of the round start."
};
}
}

View File

@@ -39,7 +39,7 @@ public class SpeedrunDayLocale() : SoloDayLocale("Speedrunners",
return new SimpleView {
{
PREFIX,
$"Round #{ChatColors.Yellow}{round}{ChatColors.Default} begins! The slowest",
$"Round {ChatColors.Yellow}#{round}{ChatColors.Default} begins! The slowest",
"player to reach the goal will be eliminated!"
},
SimpleView.NEWLINE,
@@ -74,7 +74,7 @@ public class SpeedrunDayLocale() : SoloDayLocale("Speedrunners",
PREFIX,
"The original speedrunner left, so",
player,
"is now the speedrunner!"
"is now the speedrunner."
};
}
@@ -83,7 +83,7 @@ public class SpeedrunDayLocale() : SoloDayLocale("Speedrunners",
PREFIX,
"The original speedrunner isn't moving, so",
player,
"is now the speedrunner!"
"is now the speedrunner."
};
}
@@ -145,7 +145,7 @@ public class SpeedrunDayLocale() : SoloDayLocale("Speedrunners",
"reached the goal. Eliminating one player on each time."
},
SimpleView.NEWLINE,
{ PREFIX, "Randomly selected the path from ", player, "." }
{ PREFIX, "Randomly selected the path from", player, "." }
};
}
}

View File

@@ -30,7 +30,9 @@ public class WardenCmdOpenLocale : IWardenCmdOpenLocale,
}
public IView CellsOpened
=> new SimpleView { WardenLocale.PREFIX, "Cells were auto-opened." };
=> new SimpleView {
WardenLocale.PREFIX, ChatColors.Grey + "Cells were auto-opened."
};
public IView OpeningFailed
=> new SimpleView { WardenLocale.PREFIX, "Failed to open the cells." };

View File

@@ -48,7 +48,7 @@ public class PlayerZoneCreator : BasicZoneCreator, ITypedZoneCreator {
}
var pawn = player.PlayerPawn.Value;
if (pawn == null) {
if (pawn == null || !pawn.IsValid) {
timer?.Kill();
return;
}

View File

@@ -18,43 +18,46 @@ namespace Jailbreak.LastGuard;
public class LastGuard(ILGLocale notifications, ILastRequestManager lrManager)
: ILastGuardService, IPluginBehavior {
public readonly FakeConVar<bool> CvAlwaysOverrideCt = new(
public static readonly FakeConVar<bool> CV_ALWAYS_OVERRIDE_CT = new(
"css_jb_lg_apply_lower_hp",
"If true, the LG will be forced lower health if calculated");
public readonly FakeConVar<double> CvGuardHealthRatio = new(
public static readonly FakeConVar<double> CV_GUARD_HEALTH_RATIO = new(
"css_jb_lg_ct_hp_ratio", "Ratio of CT : T Health", 0.6,
ConVarFlags.FCVAR_NONE, new RangeValidator<double>(0.00001, 10));
public readonly FakeConVar<int> CvLGBaseRoundTime = new("css_jb_lg_time_base",
"Round time to set when LG is activated, 0 to disable", 60);
public static readonly FakeConVar<int> CV_LG_BASE_ROUND_TIME =
new("css_jb_lg_time_base",
"Round time to set when LG is activated, 0 to disable", 60);
public readonly FakeConVar<int> CvLGKillBonusTime =
public static readonly FakeConVar<int> CV_LG_KILL_BONUS_TIME =
new("css_jb_lg_time_per_kill",
"Additional round time to add per prisoner kill", 10);
public readonly FakeConVar<int> CvLGMaxTime = new("css_jb_lg_time_max",
"Max round time to give the LG regardless of bonuses", 120);
public static readonly FakeConVar<int> CV_LG_MAX_TIME =
new("css_jb_lg_time_max",
"Max round time to give the LG regardless of bonuses", 120);
public readonly FakeConVar<int> CvLGPerPrisonerTime =
public static readonly FakeConVar<int> CV_LG_PER_PRISONER_TIME =
new("css_jb_lg_time_per_prisoner",
"Additional round time to add per prisoner", 10);
public readonly FakeConVar<string> CvLGWeapon = new("css_jb_lg_t_weapon",
"Weapon to give remaining prisoners once LG activates", "",
ConVarFlags.FCVAR_NONE, new WeaponValidator());
public static readonly FakeConVar<string> CV_LG_WEAPON =
new("css_jb_lg_t_weapon",
"Weapon to give remaining prisoners once LG activates", "",
ConVarFlags.FCVAR_NONE, new ItemValidator());
public readonly FakeConVar<int> CvMaxCtHealth = new("css_jb_lg_max_hp",
"Max HP that the LG can have otherwise", 125, ConVarFlags.FCVAR_NONE,
new RangeValidator<int>(1, 1000));
public static readonly FakeConVar<int> CV_MAX_CT_HEALTH =
new("css_jb_lg_max_hp", "Max HP that the LG can have otherwise", 125,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(1, 1000));
public readonly FakeConVar<int> CvMaxTHealthContribution = new(
public static readonly FakeConVar<int> CV_MAX_T_HEALTH_CONTRIBUTION = new(
"css_jb_lg_t_max_hp", "Max HP to contribute per T to LG", 200,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(1, 1000));
public readonly FakeConVar<int> CvMinimumCts = new("css_jb_lg_min_cts",
"Minimum number of CTs to start last guard", 2, ConVarFlags.FCVAR_NONE,
new RangeValidator<int>(1, 32));
public static readonly FakeConVar<int> CV_MINIMUM_CTS =
new("css_jb_lg_min_cts", "Minimum number of CTs to start last guard", 2,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(1, 32));
private readonly Random rng = new();
private bool canStart;
@@ -71,11 +74,11 @@ public class LastGuard(ILGLocale notifications, ILastRequestManager lrManager)
API.Stats?.PushStat(new ServerStat("JB_LASTGUARD",
lastGuard.SteamID.ToString()));
var calculated = CalculateHealth();
var calculated = calculateHealth();
if (calculated < guardPlayerPawn.Health && !CvAlwaysOverrideCt.Value) {
if (guardPlayerPawn.Health > CvMaxCtHealth.Value)
guardPlayerPawn.Health = CvMaxCtHealth.Value;
if (calculated < guardPlayerPawn.Health && !CV_ALWAYS_OVERRIDE_CT.Value) {
if (guardPlayerPawn.Health > CV_MAX_CT_HEALTH.Value)
guardPlayerPawn.Health = CV_MAX_CT_HEALTH.Value;
} else { guardPlayerPawn.Health = calculated; }
Utilities.SetStateChanged(guardPlayerPawn, "CBaseEntity", "m_iHealth");
@@ -87,11 +90,11 @@ public class LastGuard(ILGLocale notifications, ILastRequestManager lrManager)
.Where(p => p is { PawnIsAlive: true, Team: CsTeam.Terrorist })
.ToList();
if (CvLGBaseRoundTime.Value != 0)
RoundUtil.SetTimeRemaining(Math.Min(CvLGBaseRoundTime.Value,
CvLGMaxTime.Value));
addRoundTimeCapped(CvLGPerPrisonerTime.Value * lastGuardPrisoners.Count,
CvLGMaxTime.Value);
if (CV_LG_BASE_ROUND_TIME.Value != 0)
RoundUtil.SetTimeRemaining(Math.Min(CV_LG_BASE_ROUND_TIME.Value,
CV_LG_MAX_TIME.Value));
addRoundTimeCapped(CV_LG_PER_PRISONER_TIME.Value * lastGuardPrisoners.Count,
CV_LG_MAX_TIME.Value);
var prisonerHp =
lastGuardPrisoners.Sum(prisoner
@@ -101,15 +104,15 @@ public class LastGuard(ILGLocale notifications, ILastRequestManager lrManager)
.ToAllCenter()
.ToAllChat();
if (string.IsNullOrEmpty(CvLGWeapon.Value)) return;
if (string.IsNullOrEmpty(CV_LG_WEAPON.Value)) return;
foreach (var player in lastGuardPrisoners)
player.GiveNamedItem(CvLGWeapon.Value);
player.GiveNamedItem(CV_LG_WEAPON.Value);
}
public void DisableLastGuardForRound() { canStart = false; }
public int CalculateHealth() {
private int calculateHealth() {
var aliveTerrorists = Utilities.GetPlayers()
.Where(plr => plr is { PawnIsAlive: true, Team: CsTeam.Terrorist })
.ToList();
@@ -117,8 +120,8 @@ public class LastGuard(ILGLocale notifications, ILastRequestManager lrManager)
return (int)Math.Floor(aliveTerrorists
.Select(player => player.PlayerPawn.Value?.Health ?? 0)
.Select(playerHealth
=> Math.Min(playerHealth, CvMaxTHealthContribution.Value))
.Sum() * CvGuardHealthRatio.Value);
=> Math.Min(playerHealth, CV_MAX_T_HEALTH_CONTRIBUTION.Value))
.Sum() * CV_GUARD_HEALTH_RATIO.Value);
}
[GameEventHandler]
@@ -132,7 +135,7 @@ public class LastGuard(ILGLocale notifications, ILastRequestManager lrManager)
if (player.Team != CsTeam.Terrorist) return HookResult.Continue;
addRoundTimeCapped(CvLGKillBonusTime.Value, CvLGMaxTime.Value);
addRoundTimeCapped(CV_LG_KILL_BONUS_TIME.Value, CV_LG_MAX_TIME.Value);
giveGun(player);
return HookResult.Continue;
@@ -200,7 +203,7 @@ public class LastGuard(ILGLocale notifications, ILastRequestManager lrManager)
canStart = Utilities.GetPlayers()
.Count(plr
=> plr is { PawnIsAlive: true, Team: CsTeam.CounterTerrorist })
>= CvMinimumCts.Value;
>= CV_MINIMUM_CTS.Value;
return HookResult.Continue;
}

View File

@@ -1,7 +0,0 @@
namespace Jailbreak.LastGuard;
[Obsolete("No longer used, use FakeConvars")]
public class LastGuardConfig {
public string? LastGuardWeapon { get; } = "";
public int MinimumCTs { get; } = 4;
}

View File

@@ -20,16 +20,16 @@ namespace Jailbreak.LastRequest;
public class LastRequestManager(ILRLocale messages, IServiceProvider provider)
: ILastRequestManager, IDamageBlocker {
public readonly FakeConVar<int> CvLRBaseTime = new("css_jb_lr_time_base",
public static readonly FakeConVar<int> CV_LR_BASE_TIME = new("css_jb_lr_time_base",
"Round time to set when LR is activated, 0 to disable", 60);
public readonly FakeConVar<int> CvLRBonusTime = new("css_jb_lr_time_per_lr",
public static readonly FakeConVar<int> CV_LR_BONUS_TIME = new("css_jb_lr_time_per_lr",
"Additional round time to add per LR completion", 30);
public readonly FakeConVar<int> CvLRGuardTime =
public static readonly FakeConVar<int> CV_LR_GUARD_TIME =
new("css_jb_lr_time_per_guard", "Additional round time to add per guard");
public readonly FakeConVar<int> CvPrisonerToLR =
public static readonly FakeConVar<int> CV_PRISONER_TO_LR =
new("css_jb_lr_activate_lr_at", "Number of prisoners to activate LR at", 2,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(1, 32));
@@ -92,12 +92,12 @@ public class LastRequestManager(ILRLocale messages, IServiceProvider provider)
var cts = Utilities.GetPlayers()
.Count(p => p is { Team: CsTeam.CounterTerrorist, PawnIsAlive: true });
if (CvLRBaseTime.Value != 0) RoundUtil.SetTimeRemaining(CvLRBaseTime.Value);
if (CV_LR_BASE_TIME.Value != 0) RoundUtil.SetTimeRemaining(CV_LR_BASE_TIME.Value);
RoundUtil.AddTimeRemaining(CvLRGuardTime.Value * cts);
RoundUtil.AddTimeRemaining(CV_LR_GUARD_TIME.Value * cts);
foreach (var player in Utilities.GetPlayers()) {
player.ExecuteClientCommand($"play sounds/lr");
player.ExecuteClientCommand("play sounds/lr");
if (player.Team != CsTeam.Terrorist || !player.PawnIsAlive) continue;
if (died != null && player.SteamID == died.SteamID) continue;
player.ExecuteClientCommandFromServer("css_lr");
@@ -132,7 +132,7 @@ public class LastRequestManager(ILRLocale messages, IServiceProvider provider)
public bool EndLastRequest(AbstractLastRequest lr, LRResult result) {
if (result is LRResult.GUARD_WIN or LRResult.PRISONER_WIN) {
RoundUtil.AddTimeRemaining(CvLRBonusTime.Value);
RoundUtil.AddTimeRemaining(CV_LR_BONUS_TIME.Value);
messages.LastRequestDecided(lr, result).ToAllChat();
}
@@ -189,7 +189,7 @@ public class LastRequestManager(ILRLocale messages, IServiceProvider provider)
if (player.Team != CsTeam.Terrorist) return HookResult.Continue;
if (countAlivePrisoners() - 1 > CvPrisonerToLR.Value)
if (countAlivePrisoners() - 1 > CV_PRISONER_TO_LR.Value)
return HookResult.Continue;
if (Utilities.GetPlayers().All(p => p.Team != CsTeam.CounterTerrorist))
@@ -222,7 +222,7 @@ public class LastRequestManager(ILRLocale messages, IServiceProvider provider)
if (!IsLREnabledForRound) return HookResult.Continue;
if (player.Team != CsTeam.Terrorist) return HookResult.Continue;
if (countAlivePrisoners() > CvPrisonerToLR.Value)
if (countAlivePrisoners() > CV_PRISONER_TO_LR.Value)
return HookResult.Continue;
EnableLR();

View File

@@ -45,7 +45,8 @@ public class BulletForBullet : TeleportingRequest {
designerName = designer;
MenuManager.CloseActiveMenu(player);
msg.WeaponSelected(player, designerName).ToChat(Prisoner, Guard);
msg.WeaponSelected(player, designerName.GetFriendlyWeaponName())
.ToChat(Prisoner, Guard);
State = LRState.ACTIVE;

View File

@@ -1,6 +1,8 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Cvars.Validators;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
using Jailbreak.Formatting.Extensions;
@@ -25,11 +27,27 @@ public class C4Behavior(IC4Locale ic4Locale, IRebelService rebelService)
private BasePlugin? plugin;
public static readonly FakeConVar<bool> CV_GIVE_BOMB = new("css_jb_c4_give",
"Whether to give a random prisoner a bomb at the beginning of the round.",
true);
public static readonly FakeConVar<float> CV_C4_DELAY = new("css_jb_c4_delay",
"Time in seconds that the bomb takes to explode", .75f,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0, 2));
public static readonly FakeConVar<float> CV_C4_RADIUS =
new("css_jb_c4_radius", "Bomb explosion radius", 350,
ConVarFlags.FCVAR_NONE, new RangeValidator<float>(0, 10000));
public static readonly FakeConVar<float> CV_C4_BASE_DAMAGE =
new("css_jb_c4_damage", "Base damage to apply", 340, ConVarFlags.FCVAR_NONE,
new RangeValidator<float>(0, 10000));
public void ClearActiveC4s() { bombs.Clear(); }
public void TryGiveC4ToPlayer(CCSPlayerController player) {
var bombEntity = new CC4(player.GiveNamedItem("weapon_c4"));
bombs.Add(bombEntity, new C4Metadata(0.75f, false));
bombs.Add(bombEntity, new C4Metadata(false));
ic4Locale.JihadC4Received.ToChat(player);
ic4Locale.JihadC4Usage1.ToChat(player);
@@ -45,7 +63,6 @@ public class C4Behavior(IC4Locale ic4Locale, IRebelService rebelService)
tryEmitSound(player, "jb.jihad", 1, 1f, 0f);
bombs[bombEntity].Delay = delay;
bombs[bombEntity].IsDetonating = true;
rebelService.MarkRebel(player);
@@ -95,14 +112,15 @@ public class C4Behavior(IC4Locale ic4Locale, IRebelService rebelService)
|| activeWeapon.Handle != bomb.Handle)
continue;
StartDetonationAttempt(bombCarrier, meta.Delay, bomb);
StartDetonationAttempt(bombCarrier, CV_C4_DELAY.Value, bomb);
}
}
[GameEventHandler]
public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info) {
ClearActiveC4s();
TryGiveC4ToRandomTerrorist();
if (CV_GIVE_BOMB.Value) TryGiveC4ToRandomTerrorist();
return HookResult.Continue;
}
@@ -154,11 +172,10 @@ public class C4Behavior(IC4Locale ic4Locale, IRebelService rebelService)
var distanceFromBomb =
ct.PlayerPawn.Value!.AbsOrigin!.Distance(player.PlayerPawn.Value
.AbsOrigin!);
if (distanceFromBomb > 350f) continue;
if (distanceFromBomb > CV_C4_RADIUS.Value) continue;
// 350f = "bombRadius"
var damage = 340f;
damage *= (350f - distanceFromBomb) / 350f;
var damage = CV_C4_BASE_DAMAGE.Value;
damage *= (CV_C4_RADIUS.Value - distanceFromBomb) / CV_C4_RADIUS.Value;
float healthRef = ct.PlayerPawn.Value.Health;
if (healthRef <= damage) {
ct.CommitSuicide(true, true);
@@ -183,8 +200,7 @@ public class C4Behavior(IC4Locale ic4Locale, IRebelService rebelService)
volume, delay);
}
private class C4Metadata(float delay, bool isDetonating) {
public float Delay { get; set; } = delay;
private class C4Metadata(bool isDetonating) {
public bool IsDetonating { get; set; } = isDetonating;
}
}

View File

@@ -20,9 +20,9 @@ public class RebelManager(IRebelLocale notifs, IRichLogService logs)
[Obsolete("No longer used, use FakeConvar")]
public static readonly int MAX_REBEL_TIME = 45;
public readonly FakeConVar<int> CvRebelTime = new("css_jb_rebel_time",
"Time to mark a rebel for", 30, ConVarFlags.FCVAR_NONE,
new RangeValidator<int>(0, 500));
public static readonly FakeConVar<int> CV_REBEL_TIME =
new("css_jb_rebel_time", "Time to mark a rebel for", 45,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 500));
private readonly Dictionary<CCSPlayerController, long> rebelTimes = new();
private bool enabled = true;
@@ -71,7 +71,7 @@ public class RebelManager(IRebelLocale notifs, IRichLogService logs)
API.Stats?.PushStat(new ServerStat("JB_REBEL_STARTED",
$"{player.SteamID} {pos.X:F2} {pos.Y:F2} {pos.Z:F2}"));
if (time == -1) time = CvRebelTime.Value;
if (time == -1) time = CV_REBEL_TIME.Value;
rebelTimes[player] = DateTimeOffset.Now.ToUnixTimeSeconds() + time;
applyRebelColor(player);
@@ -128,10 +128,10 @@ public class RebelManager(IRebelLocale notifs, IRichLogService logs)
// https://www.desmos.com/calculator/g2v6vvg7ax
private float getRebelTimePercentage(CCSPlayerController player) {
var x = GetRebelTimeLeft(player);
if (x > CvRebelTime.Value) return 1;
if (x > CV_REBEL_TIME.Value) return 1;
if (x <= 0) return 0;
return (float)(100 - (CvRebelTime.Value - x)
* Math.Sqrt(CvRebelTime.Value - x) / 3.8f) / 100;
return (float)(100 - (CV_REBEL_TIME.Value - x)
* Math.Sqrt(CV_REBEL_TIME.Value - x) / 3.8f) / 100;
}
private Color getRebelColor(CCSPlayerController player) {

View File

@@ -20,15 +20,15 @@ namespace Jailbreak.SpecialDay;
public class SpecialDayCommand(IWardenService warden,
ISpecialDayFactory factory, IWardenLocale wardenMsg, ISDLocale sdMsg,
ISpecialDayManager sd) : IPluginBehavior {
public static FakeConVar<int> CvRoundsBetweenSD = new(
public static readonly FakeConVar<int> CV_ROUNDS_BETWEEN_SD = new(
"css_jb_sd_round_cooldown", "Rounds between special days", 4);
public static FakeConVar<int> CvMaxElapsedTime = new(
public static readonly FakeConVar<int> CV_MAX_ELAPSED_TIME = new(
"css_jb_sd_max_elapsed_time",
"Max time elapsed in a round to be able to call a special day", 30);
private SpecialDayMenuSelector? menuSelector;
private BasePlugin? plugin;
private SpecialDayMenuSelector menuSelector = null!;
private BasePlugin plugin = null!;
// css_lr <player> <LRType>
public void Start(BasePlugin basePlugin) {
@@ -41,6 +41,17 @@ public class SpecialDayCommand(IWardenService warden,
[ConsoleCommand("css_startday", "Start a special day as the warden")]
public void Command_SpecialDay(CCSPlayerController? executor,
CommandInfo info) {
if (executor != null && sd.IsSDRunning && info.ArgCount == 1) {
// SD is already running
if (sd.CurrentSD is ISpecialDayMessageProvider messaged)
sdMsg.SpecialDayRunning(messaged.Locale.Name).ToChat(executor);
else
sdMsg.SpecialDayRunning(sd.CurrentSD?.Type.ToString() ?? "Unknown")
.ToChat(executor);
return;
}
if (executor != null
&& !AdminManager.PlayerHasPermissions(executor, "@css/rcon")) {
if (!warden.IsWarden(executor) || RoundUtil.IsWarmup()) {
@@ -59,14 +70,14 @@ public class SpecialDayCommand(IWardenService warden,
return;
}
var roundsToNext = sd.RoundsSinceLastSD - CvRoundsBetweenSD.Value;
var roundsToNext = sd.RoundsSinceLastSD - CV_ROUNDS_BETWEEN_SD.Value;
if (roundsToNext < 0) {
sdMsg.SpecialDayCooldown(Math.Abs(roundsToNext)).ToChat(executor);
return;
}
if (RoundUtil.GetTimeElapsed() > CvMaxElapsedTime.Value) {
sdMsg.TooLateForSpecialDay(CvMaxElapsedTime.Value);
if (RoundUtil.GetTimeElapsed() > CV_MAX_ELAPSED_TIME.Value) {
sdMsg.TooLateForSpecialDay(CV_MAX_ELAPSED_TIME.Value);
return;
}
}
@@ -77,8 +88,7 @@ public class SpecialDayCommand(IWardenService warden,
return;
}
MenuManager.OpenCenterHtmlMenu(plugin!, executor,
menuSelector!.GetMenu());
MenuManager.OpenCenterHtmlMenu(plugin, executor, menuSelector.GetMenu());
return;
}

View File

@@ -11,7 +11,7 @@ using Jailbreak.Zones;
namespace Jailbreak.SpecialDay.SpecialDays;
public abstract class AbstractZoneRestrictedDay : AbstractSpecialDay {
protected readonly CsTeam RestrictedTeam;
protected CsTeam RestrictedTeam;
protected readonly IList<MovementRestrictor> Restrictors =
new List<MovementRestrictor>();

View File

@@ -23,7 +23,6 @@ public class FFADay(BasePlugin plugin, IServiceProvider provider)
base.Setup();
}
public override void Execute() {
base.Execute();
Locale.BeginsIn(0).ToAllChat();

View File

@@ -1,4 +1,7 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Cvars.Validators;
using CounterStrikeSharp.API.Modules.Utils;
using Jailbreak.English.SpecialDay;
using Jailbreak.Formatting.Base;
@@ -8,6 +11,7 @@ using Jailbreak.Public.Extensions;
using Jailbreak.Public.Mod.SpecialDay;
using Jailbreak.Public.Mod.SpecialDay.Enums;
using Jailbreak.Public.Utils;
using Jailbreak.Validator;
namespace Jailbreak.SpecialDay.SpecialDays;
@@ -15,68 +19,161 @@ public class HideAndSeekDay(BasePlugin plugin, IServiceProvider provider)
: AbstractArmoryRestrictedDay(plugin, provider), ISpecialDayMessageProvider {
public override SDType Type => SDType.HNS;
private HNSDayLocale msg => (HNSDayLocale)Locale;
public override SpecialDaySettings Settings => new HNSSettings();
public override IView ArmoryReminder => msg.StayInArmory;
private HNSDayLocale Msg => (HNSDayLocale)Locale;
public override SpecialDaySettings Settings => new HnsSettings();
public override IView ArmoryReminder => Msg.StayInArmory;
public ISDInstanceLocale Locale => new HNSDayLocale();
// Set to -1 to not modify values
public static readonly FakeConVar<int> CV_PRISONER_PRE_HEALTH = new(
"jb_sd_hns_hide_hp_t", "Health to give to prisoners during HNS hide time",
300, ConVarFlags.FCVAR_NONE, new NonZeroRangeValidator<int>(-1, 1000));
public static readonly FakeConVar<int> CV_GUARD_PRE_HEALTH =
new("jb_sd_hns_hide_hp_ct", "Health to give to guards during HNS hide time",
150, ConVarFlags.FCVAR_NONE, new RangeValidator<int>(-1, 1000));
public static readonly FakeConVar<int> CV_PRISONER_PRE_ARMOR = new(
"jb_sd_hns_hide_armor_t", "Armor to give to prisoners during HNS hide time",
200, ConVarFlags.FCVAR_NONE, new RangeValidator<int>(-1, 1000));
public static readonly FakeConVar<int> CV_GUARD_PRE_ARMOR =
new("jb_sd_hns_hide_armor_ct",
"Armor to give to guards during HNS hide time", 300,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(-1, 1000));
public static readonly FakeConVar<int> CV_PRISONER_POST_HEALTH = new(
"jb_sd_hns_seek_hp_t", "Health to give to prisoners during HNS seek time",
300, ConVarFlags.FCVAR_NONE, new NonZeroRangeValidator<int>(1, 1000));
public static readonly FakeConVar<int> CV_GUARD_POST_HEALTH = new(
"jb_sd_hns_seek_hp_ct", "Health to give to guards during HNS seek time", 25,
ConVarFlags.FCVAR_NONE, new NonZeroRangeValidator<int>(1, 1000));
public static readonly FakeConVar<int> CV_PRISONER_POST_ARMOR = new(
"jb_sd_hns_seek_armor_t", "Armor to give to prisoners during HNS seek time",
500, ConVarFlags.FCVAR_NONE, new RangeValidator<int>(-1, 1000));
public static readonly FakeConVar<int> CV_GUARD_POST_ARMOR = new(
"jb_sd_hns_seek_armor_ct", "Armor to give to guards during HNS seek time",
-1, ConVarFlags.FCVAR_NONE, new RangeValidator<int>(-1, 1000));
public static readonly FakeConVar<string> CV_GUARD_WEAPONS = new(
"jb_sd_hns_weapons_ct",
"List of weapons/items CTs may use, empty for no restrictions",
string.Join(",", Tag.PISTOLS.Union(Tag.UTILITY)), ConVarFlags.FCVAR_NONE,
new ItemValidator(allowMultiple: true));
public static readonly FakeConVar<string> CV_PRISONER_WEAPONS = new(
"jb_sd_hns_weapons_t",
"List of weapons/items Ts may use, empty for no restrictions", "",
ConVarFlags.FCVAR_NONE, new ItemValidator(allowMultiple: true));
public static readonly FakeConVar<string> CV_SEEKER_TEAM =
new("jb_sd_hns_seekers", "Team to assign as seekers and restrict to armory",
"t", ConVarFlags.FCVAR_NONE, new TeamValidator(false));
public static readonly FakeConVar<int> CV_SEEK_TIME =
new("jb_sd_hns_seektime",
"Duration in seconds to give the hiders time to hide", 45,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(5, 120));
private CsTeam? SeekerTeam => TeamUtil.FromString(CV_SEEKER_TEAM.Value);
private CsTeam? HiderTeam
=> (SeekerTeam ?? CsTeam.Terrorist) == CsTeam.Terrorist ?
CsTeam.CounterTerrorist :
CsTeam.Terrorist;
public override void Setup() {
Timers[10] += () => {
foreach (var ct in PlayerUtil.FromTeam(CsTeam.CounterTerrorist))
ct.SetSpeed(1.5f);
if (SeekerTeam == null || HiderTeam == null) return;
RestrictedTeam = SeekerTeam.Value;
msg.DamageWarning(15).ToTeamChat(CsTeam.CounterTerrorist);
if (CV_SEEK_TIME.Value >= 10)
Timers[10] += () => {
foreach (var ct in PlayerUtil.FromTeam(SeekerTeam.Value))
ct.SetSpeed(1.5f);
Locale.BeginsIn(35).ToAllChat();
};
Timers[25] += () => {
foreach (var ct in PlayerUtil.FromTeam(CsTeam.CounterTerrorist)) {
ct.SetSpeed(1.25f);
EnableDamage(ct);
}
};
Timers[30] += () => {
foreach (var ct in PlayerUtil.FromTeam(CsTeam.CounterTerrorist))
ct.SetSpeed(1.1f);
Locale.BeginsIn(15).ToAllChat();
};
Timers[45] += Execute;
Msg.DamageWarning(15).ToTeamChat(SeekerTeam.Value);
};
if (CV_SEEK_TIME.Value >= 25)
Timers[25] += () => {
foreach (var player in PlayerUtil.FromTeam(SeekerTeam.Value)) {
player.SetSpeed(1.25f);
EnableDamage(player);
}
};
if (CV_SEEK_TIME.Value >= 30)
Timers[30] += () => {
foreach (var ct in PlayerUtil.FromTeam(SeekerTeam.Value))
ct.SetSpeed(1.1f);
};
for (var offset = 15; offset < CV_SEEK_TIME.Value; offset += 15) {
var beginsIn = CV_SEEK_TIME.Value - offset;
Timers[CV_SEEK_TIME.Value - offset] +=
() => Locale.BeginsIn(beginsIn).ToAllChat();
}
Timers[CV_SEEK_TIME.Value] += Execute;
base.Setup();
foreach (var ct in PlayerUtil.FromTeam(CsTeam.CounterTerrorist))
ct.SetSpeed(2f);
foreach (var player in PlayerUtil.FromTeam(SeekerTeam.Value))
player.SetSpeed(2f);
}
public override void Execute() {
if (SeekerTeam == null || HiderTeam == null) return;
base.Execute();
foreach (var t in PlayerUtil.FromTeam(CsTeam.Terrorist)) t.SetArmor(100);
foreach (var ct in PlayerUtil.FromTeam(CsTeam.CounterTerrorist))
ct.SetSpeed(1);
foreach (var player in PlayerUtil.GetAlive()) {
var hp = (player.Team == CsTeam.Terrorist ?
CV_PRISONER_POST_HEALTH :
CV_GUARD_POST_HEALTH).Value;
var armor = (player.Team == CsTeam.Terrorist ?
CV_PRISONER_POST_ARMOR :
CV_GUARD_POST_ARMOR).Value;
if (hp != -1) player.SetHealth(hp);
if (armor != -1) player.SetArmor(armor);
}
foreach (var ct in PlayerUtil.FromTeam(SeekerTeam.Value)) ct.SetSpeed(1);
}
public class HNSSettings : SpecialDaySettings {
public HNSSettings() {
public class HnsSettings : SpecialDaySettings {
private readonly ISet<string>? cachedGuardWeapons, cachedPrisonerWeapons;
public HnsSettings() {
AllowLastRequests = true;
TTeleport = TeleportType.ARMORY;
CtTeleport = TeleportType.ARMORY;
cachedGuardWeapons = CV_GUARD_WEAPONS.Value.Split(",").ToHashSet();
cachedPrisonerWeapons = CV_PRISONER_WEAPONS.Value.Split(",").ToHashSet();
if (cachedGuardWeapons.Count == 0) cachedGuardWeapons = null;
if (cachedPrisonerWeapons.Count == 0) cachedPrisonerWeapons = null;
}
public override int InitialHealth(CCSPlayerController player) {
return player.Team == CsTeam.Terrorist ? 250 : 50;
return player.Team == CsTeam.Terrorist ?
CV_PRISONER_PRE_HEALTH.Value :
CV_GUARD_PRE_HEALTH.Value;
}
public override int InitialArmor(CCSPlayerController player) {
if (player.Team != CsTeam.Terrorist) return -1;
return 500;
return player.Team == CsTeam.Terrorist ?
CV_PRISONER_PRE_ARMOR.Value :
CV_GUARD_PRE_ARMOR.Value;
}
public override ISet<string>? AllowedWeapons(CCSPlayerController player) {
if (player.Team != CsTeam.CounterTerrorist) return null;
return Tag.PISTOLS.Union(Tag.UTILITY).ToHashSet();
return player.Team == CsTeam.Terrorist ?
cachedPrisonerWeapons :
cachedGuardWeapons;
}
}
}

View File

@@ -12,13 +12,11 @@ using Jailbreak.Public.Utils;
namespace Jailbreak.SpecialDay.SpecialDays;
public class InfectionDay : AbstractArmoryRestrictedDay,
ISpecialDayMessageProvider {
public class InfectionDay(BasePlugin plugin, IServiceProvider provider)
: AbstractArmoryRestrictedDay(plugin, provider, CsTeam.CounterTerrorist),
ISpecialDayMessageProvider {
private readonly ICollection<int> swappedPrisoners = new HashSet<int>();
public InfectionDay(BasePlugin Plugin, IServiceProvider provider) : base(
Plugin, provider, CsTeam.CounterTerrorist) { }
public override SDType Type => SDType.INFECTION;
public override SpecialDaySettings Settings => new InfectionSettings();
@@ -40,12 +38,12 @@ public class InfectionDay : AbstractArmoryRestrictedDay,
foreach (var ct in PlayerUtil.FromTeam(CsTeam.CounterTerrorist))
ct.SetColor(Color.LimeGreen);
Plugin.RegisterEventHandler<EventPlayerDeath>(OnPlayerDeath);
Plugin.RegisterEventHandler<EventPlayerSpawn>(OnRespawn);
Plugin.RegisterEventHandler<EventPlayerDeath>(onPlayerDeath);
Plugin.RegisterEventHandler<EventPlayerSpawn>(onRespawn);
}
private HookResult
OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info) {
onPlayerDeath(EventPlayerDeath @event, GameEventInfo info) {
var player = @event.Userid;
if (player == null || !player.IsValid) return HookResult.Continue;
if (player.Team != CsTeam.Terrorist) return HookResult.Continue;
@@ -101,7 +99,7 @@ public class InfectionDay : AbstractArmoryRestrictedDay,
return HookResult.Continue;
}
public HookResult OnRespawn(EventPlayerSpawn @event, GameEventInfo info) {
private HookResult onRespawn(EventPlayerSpawn @event, GameEventInfo info) {
var player = @event.Userid;
if (player == null || !player.IsValid) return HookResult.Continue;
if (player.Team != CsTeam.CounterTerrorist) return HookResult.Continue;
@@ -119,13 +117,13 @@ public class InfectionDay : AbstractArmoryRestrictedDay,
override protected HookResult
OnEnd(EventRoundEnd @event, GameEventInfo info) {
var result = base.OnEnd(@event, info);
Plugin.DeregisterEventHandler<EventPlayerDeath>(OnPlayerDeath);
Plugin.DeregisterEventHandler<EventPlayerSpawn>(OnRespawn);
Plugin.DeregisterEventHandler<EventPlayerDeath>(onPlayerDeath);
Plugin.DeregisterEventHandler<EventPlayerSpawn>(onRespawn);
Plugin.AddTimer(0.1f, () => {
foreach (var index in swappedPrisoners) {
var player = Utilities.GetPlayerFromSlot(index);
if (player == null) continue;
if (player == null || !player.IsValid) continue;
player.SwitchTeam(CsTeam.Terrorist);
}
});

View File

@@ -1,11 +1,14 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Cvars.Validators;
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;
namespace Jailbreak.SpecialDay.SpecialDays;
@@ -19,18 +22,42 @@ public class NoScopeDay(BasePlugin plugin, IServiceProvider provider)
public override SpecialDaySettings Settings => new NoScopeSettings();
public static readonly FakeConVar<string> CV_WEAPON = new(
"jb_sd_noscope_weapon",
"Weapon to give to all players, recommended it be a weapon with a scope (duh)",
"weapon_ssg08", ConVarFlags.FCVAR_NONE,
new ItemValidator(allowMultiple: true));
public static readonly FakeConVar<string> CV_WEAPON_WHITELIST = new(
"jb_sd_noscope_allowedweapons",
"Weapons to allow players to use, empty for no restrictions",
string.Join(",",
Tag.UTILITY.Union(new[] { "weapon_ssg08", "weapon_knife" }.ToHashSet())),
ConVarFlags.FCVAR_NONE, new ItemValidator(allowMultiple: true));
public static readonly FakeConVar<int> CV_KNIFE_DELAY = new(
"jb_sd_noscope_knife_delay",
"Time delay in seconds to give knives at, 0 to disable", 120,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(0, 500));
public static readonly FakeConVar<float> CV_GRAVITY =
new("jb_sd_noscope_gravity",
"Gravity to set during the special day, default is 800", 200f);
public override void Setup() {
Timers[120] += () => {
foreach (var player in PlayerUtil.GetAlive())
player.GiveNamedItem("weapon_knife");
};
if (CV_KNIFE_DELAY.Value > 0)
Timers[CV_KNIFE_DELAY.Value] += () => {
foreach (var player in PlayerUtil.GetAlive())
player.GiveNamedItem("weapon_knife");
};
base.Setup();
}
public override void Execute() {
foreach (var player in PlayerUtil.GetAlive()) {
player.RemoveWeapons();
player.GiveNamedItem("weapon_ssg08");
foreach (var weapon in CV_WEAPON.Value.Split(","))
player.GiveNamedItem(weapon);
}
base.Execute();
@@ -50,8 +77,9 @@ public class NoScopeDay(BasePlugin plugin, IServiceProvider provider)
if (activeWeapon == null || !activeWeapon.IsValid) return;
activeWeapon.NextSecondaryAttackTick = Server.TickCount + 500;
if (activeWeapon.DesignerName is "weapon_ssg08" or "weapon_knife") return;
if (Tag.UTILITY.Contains(activeWeapon.DesignerName)) return;
if (CV_WEAPON_WHITELIST.Value.Contains(activeWeapon.DesignerName,
StringComparison.CurrentCultureIgnoreCase))
return;
activeWeapon.NextPrimaryAttackTick = Server.TickCount + 500;
}
@@ -61,7 +89,7 @@ public class NoScopeDay(BasePlugin plugin, IServiceProvider provider)
TTeleport = TeleportType.RANDOM;
RestrictWeapons = true;
ConVarValues["sv_gravity"] = (float)200;
ConVarValues["sv_gravity"] = CV_GRAVITY.Value;
ConVarValues["sv_infinite_ammo"] = 2;
}

View File

@@ -1,10 +1,13 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
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;
namespace Jailbreak.SpecialDay.SpecialDays;
@@ -18,6 +21,15 @@ public class OneInTheChamberDay(BasePlugin plugin, IServiceProvider provider)
public override SpecialDaySettings Settings => new OitcSettings();
public static readonly FakeConVar<string> CV_WEAPON = new("jb_sd_oitc_weapon",
"Weapon to give to players for the day", "weapon_deagle",
ConVarFlags.FCVAR_NONE, new ItemValidator(ItemValidator.WeaponType.GUNS));
public static readonly FakeConVar<string> CV_ADDITIONAL_WEAPON = new(
"jb_sd_oitc_additionalweapon",
"Additional (non-ammo restricted) weapons to give for the day",
"weapon_knife", ConVarFlags.FCVAR_NONE, new ItemValidator());
public override void Setup() {
base.Setup();
Plugin.RegisterEventHandler<EventPlayerHurt>(OnPlayerDamage);
@@ -29,9 +41,12 @@ public class OneInTheChamberDay(BasePlugin plugin, IServiceProvider provider)
foreach (var player in PlayerUtil.GetAlive()) {
player.RemoveWeapons();
player.GiveNamedItem("weapon_knife");
player.GiveNamedItem("weapon_deagle");
player.GetWeaponBase("weapon_deagle")?.SetAmmo(1, 0);
if (CV_ADDITIONAL_WEAPON.Value.Length > 0)
player.GiveNamedItem(CV_ADDITIONAL_WEAPON.Value);
if (CV_WEAPON.Value.Length > 0) {
player.GiveNamedItem(CV_WEAPON.Value);
player.GetWeaponBase(CV_WEAPON.Value)?.SetAmmo(1, 0);
}
}
}
@@ -46,7 +61,7 @@ public class OneInTheChamberDay(BasePlugin plugin, IServiceProvider provider)
private HookResult
OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info) {
if (@event.Attacker == null) return HookResult.Continue;
@event.Attacker.GetWeaponBase("weapon_deagle")?.AddBulletsToMagazine(1);
@event.Attacker.GetWeaponBase(CV_WEAPON.Value)?.AddBulletsToMagazine(1);
return HookResult.Continue;
}
@@ -56,19 +71,21 @@ public class OneInTheChamberDay(BasePlugin plugin, IServiceProvider provider)
Plugin.DeregisterEventHandler<EventPlayerDeath>(OnPlayerDeath);
return base.OnEnd(@event, info);
}
}
public class OitcSettings : SpecialDaySettings {
public OitcSettings() {
CtTeleport = TeleportType.RANDOM;
TTeleport = TeleportType.RANDOM;
RestrictWeapons = true;
WithFriendlyFire();
public class OitcSettings : SpecialDaySettings {
public OitcSettings() {
CtTeleport = TeleportType.RANDOM;
TTeleport = TeleportType.RANDOM;
RestrictWeapons = true;
WithFriendlyFire();
ConVarValues["mp_death_drop_gun"] = 0;
}
ConVarValues["mp_death_drop_gun"] = 0;
}
public override ISet<string> AllowedWeapons(CCSPlayerController player) {
return new HashSet<string> { "weapon_deagle", "weapon_knife" };
public override ISet<string> AllowedWeapons(CCSPlayerController player) {
return new HashSet<string> {
CV_WEAPON.Value, CV_ADDITIONAL_WEAPON.Value
};
}
}
}

View File

@@ -26,56 +26,56 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
: AbstractSpecialDay(plugin, provider), ISpecialDayMessageProvider {
private const int MAX_POINTS = 500;
public static readonly FakeConVar<int> CvInitialSpeedrunTime =
public static readonly FakeConVar<int> CV_INITIAL_SPEEDRUN_TIME =
new("css_jb_speedrun_initial_time",
"Duration in seconds to grant the speedrunner", 40);
public static readonly FakeConVar<int> CvFirstRoundFreeze =
public static readonly FakeConVar<int> CV_FIRST_ROUND_FREEZE =
new("css_jb_speedrun_first_round_freeze",
"Duration in seconds to give players time to read the rules of speedrun",
6);
public static readonly FakeConVar<int> CvFreezeTime =
public static readonly FakeConVar<int> CV_FREEZE_TIME =
new("css_jb_speedrun_freeze_time",
"Duration in seconds to freeze players before the speedrun starts", 2);
public static readonly FakeConVar<int> CvWinTimeBase =
public static readonly FakeConVar<int> CV_WIN_TIME_BASE =
new("css_jb_speedrun_win_time_base",
"Base duration in seconds to give the winner to kill other competitors",
25);
public static readonly FakeConVar<int> CvWinTimeBonus =
public static readonly FakeConVar<int> CV_WIN_TIME_BONUS =
new("css_jb_speedrun_win_time_bonus",
"Bonus duration in seconds to give the winner for every competitor", 5);
public static readonly FakeConVar<int> CvWinTimeMax =
public static readonly FakeConVar<int> CV_WIN_TIME_MAX =
new("css_jb_speedrun_win_max",
"Max time to give the winner regardless of bonus", 60);
public static readonly FakeConVar<string> CvWinWeapon = new(
public static readonly FakeConVar<string> CV_WIN_WEAPONS = new(
"css_jb_speedrun_win_weapons",
"Weapon(s) to give to the winner to kill other competitors",
"weapon_knife,weapon_negev",
customValidators: new WeaponValidator(allowMultiple: true));
customValidators: new ItemValidator(allowMultiple: true));
public static readonly FakeConVar<string> CvLosersWeapon = new(
public static readonly FakeConVar<string> CV_LOSERS_WEAPONS = new(
"css_jb_speedrun_loser_weapons",
"Weapon(s) to give to the losers to use against the winner", "",
customValidators: new WeaponValidator(allowMultiple: true));
customValidators: new ItemValidator(allowMultiple: true));
public static readonly FakeConVar<bool> CvWinnerDamageable = new(
public static readonly FakeConVar<bool> CV_WINNER_DAMAGEABLE = new(
"css_jb_speedrun_winner_damageable", "Whether the winner can be damaged");
public static readonly FakeConVar<bool> CvLoserDamageable = new(
public static readonly FakeConVar<bool> CV_LOSER_DAMAGEABLE = new(
"css_jb_speedrun_loser_damageable", "Whether the losers can be damaged",
true);
public static readonly FakeConVar<int> CvTeleportType =
public static readonly FakeConVar<int> CV_TELEPORT_TYPE =
new("css_jb_speedrun_teleport_type",
"0 = Dont teleport at end, 1 = Teleport losers to winner, 2 = Teleport winner to loser(s)",
2);
public static readonly FakeConVar<int> CvMaxPlayersToFinish = new(
public static readonly FakeConVar<int> CV_MAX_PLAYERS_TO_FINISH = new(
"css_jb_speedrun_finish_at",
"Number of players required to declare a winner", 2,
customValidators: new RangeValidator<int>(2, 10));
@@ -88,7 +88,7 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
/// Positive values represent players who are still alive, and the value
/// being the distance they are from the target.
/// </summary>
private readonly HashSet<int> finishedPlayers = new();
private readonly HashSet<int> finishedPlayers = [];
private readonly Random rng = new();
private float? bestTime;
@@ -96,7 +96,7 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
private AbstractTrail<BeamTrailSegment>? bestTrail;
private LinkedList<(int, float)> finishTimestampList = new();
private LinkedList<(int, float)> finishTimestampList = [];
private IGenericCmdLocale generics = null!;
private int round, playersAliveAtStart;
@@ -107,9 +107,9 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
private Vector? start;
private Vector? target;
private BeamCircle? targetCircle;
private ISpeedDayLocale msg => (ISpeedDayLocale)Locale;
private ISpeedDayLocale Msg => (ISpeedDayLocale)Locale;
private bool isRoundActive
private bool IsRoundActive
=> Provider.GetRequiredService<ISpecialDayManager>().CurrentSD == this;
public override SDType Type => SDType.SPEEDRUN;
@@ -144,7 +144,7 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
player.SetColor(Color.FromArgb(100, 255, 255, 255));
}
};
Timers[CvFirstRoundFreeze.Value - 4] += () => {
Timers[CV_FIRST_ROUND_FREEZE.Value - 4] += () => {
if (!speedrunner.IsValid || speedrunner.Connected
!= PlayerConnectedState.PlayerConnected)
speedrunner = getRunner();
@@ -153,11 +153,11 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
return;
}
msg.RunnerAssigned(speedrunner).ToAllChat();
Msg.RunnerAssigned(speedrunner).ToAllChat();
speedrunner.SetColor(Color.DodgerBlue);
msg.YouAreRunner(CvInitialSpeedrunTime.Value).ToChat(speedrunner);
Msg.YouAreRunner(CV_INITIAL_SPEEDRUN_TIME.Value).ToChat(speedrunner);
};
Timers[CvFirstRoundFreeze.Value] += () => {
Timers[CV_FIRST_ROUND_FREEZE.Value] += () => {
if (!speedrunner.IsValid || speedrunner.Connected
!= PlayerConnectedState.PlayerConnected) {
speedrunner = getRunner();
@@ -168,8 +168,8 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
}
speedrunner.SetColor(Color.DodgerBlue);
msg.RunnerLeftAndReassigned(speedrunner).ToAllChat();
msg.YouAreRunner(CvInitialSpeedrunTime.Value).ToChat(speedrunner);
Msg.RunnerLeftAndReassigned(speedrunner).ToAllChat();
Msg.YouAreRunner(CV_INITIAL_SPEEDRUN_TIME.Value).ToChat(speedrunner);
}
start = speedrunner.PlayerPawn.Value!.AbsOrigin!.Clone();
@@ -177,8 +177,9 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
bestTrail = createFirstTrail(speedrunner);
};
if (CvInitialSpeedrunTime.Value > 30)
Timers[CvInitialSpeedrunTime.Value + CvFirstRoundFreeze.Value - 30] += ()
if (CV_INITIAL_SPEEDRUN_TIME.Value > 30)
Timers[
CV_INITIAL_SPEEDRUN_TIME.Value + CV_FIRST_ROUND_FREEZE.Value - 30] += ()
=> {
if (target != null) return;
if (!speedrunner.IsValid || speedrunner.Connected
@@ -190,15 +191,16 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
return;
}
msg.RuntimeLeft(RoundUtil.GetTimeRemaining()).ToChat(speedrunner);
Msg.RuntimeLeft(RoundUtil.GetTimeRemaining()).ToChat(speedrunner);
};
Timers[CvInitialSpeedrunTime.Value + CvFirstRoundFreeze.Value - 10] += ()
=> {
if (target != null) return;
msg.RuntimeLeft(RoundUtil.GetTimeRemaining()).ToChat(speedrunner);
};
Timers[CvInitialSpeedrunTime.Value + CvFirstRoundFreeze.Value] += Execute;
Timers[CV_INITIAL_SPEEDRUN_TIME.Value + CV_FIRST_ROUND_FREEZE.Value - 10] +=
() => {
if (target != null) return;
Msg.RuntimeLeft(RoundUtil.GetTimeRemaining()).ToChat(speedrunner);
};
Timers[CV_INITIAL_SPEEDRUN_TIME.Value + CV_FIRST_ROUND_FREEZE.Value] +=
Execute;
base.Setup();
}
@@ -234,7 +236,7 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
bestTime = timeSpent;
var minTime = CvInitialSpeedrunTime.Value * 0.5;
var minTime = CV_INITIAL_SPEEDRUN_TIME.Value * 0.5;
startRound((int)Math.Ceiling(Math.Max(timeSpent * 1.1, minTime)));
@@ -290,8 +292,8 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
furthest.Pawn.Value?.Teleport(end);
furthest.SetColor(Color.DodgerBlue);
msg.RunnerAFKAndReassigned(furthest).ToAllChat();
msg.YouAreRunner(RoundUtil.GetTimeRemaining()).ToChat(furthest);
Msg.RunnerAFKAndReassigned(furthest).ToAllChat();
Msg.YouAreRunner(RoundUtil.GetTimeRemaining()).ToChat(furthest);
trail.StartTracking(furthest);
trail.DidntMoveTicks = 0;
return;
@@ -299,7 +301,7 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
if (trail.DidntMoveTicks < thresholdTicks) return;
if (trail.DidntMoveTicks == thresholdTicks)
msg.StayStillToSpeedup.ToChat(trail.Player);
Msg.StayStillToSpeedup.ToChat(trail.Player);
if (didntMoveSeconds % 2 == 0) RoundUtil.AddTimeRemaining(-1);
if (RoundUtil.GetTimeRemaining() <= 0) Execute();
};
@@ -327,8 +329,8 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
speedrunner = nearest;
nearest.Pawn.Value?.Teleport(end);
nearest.SetColor(Color.DodgerBlue);
msg.RunnerLeftAndReassigned(nearest).ToAllChat();
msg.YouAreRunner(RoundUtil.GetTimeRemaining()).ToChat(nearest);
Msg.RunnerLeftAndReassigned(nearest).ToAllChat();
Msg.YouAreRunner(RoundUtil.GetTimeRemaining()).ToChat(nearest);
trail.StartTracking(nearest);
};
return trail;
@@ -344,17 +346,17 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
private void startRound(int seconds) {
roundStartTime = null;
if (!isRoundActive) {
if (!IsRoundActive) {
panic("Round is not active but we are in startRound");
return;
}
var alive = PlayerUtil.GetAlive().ToArray();
playersAliveAtStart = PlayerUtil.GetAlive().Count();
msg.BeginRound(++round, getEliminations(playersAliveAtStart), seconds)
Msg.BeginRound(++round, getEliminations(playersAliveAtStart), seconds)
.ToAllChat();
RoundUtil.SetTimeRemaining(seconds + CvFreezeTime.Value);
RoundUtil.SetTimeRemaining(seconds + CV_FREEZE_TIME.Value);
foreach (var player in alive) {
var pawn = player.PlayerPawn.Value;
@@ -367,19 +369,19 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
finishedPlayers.Clear();
finishTimestampList.Clear();
Plugin.AddTimer(CvFreezeTime.Value, () => {
if (!isRoundActive) return;
Plugin.AddTimer(CV_FREEZE_TIME.Value, () => {
if (!IsRoundActive) return;
foreach (var player in PlayerUtil.GetAlive()) player.UnFreeze();
roundStartTime = Server.CurrentTime;
}, TimerFlags.STOP_ON_MAPCHANGE);
roundEndTimer = Plugin.AddTimer(seconds + CvFreezeTime.Value, endRound,
roundEndTimer = Plugin.AddTimer(seconds + CV_FREEZE_TIME.Value, endRound,
TimerFlags.STOP_ON_MAPCHANGE);
}
private void checkFinishers() {
if (target == null || roundStartTime == null) return;
if (!isRoundActive) {
if (!IsRoundActive) {
panic("Round is not active but we are in checkFinishers");
return;
}
@@ -432,9 +434,9 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
foreach (var (slot, dist) in unfinished)
finishTimestampList.AddLast((slot, dist));
const int TOTAL_LINES = 8;
var pos = 1;
var current = finishTimestampList.First;
const int totalLines = 8;
var pos = 1;
var current = finishTimestampList.First;
string? top = null;
while (current != null) {
@@ -451,14 +453,14 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
var playerLine = current;
var d = 0;
while (playerLine != null && (display < TOTAL_LINES / 2
|| pos - d > finishTimestampList.Count - TOTAL_LINES
&& display < TOTAL_LINES)) {
while (playerLine != null && (display < totalLines / 2
|| pos - d > finishTimestampList.Count - totalLines
&& display < totalLines)) {
var (slot, dist) = playerLine.Value;
playerLine = playerLine.Previous;
var p = Utilities.GetPlayerFromSlot(slot);
if (p == null) continue;
lines = generateHTMLLine(p, pos - d++, dist) + (d == 1 ? "" : "<br>")
lines = generateHtmlLine(p, pos - d++, dist) + (d == 1 ? "" : "<br>")
+ lines;
display++;
}
@@ -467,12 +469,12 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
playerLine = current;
d = 0;
while (playerLine != null && display < TOTAL_LINES) {
while (playerLine != null && display < totalLines) {
var (slot, dist) = playerLine.Value;
playerLine = playerLine.Next;
var p = Utilities.GetPlayerFromSlot(slot);
if (p == null) continue;
lines += "<br>" + generateHTMLLine(p, pos + ++d, dist);
lines += "<br>" + generateHtmlLine(p, pos + ++d, dist);
display++;
}
@@ -488,7 +490,7 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
finishTimestampList = originalCompletions;
}
private string generateHTMLLine(CCSPlayerController player, int position,
private string generateHtmlLine(CCSPlayerController player, int position,
float distance) {
string color;
var eliminations = getEliminations(playersAliveAtStart);
@@ -541,10 +543,10 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
if (bestTime == null || time < bestTime) {
bestTime = time;
bestTimePlayerSlot = player.Slot;
msg.BestTime(player, time).ToAllChat();
Msg.BestTime(player, time).ToAllChat();
player.SetColor(Color.FromArgb(255, Color.Gold));
} else {
msg.PlayerTime(player, finishedPlayers.Count + 1, -time).ToAllChat();
Msg.PlayerTime(player, finishedPlayers.Count + 1, -time).ToAllChat();
}
finishTimestampList.AddLast((player.Slot, -Server.CurrentTime));
@@ -605,7 +607,7 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
return;
}
if (!isRoundActive) {
if (!IsRoundActive) {
panic("Round is not active but we are in endRound");
return;
}
@@ -642,7 +644,7 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
if (random != null && activeTrails.TryGetValue(random.Slot,
out var randomTrail)) {
msg.ImpossibleLocation(
Msg.ImpossibleLocation(
ctMade ? CsTeam.Terrorist : CsTeam.CounterTerrorist, random);
bestTrail?.Kill();
@@ -657,7 +659,7 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
announceTimes();
if (aliveCount <= CvMaxPlayersToFinish.Value) {
if (aliveCount <= CV_MAX_PLAYERS_TO_FINISH.Value) {
if (finishTimestampList.Count == 0) {
generics.Error("No slowest times found").ToAllChat();
return;
@@ -678,29 +680,30 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
.Where(p => p.Slot != winner.Slot)
.ToList();
var timeToSet = CvWinTimeBase.Value + CvWinTimeBonus.Value * losers.Count;
var timeToSet = CV_WIN_TIME_BASE.Value
+ CV_WIN_TIME_BONUS.Value * losers.Count;
msg.PlayerWon(winner).ToAllChat();
Msg.PlayerWon(winner).ToAllChat();
foreach (var loser in losers) {
loser.SetColor(Color.FromArgb(254, Color.White));
if (CvTeleportType.Value == 1)
if (CV_TELEPORT_TYPE.Value == 1)
loser.Teleport(winner);
else if (CvTeleportType.Value == 2) winner.Teleport(loser);
if (CvLoserDamageable.Value) EnableDamage(loser);
else if (CV_TELEPORT_TYPE.Value == 2) winner.Teleport(loser);
if (CV_LOSER_DAMAGEABLE.Value) EnableDamage(loser);
}
if (CvWinnerDamageable.Value) EnableDamage(winner);
if (CV_WINNER_DAMAGEABLE.Value) EnableDamage(winner);
foreach (var weapon in CvLosersWeapon.Value.Split(','))
foreach (var weapon in CV_LOSERS_WEAPONS.Value.Split(','))
foreach (var loser in losers)
loser.GiveNamedItem(weapon);
foreach (var weapon in CvWinWeapon.Value.Split(','))
foreach (var weapon in CV_WIN_WEAPONS.Value.Split(','))
winner.GiveNamedItem(weapon);
Plugin.RemoveListener<Listeners.OnTick>(checkFinishers);
RoundUtil.SetTimeRemaining(Math.Min(timeToSet, CvWinTimeMax.Value));
RoundUtil.SetTimeRemaining(Math.Min(timeToSet, CV_WIN_TIME_MAX.Value));
Server.ExecuteCommand("mp_ignore_round_win_conditions 0");
return;
}
@@ -709,7 +712,7 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
var nextRoundTime = (int)Math.Ceiling((bestTime ?? 20) + 10 - round * 1.5);
if (toEliminate <= 0) {
msg.NoneEliminated.ToAllChat();
Msg.NoneEliminated.ToAllChat();
Plugin.AddTimer(3f, () => { startRound(nextRoundTime); },
TimerFlags.STOP_ON_MAPCHANGE);
return;
@@ -750,7 +753,7 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
if (player == null || !player.IsValid) continue;
EnableDamage(player);
player.CommitSuicide(false, true);
msg.PlayerEliminated(player).ToAllChat();
Msg.PlayerEliminated(player).ToAllChat();
}
Plugin.AddTimer(3f, () => { startRound(nextRoundTime); },
@@ -760,7 +763,7 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
private void eliminatePlayer(CCSPlayerController player) {
EnableDamage(player);
player.CommitSuicide(false, true);
msg.PlayerEliminated(player).ToAllChat();
Msg.PlayerEliminated(player).ToAllChat();
}
private void panic(string reason) {
@@ -794,7 +797,7 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
var player = Utilities.GetPlayerFromSlot(slot);
slowest = slowest.Previous;
if (player == null) continue;
msg.PlayerTime(player, position--, dist).ToChat(player);
Msg.PlayerTime(player, position--, dist).ToChat(player);
}
}
@@ -825,7 +828,7 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
}
public override Func<int> RoundTime
=> () => CvInitialSpeedrunTime.Value + CvFirstRoundFreeze.Value;
=> () => CV_INITIAL_SPEEDRUN_TIME.Value + CV_FIRST_ROUND_FREEZE.Value;
public override ISet<string> AllowedWeapons(CCSPlayerController player) {
// Return empty set to allow no weapons
@@ -833,7 +836,7 @@ public class SpeedrunDay(BasePlugin plugin, IServiceProvider provider)
}
public override float FreezeTime(CCSPlayerController player) {
return CvFirstRoundFreeze.Value;
return CV_FIRST_ROUND_FREEZE.Value;
}
}
}

View File

@@ -10,12 +10,13 @@ using Jailbreak.Formatting.Views.Warden;
using Jailbreak.Public.Behaviors;
using Jailbreak.Public.Mod.Warden;
using Jailbreak.Public.Utils;
using Jailbreak.Warden.Global;
namespace Jailbreak.Warden.Commands;
public class WardenCommandsBehavior(IWardenLocale locale,
IWardenSelectionService queue, IWardenService warden,
IGenericCmdLocale generics, WardenConfig config) : IPluginBehavior {
IGenericCmdLocale generics) : IPluginBehavior {
private readonly Dictionary<CCSPlayerController, DateTime> lastPassCommand =
new();
@@ -39,7 +40,7 @@ public class WardenCommandsBehavior(IWardenLocale locale,
// GetPlayers() returns valid players, no need to error check here.
foreach (var clients in Utilities.GetPlayers())
clients.ExecuteClientCommand(
$"play sounds/{config.WardenPassedSoundName}");
$"play sounds/{WardenBehavior.CV_WARDEN_SOUND_PASSED.Value}");
locale.BecomeNextWarden.ToAllChat();
@@ -71,7 +72,7 @@ public class WardenCommandsBehavior(IWardenLocale locale,
locale.FireWarden(warden.Warden).ToChat(client);
client.ExecuteClientCommand(
$"play sounds/{config.WardenPassedSoundName}");
$"play sounds/{WardenBehavior.CV_WARDEN_SOUND_PASSED.Value}");
}
locale.BecomeNextWarden.ToAllChat();

View File

@@ -17,7 +17,7 @@ namespace Jailbreak.Warden.Commands;
public class WardenOpenCommandsBehavior(IWardenService warden,
IWardenLocale msg, IWardenCmdOpenLocale wardenCmdOpenMsg,
IZoneManager zoneManager) : IPluginBehavior, IWardenOpenCommand {
public static readonly FakeConVar<int> CvOpenCommandCooldown = new(
public static readonly FakeConVar<int> CV_OPEN_COMMAND_COOLDOWN = new(
"css_jb_warden_open_cooldown",
"Minimum seconds warden must wait before being able to open the cells.", 30,
customValidators: new RangeValidator<int>(0, 300));
@@ -40,8 +40,8 @@ public class WardenOpenCommandsBehavior(IWardenService warden,
return;
}
if (RoundUtil.GetTimeElapsed() < CvOpenCommandCooldown.Value) {
wardenCmdOpenMsg.CannotOpenYet(CvOpenCommandCooldown.Value)
if (RoundUtil.GetTimeElapsed() < CV_OPEN_COMMAND_COOLDOWN.Value) {
wardenCmdOpenMsg.CannotOpenYet(CV_OPEN_COMMAND_COOLDOWN.Value)
.ToChat(executor);
return;
}
@@ -56,9 +56,10 @@ public class WardenOpenCommandsBehavior(IWardenService warden,
var result = MapUtil.OpenCells(zoneManager);
IView message;
if (result) {
if (executor != null && !warden.IsWarden(executor)) {
if (executor != null && !warden.IsWarden(executor))
message = wardenCmdOpenMsg.CellsOpenedBy(executor);
} else { message = wardenCmdOpenMsg.CellsOpenedBy(null); }
else
message = wardenCmdOpenMsg.CellsOpenedBy(null);
} else { message = wardenCmdOpenMsg.OpeningFailed; }
message.ToAllChat();

View File

@@ -36,39 +36,54 @@ public struct PreWardenStats(int armorValue, int health, int maxHealth,
public class WardenBehavior(ILogger<WardenBehavior> logger,
IWardenLocale locale, IRichLogService logs,
ISpecialTreatmentService specialTreatment, IRebelService rebels,
WardenConfig config, IMuteService mute, IServiceProvider provider)
IMuteService mute, IServiceProvider provider)
: IPluginBehavior, IWardenService {
private readonly ISet<CCSPlayerController> bluePrisoners =
new HashSet<CCSPlayerController>();
public readonly FakeConVar<int> CvArmorEqual = new("css_jb_hp_outnumbered",
"Health points for CTs have equal balance", 50, ConVarFlags.FCVAR_NONE,
new RangeValidator<int>(1, 200));
public static readonly FakeConVar<int> CV_ARMOR_EQUAL =
new("css_jb_hp_outnumbered", "Health points for CTs have equal balance", 50,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(1, 200));
public readonly FakeConVar<int> CvArmorOutnumber = new("css_jb_hp_outnumber",
"HP for CTs when outnumbering Ts", 25, ConVarFlags.FCVAR_NONE,
new RangeValidator<int>(1, 200));
public static readonly FakeConVar<int> CV_ARMOR_OUTNUMBER =
new("css_jb_hp_outnumber", "HP for CTs when outnumbering Ts", 25,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(1, 200));
public readonly FakeConVar<int> CvArmorOutnumbered =
public static readonly FakeConVar<int> CV_ARMOR_OUTNUMBERED =
new("css_jb_hp_outnumbered", "Health points for CTs when outnumbered by Ts",
100, ConVarFlags.FCVAR_NONE, new RangeValidator<int>(1, 200));
public readonly FakeConVar<int> CvWardenArmor = new("css_jb_warden_armor",
"Armor for the warden", 125, ConVarFlags.FCVAR_NONE,
new RangeValidator<int>(1, 200));
public static readonly FakeConVar<int> CV_WARDEN_ARMOR =
new("css_jb_warden_armor", "Armor for the warden", 125,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(1, 200));
public readonly FakeConVar<int> CvWardenHealth = new("css_jb_warden_hp",
"HP for the warden", 125, ConVarFlags.FCVAR_NONE,
new RangeValidator<int>(1, 200));
public readonly FakeConVar<int> CvWardenMaxHealth = new("css_jb_warden_maxhp",
"Max HP for the warden", 100, ConVarFlags.FCVAR_NONE,
new RangeValidator<int>(1, 200));
public readonly FakeConVar<int> CvWardenAutoOpenCells =
public static readonly FakeConVar<int> CV_WARDEN_AUTO_OPEN_CELLS =
new("css_jb_warden_opencells_delay",
"Delay in seconds to auto-open cells at, -1 to disable", 60);
public static readonly FakeConVar<int> CV_WARDEN_HEALTH =
new("css_jb_warden_hp", "HP for the warden", 125, ConVarFlags.FCVAR_NONE,
new RangeValidator<int>(1, 200));
public static readonly FakeConVar<int> CV_WARDEN_MAX_HEALTH =
new("css_jb_warden_maxhp", "Max HP for the warden", 100,
ConVarFlags.FCVAR_NONE, new RangeValidator<int>(1, 200));
public static readonly FakeConVar<string> CV_WARDEN_SOUND_KILLED =
new("css_jb_warden_sound_killed", "Sound to play when the warden is killed",
"wardenKilled");
public static readonly FakeConVar<string> CV_WARDEN_SOUND_PASSED =
new("css_jb_warden_sound_killed", "Sound to play when the warden passes",
"wardenPassed");
public static readonly FakeConVar<string> CV_WARDEN_SOUND_NEW =
new("css_jb_warden_sound_killed",
"Sound to play when the warden is assigned", "wardenNew");
public static readonly FakeConVar<int> CV_WARDEN_TERRORIST_RATIO =
new("css_jb_warden_t_ratio", "Ratio of T:CT to use for HP adjustments", 3);
private bool firstWarden;
private string? oldTag;
private char? oldTagColor;
@@ -133,7 +148,7 @@ public class WardenBehavior(ILogger<WardenBehavior> logger,
}
foreach (var player in Utilities.GetPlayers())
player.ExecuteClientCommand($"play sounds/{config.WardenNewSoundName}");
player.ExecuteClientCommand($"play sounds/{CV_WARDEN_SOUND_NEW.Value}");
logs.Append(logs.Player(Warden), "is now the warden.");
@@ -157,10 +172,10 @@ public class WardenBehavior(ILogger<WardenBehavior> logger,
if (!hasHelmet) Warden.GiveNamedItem("item_assaultsuit");
var ctArmorValue = getBalance() switch {
0 => CvArmorEqual.Value, // Balanced teams
1 => CvArmorOutnumbered.Value, // Ts outnumber CTs
-1 => CvArmorOutnumber.Value, // CTs outnumber Ts
_ => CvArmorEqual.Value // default (should never happen)
0 => CV_ARMOR_EQUAL.Value, // Balanced teams
1 => CV_ARMOR_OUTNUMBERED.Value, // Ts outnumber CTs
-1 => CV_ARMOR_OUTNUMBER.Value, // CTs outnumber Ts
_ => CV_ARMOR_EQUAL.Value // default (should never happen)
};
/* Round start CT buff */
@@ -173,8 +188,8 @@ public class WardenBehavior(ILogger<WardenBehavior> logger,
Utilities.SetStateChanged(guardPawn, "CCSPlayerPawn", "m_ArmorValue");
}
setWardenStats(wardenPawn, CvWardenArmor.Value, CvWardenHealth.Value,
CvWardenMaxHealth.Value);
setWardenStats(wardenPawn, CV_WARDEN_ARMOR.Value, CV_WARDEN_HEALTH.Value,
CV_WARDEN_MAX_HEALTH.Value);
if (!hasHealthshot) Warden.GiveNamedItem("weapon_healthshot");
} else { preWardenStats = null; }
@@ -282,7 +297,7 @@ public class WardenBehavior(ILogger<WardenBehavior> logger,
foreach (var player in Utilities.GetPlayers()) {
if (!player.IsReal()) continue;
player.ExecuteClientCommand(
$"play sounds/{config.WardenKilledSoundName}");
$"play sounds/{CV_WARDEN_SOUND_KILLED.Value}");
}
locale.BecomeNextWarden.ToAllChat();
@@ -333,7 +348,7 @@ public class WardenBehavior(ILogger<WardenBehavior> logger,
var tCount = Utilities.GetPlayers().Count(p => p.Team == CsTeam.Terrorist);
// Casting to a float ensures if we're diving by zero, we get infinity instead of an error.
var ratio = (float)tCount / config.TerroristRatio - ctCount;
var ratio = (float)tCount / CV_WARDEN_TERRORIST_RATIO.Value - ctCount;
return ratio switch {
> 0 => 1,
@@ -403,14 +418,14 @@ public class WardenBehavior(ILogger<WardenBehavior> logger,
firstWarden = true;
preWardenStats = null;
if (CvWardenAutoOpenCells.Value < 0 || RoundUtil.IsWarmup())
if (CV_WARDEN_AUTO_OPEN_CELLS.Value < 0 || RoundUtil.IsWarmup())
return HookResult.Continue;
var openCmd = provider.GetService<IWardenOpenCommand>();
if (openCmd == null) return HookResult.Continue;
var cmdLocale = provider.GetRequiredService<IWardenCmdOpenLocale>();
openCellsTimer?.Kill();
openCellsTimer = parent.AddTimer(CvWardenAutoOpenCells.Value, () => {
openCellsTimer = parent.AddTimer(CV_WARDEN_AUTO_OPEN_CELLS.Value, () => {
if (openCmd.OpenedCells) return;
var zone = provider.GetService<IZoneManager>();
if (zone != null)
@@ -436,7 +451,7 @@ public class WardenBehavior(ILogger<WardenBehavior> logger,
foreach (var player in Utilities.GetPlayers())
player.ExecuteClientCommand(
$"play sounds/{config.WardenPassedSoundName}");
$"play sounds/{CV_WARDEN_SOUND_PASSED.Value}");
locale.BecomeNextWarden.ToAllChat();
return HookResult.Continue;

View File

@@ -13,13 +13,13 @@ using MStatsShared;
namespace Jailbreak.Warden.Markers;
public class WardenMarkerBehavior(IWardenService warden) : IPluginBehavior {
public readonly FakeConVar<float> CvMaxRadius = new(
public static readonly FakeConVar<float> CV_MAX_RADIUS = new(
"css_jb_warden_marker_max_radius", "Maximum radius for warden marker", 360);
public readonly FakeConVar<float> CvMinRadius = new(
public static readonly FakeConVar<float> CV_MIN_RADIUS = new(
"css_jb_warden_marker_min_radius", "Minimum radius for warden marker", 60);
public readonly FakeConVar<long> CvResizeTime = new(
public static readonly FakeConVar<long> CV_RESIZE_TIME = new(
"css_jb_warden_resize_time", "Milliseconds to wait for resizing marker",
800);
@@ -30,7 +30,7 @@ public class WardenMarkerBehavior(IWardenService warden) : IPluginBehavior {
private float radius;
public void Start(BasePlugin basePlugin) {
marker = new BeamCircle(basePlugin, new Vector(), CvMinRadius.Value,
marker = new BeamCircle(basePlugin, new Vector(), CV_MIN_RADIUS.Value,
(int)Math.PI * 15);
basePlugin.AddCommandListener("player_ping", CommandListener_PlayerPing);
}
@@ -46,9 +46,9 @@ public class WardenMarkerBehavior(IWardenService warden) : IPluginBehavior {
var distance = currentPos.Distance(vec);
var timeElapsed = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
- placementTime;
if (timeElapsed < CvResizeTime.Value) {
if (distance <= CvMaxRadius.Value * 1.3) {
distance = Math.Clamp(distance, CvMinRadius.Value, CvMaxRadius.Value);
if (timeElapsed < CV_RESIZE_TIME.Value) {
if (distance <= CV_MAX_RADIUS.Value * 1.3) {
distance = Math.Clamp(distance, CV_MIN_RADIUS.Value, CV_MAX_RADIUS.Value);
marker?.SetRadius(distance);
marker?.Update();
radius = distance;
@@ -60,7 +60,7 @@ public class WardenMarkerBehavior(IWardenService warden) : IPluginBehavior {
}
}
radius = CvMinRadius.Value;
radius = CV_MIN_RADIUS.Value;
currentPos = vec;
placementTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();

View File

@@ -1,8 +0,0 @@
namespace Jailbreak.Warden;
public class WardenConfig {
public string WardenKilledSoundName { get; } = "wardenKilled";
public string WardenPassedSoundName { get; } = "wardenPassed";
public string WardenNewSoundName { get; } = "wardenNew";
public int TerroristRatio { get; } = 3;
}

View File

@@ -13,13 +13,13 @@ namespace Jailbreak.Warden;
public static class WardenServiceExtension {
public static void AddJailbreakWarden(
this IServiceCollection serviceCollection) {
serviceCollection.AddConfig<WardenConfig>("warden");
serviceCollection.AddPluginBehavior<IWardenService, WardenBehavior>();
serviceCollection
.AddPluginBehavior<IWardenSelectionService, WardenSelectionBehavior>();
serviceCollection
.AddPluginBehavior<ISpecialTreatmentService, SpecialTreatmentBehavior>();
serviceCollection.AddPluginBehavior<IWardenOpenCommand, WardenOpenCommandsBehavior>();
serviceCollection
.AddPluginBehavior<IWardenOpenCommand, WardenOpenCommandsBehavior>();
serviceCollection.AddPluginBehavior<SpecialTreatmentCommandsBehavior>();
serviceCollection.AddPluginBehavior<PeaceCommandsBehavior>();

View File

@@ -8,10 +8,10 @@ using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
namespace Jailbreak.Zones;
public class SqlZoneManager(IZoneFactory factory) : IZoneManager {
public static readonly FakeConVar<string> CvSqlTable = new("css_jb_zonetable",
public static readonly FakeConVar<string> CV_SQL_TABLE = new("css_jb_zonetable",
"The table name for the zones", "cs2_jb_zones");
public static readonly FakeConVar<string> CvSqlConnectionString =
public static readonly FakeConVar<string> CV_SQL_CONNECTION_STRING =
new("css_jb_sqlconnection", "The connection string for the database", "",
ConVarFlags.FCVAR_PROTECTED);
@@ -22,7 +22,7 @@ public class SqlZoneManager(IZoneFactory factory) : IZoneManager {
public void Start(BasePlugin basePlugin) {
plugin = basePlugin;
CvSqlConnectionString.ValueChanged += async (_, _) => {
CV_SQL_CONNECTION_STRING.ValueChanged += async (_, _) => {
await LoadZones(Server.MapName);
};
@@ -56,7 +56,7 @@ public class SqlZoneManager(IZoneFactory factory) : IZoneManager {
await conn.OpenAsync();
await using var cmd = conn.CreateCommand();
cmd.CommandText = $"""
DELETE FROM {CvSqlTable.Value.Trim('"')}
DELETE FROM {CV_SQL_TABLE.Value.Trim('"')}
WHERE zoneid = @zoneid
AND map = @map
""";
@@ -91,7 +91,7 @@ public class SqlZoneManager(IZoneFactory factory) : IZoneManager {
await conn.OpenAsync();
var insertPointCommand = $"""
INSERT INTO {CvSqlTable.Value.Trim('"')} (map, type, zoneid, pointid, X, Y, Z)
INSERT INTO {CV_SQL_TABLE.Value.Trim('"')} (map, type, zoneid, pointid, X, Y, Z)
VALUES (@map, @type, @zoneid, @pointid, @X, @Y, @Z)
""";
var pointId = 0;
@@ -160,7 +160,7 @@ public class SqlZoneManager(IZoneFactory factory) : IZoneManager {
await conn.OpenAsync();
var cmdText = $"""
CREATE TABLE IF NOT EXISTS {CvSqlTable.Value.Trim('"')}(
CREATE TABLE IF NOT EXISTS {CV_SQL_TABLE.Value.Trim('"')}(
zoneid INT NOT NULL,
pointid INT NOT NULL,
map VARCHAR(64) NOT NULL,
@@ -188,7 +188,7 @@ public class SqlZoneManager(IZoneFactory factory) : IZoneManager {
private MySqlCommand queryAllZones(MySqlConnection conn, string map,
ZoneType type) {
var cmdText = $"""
SELECT * FROM {CvSqlTable.Value.Trim('"')}
SELECT * FROM {CV_SQL_TABLE.Value.Trim('"')}
WHERE map = '{map}' AND type = '{type}'
ORDER BY zoneid, pointid DESC
""";
@@ -260,7 +260,7 @@ public class SqlZoneManager(IZoneFactory factory) : IZoneManager {
}
private MySqlConnection? createConnection() {
var str = CvSqlConnectionString.Value.Trim('"');
var str = CV_SQL_CONNECTION_STRING.Value.Trim('"');
return string.IsNullOrWhiteSpace(str) ? null : new MySqlConnection(str);
}
}

View File

@@ -5,19 +5,20 @@ namespace Jailbreak.Formatting.Views.Warden;
public interface IWardenCmdOpenLocale {
/// <summary>
/// The cells were auto-opened.
/// The cells were auto-opened.
/// </summary>
public IView CellsOpened { get; }
public IView OpeningFailed { get; }
public IView AlreadyOpened { get; }
/// <summary>
/// The cells were opened by the specified player.
/// If the player is null, the cells were opened by the warden.
/// The cells were opened by the specified player.
/// If the player is null, the cells were opened by the warden.
/// </summary>
/// <param name="player"></param>
/// <returns></returns>
public IView CellsOpenedBy(CCSPlayerController? player);
public IView OpeningFailed { get; }
public IView AlreadyOpened { get; }
public IView CannotOpenYet(int seconds);
}

View File

@@ -1,14 +0,0 @@
namespace Jailbreak.Public.Configuration;
public interface IConfigService {
public const string CONFIG_PATH = "jailbreak.json";
/// <summary>
/// Get the configuration object with the provided name
/// </summary>
/// <param name="path"></param>
/// <param name="failOnDefault">If the configuration service would return the default value, fail instead. Loudly.</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
T Get<T>(string path, bool failOnDefault = false) where T : class, new();
}

View File

@@ -1,13 +1,13 @@
namespace Jailbreak.Public.Extensions;
public static class CollectionExtensions {
private static readonly Random Random = new();
private static readonly Random RANDOM = new();
public static void Shuffle<T>(this IList<T> list) {
var n = list.Count;
while (n > 1) {
n--;
var k = Random.Next(n + 1);
var k = RANDOM.Next(n + 1);
(list[k], list[n]) = (list[n], list[k]);
}
}

View File

@@ -1,5 +1,4 @@
using Jailbreak.Public.Behaviors;
using Jailbreak.Public.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Jailbreak.Public.Extensions;
@@ -44,24 +43,4 @@ public static class ServiceCollectionExtensions {
collection.AddTransient<IPluginBehavior, TExtension>(provider
=> provider.GetRequiredService<TExtension>());
}
/// <summary>
/// Add an object to be loaded from the configuration file
/// </summary>
/// <param name="collection"></param>
/// <param name="sectionName">The section where the configuration object will be loaded from</param>
/// <typeparam name="TConfig">The configuration object. Must auto-fill all default values!</typeparam>
[Obsolete(
"Conguration is up to each module, and should ideally be done through CVars")]
public static void AddConfig<TConfig>(this IServiceCollection collection,
string sectionName) where TConfig : class, new() {
// Get the object by resolving IConfigService
// and use the Get<T>() method.
// Not *really* important... but do we want to fail here or return default if section
// isn't available?
collection.AddTransient<TConfig>(provider => provider
.GetRequiredService<IConfigService>()
.Get<TConfig>(sectionName));
}
}

View File

@@ -48,6 +48,7 @@ public abstract class AbstractSpecialDay(BasePlugin plugin,
/// know why you are not calling it.
/// </summary>
public virtual void Setup() {
Plugin.RegisterFakeConVars(this);
Plugin.RegisterEventHandler<EventRoundEnd>(OnEnd);
foreach (var entry in Settings.ConVarValues) {

View File

@@ -0,0 +1,19 @@
using CounterStrikeSharp.API.Modules.Utils;
namespace Jailbreak.Public.Utils;
public static class TeamUtil {
public static CsTeam? FromString(string team) {
return team.ToLower() switch {
"0" or "n" or "none" => CsTeam.None,
"1" or "s" or "spec" or "spectator" or "spectators" or "specs" => CsTeam
.Spectator,
"2" or "t" or "ts" or "terror" or "terrorist" or "terrorists"
or "prisoner" or "prisoners" => CsTeam.Terrorist,
"3" or "ct" or "cts" or "counter" or "counterterrorist"
or "counterterrorists" or "guard"
or "guards" => CsTeam.CounterTerrorist,
_ => null
};
}
}

View File

@@ -0,0 +1,70 @@
using CounterStrikeSharp.API.Modules.Cvars.Validators;
namespace Jailbreak.Validator;
public class ItemValidator(
ItemValidator.WeaponType type = ItemValidator.WeaponType.WEAPON
| ItemValidator.WeaponType.UTILITY, bool allowEmpty = true,
bool allowMultiple = false) : IValidator<string> {
[Flags]
public enum WeaponType {
GRENADE,
UTILITY,
WEAPON,
SNIPERS,
RIFLES,
PISTOLS,
SHOTGUNS,
SMGS,
HEAVY,
GUNS
}
public bool Validate(string value, out string? errorMessage) {
if (value.Contains(',') && !allowMultiple) {
errorMessage = "Value cannot contain multiple values";
return false;
}
if (string.IsNullOrWhiteSpace(value)) {
errorMessage = allowEmpty ? null : "weapon cannot be empty";
return allowEmpty;
}
foreach (var weapon in value.Split(',')) {
if (string.IsNullOrWhiteSpace(weapon)) {
if (!allowEmpty) {
errorMessage = allowEmpty ? null : "weapon cannot be empty";
return allowEmpty;
}
continue;
}
errorMessage = $"invalid {nameof(type).ToLower()}: {weapon}";
return Enum.GetValues<WeaponType>()
.Where(t => (t & type) == type)
.Any(t => validateType(t, weapon));
}
errorMessage = null;
return true;
}
private bool validateType(WeaponType current, string weapon) {
var result = current switch {
WeaponType.GRENADE => Tag.GRENADES.Contains(weapon),
WeaponType.UTILITY => Tag.UTILITY.Contains(weapon),
WeaponType.WEAPON => Tag.WEAPONS.Contains(weapon),
WeaponType.SNIPERS => Tag.SNIPERS.Contains(weapon),
WeaponType.RIFLES => Tag.RIFLES.Contains(weapon),
WeaponType.PISTOLS => Tag.PISTOLS.Contains(weapon),
WeaponType.SHOTGUNS => Tag.SHOTGUNS.Contains(weapon),
WeaponType.SMGS => Tag.SMGS.Contains(weapon),
WeaponType.HEAVY => Tag.HEAVY.Contains(weapon),
WeaponType.GUNS => Tag.GUNS.Contains(weapon),
_ => throw new ArgumentOutOfRangeException(nameof(weapon))
};
return result;
}
}

View File

@@ -0,0 +1,21 @@
using CounterStrikeSharp.API.Modules.Cvars.Validators;
namespace Jailbreak.Validator;
public class NonZeroRangeValidator<T>(T min, T max)
: IValidator<T> where T : IComparable<T> {
public bool Validate(T value, out string? errorMessage) {
if (value.Equals(default(T))) {
errorMessage = $"Value must be non-zero between {min} and {max}";
return false;
}
if (value.CompareTo(min) >= 0 && value.CompareTo(max) <= 0) {
errorMessage = null;
return true;
}
errorMessage = $"Value must be between {min} and {max}";
return false;
}
}

View File

@@ -0,0 +1,25 @@
using CounterStrikeSharp.API.Modules.Cvars.Validators;
using CounterStrikeSharp.API.Modules.Utils;
using Jailbreak.Public.Utils;
namespace Jailbreak.Validator;
public class TeamValidator(bool allowSpecs = true) : IValidator<string> {
public bool Validate(string value, out string? errorMessage) {
errorMessage = null;
var team = TeamUtil.FromString(value);
if (team == null) {
errorMessage = $"Unknown team: \"{value}\"";
return false;
}
if (team is CsTeam.None or CsTeam.Spectator && !allowSpecs) {
errorMessage = "Team must be CT or T";
return false;
}
return true;
}
}

View File

@@ -1,56 +0,0 @@
using CounterStrikeSharp.API.Modules.Cvars.Validators;
namespace Jailbreak.Validator;
public class WeaponValidator(
WeaponValidator.WeaponType type = WeaponValidator.WeaponType.WEAPON,
bool allowEmpty = true, bool allowMultiple = false) : IValidator<string> {
public enum WeaponType {
GRENADE,
UTILITY,
WEAPON,
SNIPERS,
RIFLES,
PISTOLS,
SHOTGUNS,
SMGS,
HEAVY
}
public bool Validate(string value, out string? errorMessage) {
if (value.Contains(',') && !allowMultiple) {
errorMessage = "Value cannot contain multiple values";
return false;
}
if (string.IsNullOrWhiteSpace(value)) {
errorMessage = allowEmpty ? null : "weapon cannot be empty";
return allowEmpty;
}
foreach (var weapon in value.Split(',')) {
if (string.IsNullOrWhiteSpace(weapon)) {
errorMessage = allowEmpty ? null : "weapon cannot be empty";
return allowEmpty;
}
errorMessage = $"invalid {nameof(type).ToLower()}: {weapon}";
var result = type switch {
WeaponType.GRENADE => Tag.GRENADES.Contains(weapon),
WeaponType.UTILITY => Tag.UTILITY.Contains(weapon),
WeaponType.WEAPON => Tag.WEAPONS.Contains(weapon),
WeaponType.SNIPERS => Tag.SNIPERS.Contains(weapon),
WeaponType.RIFLES => Tag.RIFLES.Contains(weapon),
WeaponType.PISTOLS => Tag.PISTOLS.Contains(weapon),
WeaponType.SHOTGUNS => Tag.SHOTGUNS.Contains(weapon),
WeaponType.SMGS => Tag.SMGS.Contains(weapon),
WeaponType.HEAVY => Tag.HEAVY.Contains(weapon),
_ => throw new ArgumentOutOfRangeException(nameof(weapon))
};
if (!result) return false;
}
errorMessage = null;
return true;
}
}

View File

@@ -1,63 +0,0 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using CounterStrikeSharp.API;
using Jailbreak.Public.Configuration;
using Microsoft.Extensions.Logging;
namespace Jailbreak.Config;
/// <summary>
/// A service to load and parse configuration files.
/// </summary>
public class ConfigService : IConfigService {
private readonly ILogger<ConfigService> logger;
/// <summary>
/// Constructor
/// </summary>
/// <param name="logger"></param>
public ConfigService(ILogger<ConfigService> logger) { this.logger = logger; }
/// <summary>
/// </summary>
/// <param name="path"></param>
/// <param name="fail"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T Get<T>(string path, bool fail = false) where T : class, new() {
var jsonPath =
Path.Combine(Server.GameDirectory, IConfigService.CONFIG_PATH);
if (!File.Exists(jsonPath))
return fail<T>(fail, "Config file does not exist");
var jsonText = File.ReadAllText(jsonPath);
var jsonObject = JsonNode.Parse(jsonText);
if (jsonObject == null)
return fail<T>(fail, $"Unable to parse configuration file at {jsonPath}");
var configObject = jsonObject[path];
if (configObject == null)
return fail<T>(fail, $"Unable to navigate to config section {path}");
var config = configObject.Deserialize<T>();
if (config == null)
return fail<T>(fail,
$"Unable to deserialize ({configObject.ToJsonString()}) into {typeof(T).FullName}.");
return config;
}
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
private T fail<T>(bool fail, string message) where T : class, new() {
// We would be returning default.
// Check if caller wants us to cry and scream instead.
if (fail) throw new InvalidOperationException(message);
logger.LogWarning(
"[Config] Tripped load fail state with message: {@Message}", message);
return new T();
}
}

View File

@@ -1,5 +1,4 @@
using CounterStrikeSharp.API.Core;
using Jailbreak.Config;
using Jailbreak.Debug;
using Jailbreak.English.Generic;
using Jailbreak.English.LastGuard;
@@ -18,7 +17,6 @@ using Jailbreak.LastGuard;
using Jailbreak.LastRequest;
using Jailbreak.Logs;
using Jailbreak.Mute;
using Jailbreak.Public.Configuration;
using Jailbreak.Rebel;
using Jailbreak.SpecialDay;
using Jailbreak.Warden;
@@ -52,7 +50,6 @@ public class JailbreakServiceCollection : IPluginServiceCollection<Jailbreak> {
// Do we want to make this scoped?
// Not sure how this will behave with multiple rounds and whatnot.
serviceCollection.AddTransient<IConfigService, ConfigService>();
serviceCollection.AddJailbreakGeneric();
serviceCollection.AddJailbreakLogs();
serviceCollection.AddJailbreakRebel();