diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs index 024edaf26d9..847903be067 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs @@ -57,7 +57,7 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new("/Textures/Backmen/Interface/Actions/blob.rsi"), "blobFactory"), Act = () => { - EnsureComp(args.Target).HasMind = targetMindComp.HasMind; + EnsureComp(args.Target).HasMind = HasComp(args.Target); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-text-make-blob"), @@ -71,11 +71,11 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new("/Textures/Structures/flesh_heart.rsi"), "base_heart"), Act = () => { - if (!_minds.TryGetSession(targetMindComp.Mind, out var session)) + if (!TryComp(args.Target, out var actor)) return; EntityManager.System() - .MakeCultist(session); + .MakeCultist(actor.PlayerSession); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-text-make-flesh-leader-cultist"), @@ -89,11 +89,11 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new("/Textures/Mobs/Aliens/FleshCult/flesh_cult_mobs.rsi"), "worm"), Act = () => { - if (!_minds.TryGetSession(targetMindComp.Mind, out var session)) + if (!TryComp(args.Target, out var actor)) return; EntityManager.System() - .MakeCultist(session); + .MakeCultist(actor.PlayerSession); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-text-make-flesh-cultist"), @@ -108,11 +108,8 @@ private void AddAntagVerbs(GetVerbsEvent args) "poster3_legit"), Act = () => { - if (!_minds.TryGetSession(targetMindComp.Mind, out var session)) - return; - EntityManager.System() - .MakeTwin(out _, session.AttachedEntity); + .MakeTwin(out _, args.Target); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-make-eviltwin"), diff --git a/Content.Server/Backmen/Blob/Rule/BlobGameRuleComponent.cs b/Content.Server/Backmen/Blob/Rule/BlobGameRuleComponent.cs index dea7e5762fd..ee27da052be 100644 --- a/Content.Server/Backmen/Blob/Rule/BlobGameRuleComponent.cs +++ b/Content.Server/Backmen/Blob/Rule/BlobGameRuleComponent.cs @@ -1,7 +1,12 @@ -namespace Content.Server.Backmen.Blob.Rule; +using Robust.Shared.Audio; + +namespace Content.Server.Backmen.Blob.Rule; [RegisterComponent] public sealed partial class BlobGameRuleComponent : Component { public int TotalBlobs = 0; + + [DataField] + public SoundSpecifier InitialInfectedSound = new SoundPathSpecifier("/Audio/Backmen/Ambience/Antag/blob_start.ogg"); } diff --git a/Content.Server/Backmen/Blob/Rule/BlobRuleSystem.cs b/Content.Server/Backmen/Blob/Rule/BlobRuleSystem.cs index 794fcb4537a..0aed34f6315 100644 --- a/Content.Server/Backmen/Blob/Rule/BlobRuleSystem.cs +++ b/Content.Server/Backmen/Blob/Rule/BlobRuleSystem.cs @@ -7,10 +7,14 @@ using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules.Components; using Content.Server.Mind; +using Content.Shared.Actions; +using Content.Shared.Antag; using Content.Shared.Backmen.Blob; using Content.Shared.Backmen.CCVar; +using Content.Shared.Humanoid; using Content.Shared.Preferences; using Content.Shared.Roles; +using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Player; using Robust.Shared.Prototypes; @@ -31,6 +35,8 @@ public sealed class BlobGameRuleSystem : GameRuleSystem [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly AntagSelectionSystem _antagSelection = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; public override void Initialize() @@ -46,25 +52,16 @@ public override void Initialize() private void HandleLatejoin(PlayerSpawnCompleteEvent ev) { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var blob, out var gameRule)) + var query = QueryActiveRules(); + while (query.MoveNext(out _, out var blob, out var gameRule)) { - if (!GameTicker.IsGameRuleAdded(uid, gameRule)) - continue; - if (blob.TotalBlobs >= MaxBlob) continue; if (!ev.LateJoin) continue; - if (!ev.Profile.AntagPreferences.Contains(Blob)) - continue; - - if (ev.JobId == null || !_prototypeManager.TryIndex(ev.JobId, out var job)) - continue; - - if (!job.CanBeAntag) + if (!_antagSelection.IsPlayerEligible(ev.Player, Blob, acceptableAntags: AntagAcceptability.NotExclusive, allowNonHumanoids: false)) continue; // the nth player we adjust our probabilities around @@ -89,54 +86,52 @@ private void HandleLatejoin(PlayerSpawnCompleteEvent ev) // You get one shot. if (_random.Prob(chance) && ev.Player.AttachedEntity.HasValue) { - MakeBlob(blob, ev.Player); + MakeBlob(blob, ev.Player.AttachedEntity.Value); + _antagSelection.SendBriefing(ev.Player, Loc.GetString("blob-carrier-role-greeting"), Color.Plum, blob.InitialInfectedSound); } } } - private void MakeBlob(BlobGameRuleComponent blob, ICommonSession player) + private void MakeBlob(BlobGameRuleComponent blob, EntityUid player) { - if (!player.AttachedEntity.HasValue) - return; - blob.TotalBlobs++; - EnsureComp(player.AttachedEntity.Value).HasMind = true; + var comp = EnsureComp(player); + comp.HasMind = HasComp(player); + comp.TransformationDelay = 10 * 60; // 10min + _actions.SetCooldown(comp.TransformToBlob, TimeSpan.FromMinutes(5)); } private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev) { - var query = EntityQueryEnumerator(); + var query = QueryActiveRules(); while (query.MoveNext(out var uid, out var blob, out var gameRule)) { - var plr = new Dictionary(); + var eligiblePlayers = _antagSelection.GetEligiblePlayers( + ev.Players, Blob, + acceptableAntags: AntagAcceptability.None, + allowNonHumanoids: false, includeAllJobs: false); - if (!GameTicker.IsGameRuleAdded(uid, gameRule)) + if (eligiblePlayers.Count == 0) + { + //Log.Warning($"No eligible thieves found, ending game rule {ToPrettyString(uid):rule}"); + //GameTicker.EndGameRule(uid, gameRule); continue; + } - foreach (var player in ev.Players) - { - if (!ev.Profiles.ContainsKey(player.UserId)) - continue; + var initialInfectedCount = _antagSelection.CalculateAntagCount(_playerManager.PlayerCount, PlayersPerBlob, MaxBlob); - plr.Add(player, ev.Profiles[player.UserId]); - } + var blobs = _antagSelection.ChooseAntags(initialInfectedCount, eligiblePlayers); + + DoBlobStart(blobs, blob); - DoBlobStart(blob, plr); + _antagSelection.SendBriefing(blobs, Loc.GetString("blob-carrier-role-greeting"), Color.Plum, blob.InitialInfectedSound); } } - private void DoBlobStart(BlobGameRuleComponent blob, - Dictionary startCandidates) + private void DoBlobStart(List selectedTraitors, BlobGameRuleComponent blob) { - var numTraitors = MathHelper.Clamp(startCandidates.Count / PlayersPerBlob, 1, MaxBlob); - var traitorPool = _antagSelection.FindPotentialAntags(startCandidates, Blob); - var selectedTraitors = _antagSelection.PickAntag(numTraitors, traitorPool); - foreach (var traitor in selectedTraitors) { - if (!traitor.AttachedEntity.HasValue) - continue; - MakeBlob(blob, traitor); } } diff --git a/Content.Server/Backmen/Vampiric/Rule/BloodsuckerRuleComponent.cs b/Content.Server/Backmen/Vampiric/Rule/BloodsuckerRuleComponent.cs index 14212ef3fba..d8cb24bbc05 100644 --- a/Content.Server/Backmen/Vampiric/Rule/BloodsuckerRuleComponent.cs +++ b/Content.Server/Backmen/Vampiric/Rule/BloodsuckerRuleComponent.cs @@ -1,4 +1,6 @@ -namespace Content.Server.Backmen.Vampiric; +using Robust.Shared.Audio; + +namespace Content.Server.Backmen.Vampiric; [RegisterComponent] public sealed partial class BloodsuckerRuleComponent : Component @@ -16,4 +18,7 @@ public sealed partial class BloodsuckerRuleComponent : Component "Vox", "HumanoidFoxes", }; + + [DataField] + public SoundSpecifier InitialInfectedSound = new SoundPathSpecifier("/Audio/Backmen/Ambience/Antag/vampier_start.ogg"); } diff --git a/Content.Server/Backmen/Vampiric/Rule/BloodsuckerRuleSystem.cs b/Content.Server/Backmen/Vampiric/Rule/BloodsuckerRuleSystem.cs index 672fe8c50da..077fdbce100 100644 --- a/Content.Server/Backmen/Vampiric/Rule/BloodsuckerRuleSystem.cs +++ b/Content.Server/Backmen/Vampiric/Rule/BloodsuckerRuleSystem.cs @@ -8,12 +8,16 @@ using Content.Server.GameTicking.Rules.Components; using Content.Server.Mind; using Content.Server.Objectives; +using Content.Shared.Antag; using Content.Shared.Backmen.CCVar; using Content.Shared.Backmen.Vampiric; +using Content.Shared.Humanoid; using Content.Shared.Mind; using Content.Shared.Mobs.Components; using Content.Shared.Preferences; using Content.Shared.Roles; +using Robust.Server.Placement; +using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Player; using Robust.Shared.Prototypes; @@ -38,6 +42,7 @@ public sealed class BloodsuckerRuleSystem : GameRuleSystem(); - while (query.MoveNext(out var uid, out var vpmRule, out var gameRule)) + var query = QueryActiveRules(); + while (query.MoveNext(out _, out var vpmRule, out _)) { - if (!GameTicker.IsGameRuleAdded(uid, gameRule)) - continue; - if (vpmRule.TotalBloodsuckers >= MaxBloodsuckers) continue; if (!ev.LateJoin) continue; - if (!ev.Profile.AntagPreferences.Contains(Bloodsucker)) - continue; - - if (ev.JobId == null || !_prototypeManager.TryIndex(ev.JobId, out var job)) - continue; - - if (!job.CanBeAntag) - continue; - - if(!vpmRule.SpeciesWhitelist.Contains(ev.Profile.Species)) + var whitelistSpecies = vpmRule.SpeciesWhitelist; + if (!_antagSelection.IsPlayerEligible(ev.Player, Bloodsucker, acceptableAntags: AntagAcceptability.NotExclusive, + allowNonHumanoids: false, + customExcludeCondition: ent => + { + if (HasComp(ent)) + { + return true; + } + + return TryComp(ent, out var humanoidAppearanceComponent) && + !whitelistSpecies.Contains(humanoidAppearanceComponent.Species.Id); + })) continue; // the nth player we adjust our probabilities around @@ -115,65 +120,71 @@ private void HandleLatejoin(PlayerSpawnCompleteEvent ev) // You get one shot. if (_random.Prob(chance) && ev.Player.AttachedEntity.HasValue) { - if(HasComp(ev.Player.AttachedEntity)) - continue; _bloodSuckerSystem.ConvertToVampire(ev.Player.AttachedEntity.Value); vpmRule.TotalBloodsuckers++; if (_mindSystem.TryGetMind(ev.Player, out var mindId, out _)) { vpmRule.Elders.Add(MetaData(ev.Player.AttachedEntity.Value).EntityName,mindId); } + _antagSelection.SendBriefing(ev.Player, Loc.GetString("vampire-role-greeting"), Color.Plum, vpmRule.InitialInfectedSound); } } } private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev) { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var vpmRule, out var gameRule)) + var query = QueryActiveRules(); + while (query.MoveNext(out var uid, out _, out var comp, out var gameRule)) { - var plr = new Dictionary(); + //Get all players eligible for this role, allow selecting existing antags + //TO DO: When voxes specifies are added, increase their chance of becoming a thief by 4 times >:) + var whitelistSpecies = comp.SpeciesWhitelist; + var eligiblePlayers = _antagSelection.GetEligiblePlayers( + ev.Players, Bloodsucker, + acceptableAntags: AntagAcceptability.NotExclusive, + allowNonHumanoids: false, + customExcludeCondition: ent => + { + if (HasComp(ent)) + { + return true; + } - if (!GameTicker.IsGameRuleAdded(uid, gameRule)) - continue; + return TryComp(ent, out var humanoidAppearanceComponent) && + !whitelistSpecies.Contains(humanoidAppearanceComponent.Species.Id); + }); - foreach (var player in ev.Players) + //Abort if there are none + if (eligiblePlayers.Count == 0) { - if (!ev.Profiles.ContainsKey(player.UserId)) - continue; + //Log.Warning($"No eligible thieves found, ending game rule {ToPrettyString(uid):rule}"); + //GameTicker.EndGameRule(uid, gameRule); + continue; + } - if(!vpmRule.SpeciesWhitelist.Contains(ev.Profiles[player.UserId].Species)) - continue; + var initialInfectedCount = _antagSelection.CalculateAntagCount(_playerManager.PlayerCount, PlayersPerBloodsucker, MaxBloodsuckers); - if (player.AttachedEntity.HasValue && HasComp(player.AttachedEntity)) - continue; + //Select our theives + var thieves = _antagSelection.ChooseAntags(initialInfectedCount, eligiblePlayers); - plr.Add(player, ev.Profiles[player.UserId]); - } + DoVampirStart(thieves, comp); - DoVampirStart(vpmRule, plr); + _antagSelection.SendBriefing(thieves, Loc.GetString("vampire-role-greeting"), Color.Plum, comp.InitialInfectedSound); } } [ValidatePrototypeId] private const string Bloodsucker = "Bloodsucker"; - private void DoVampirStart(BloodsuckerRuleComponent vpmRule, Dictionary startCandidates) + private void DoVampirStart(List startCandidates, BloodsuckerRuleComponent vpmRule) { - var numTraitors = MathHelper.Clamp(startCandidates.Count / PlayersPerBloodsucker, 1, MaxBloodsuckers); - var traitorPool = _antagSelection.FindPotentialAntags(startCandidates, Bloodsucker); - var selectedTraitors = _antagSelection.PickAntag(numTraitors, traitorPool); - - foreach (var traitor in selectedTraitors) + foreach (var traitor in startCandidates) { - if (!traitor.AttachedEntity.HasValue) - continue; - - _bloodSuckerSystem.ConvertToVampire(traitor.AttachedEntity.Value); + _bloodSuckerSystem.ConvertToVampire(traitor); vpmRule.TotalBloodsuckers++; if (_mindSystem.TryGetMind(traitor, out var mindId, out _)) { - vpmRule.Elders.Add(MetaData(traitor.AttachedEntity.Value).EntityName,mindId); + vpmRule.Elders.Add(MetaData(traitor).EntityName,mindId); } } } diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index 77f94f755a1..58046fac035 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -76,6 +76,8 @@ public sealed class NukeopsRuleSystem : GameRuleSystem private ISawmill _sawmill = default!; + [Dependency] private readonly Backmen.Arrivals.CentcommSystem _centcommSystem = default!; // backmen: centcom + [ValidatePrototypeId] private const string TelecrystalCurrencyPrototype = "Telecrystal"; diff --git a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs index aa8ada95a78..cc628b34263 100644 --- a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs @@ -217,6 +217,7 @@ private List GetHealthyHumans(bool includeOffStation = true) var players = AllEntityQuery(); var zombers = GetEntityQuery(); + var centcom = GetEntityQuery(); // backmen: centcom while (players.MoveNext(out var uid, out _, out _, out var mob, out var xform)) { if (!_mobState.IsAlive(uid, mob)) @@ -225,6 +226,11 @@ private List GetHealthyHumans(bool includeOffStation = true) if (zombers.HasComponent(uid)) continue; + // start-backmen: centcom + if (centcom.HasComponent(uid)) + continue; + // end-backmen: centcom + if (!includeOffStation && !stationGrids.Contains(xform.GridUid ?? EntityUid.Invalid)) continue; diff --git a/Resources/Audio/Backmen/Ambience/Antag/attributions.yml b/Resources/Audio/Backmen/Ambience/Antag/attributions.yml new file mode 100644 index 00000000000..4f1afbe7fcb --- /dev/null +++ b/Resources/Audio/Backmen/Ambience/Antag/attributions.yml @@ -0,0 +1,4 @@ +- files: ["vampier_start.ogg", "blob_start.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from TG bot silero" + source: "https://t.me/silero_voice_bot" diff --git a/Resources/Audio/Backmen/Ambience/Antag/blob_start.ogg b/Resources/Audio/Backmen/Ambience/Antag/blob_start.ogg new file mode 100644 index 00000000000..1b7f6b7f101 Binary files /dev/null and b/Resources/Audio/Backmen/Ambience/Antag/blob_start.ogg differ diff --git a/Resources/Audio/Backmen/Ambience/Antag/vampier_start.ogg b/Resources/Audio/Backmen/Ambience/Antag/vampier_start.ogg new file mode 100644 index 00000000000..defc5d2dc3a Binary files /dev/null and b/Resources/Audio/Backmen/Ambience/Antag/vampier_start.ogg differ diff --git a/Resources/Locale/ru-RU/backmen/abilities/vampire.ftl b/Resources/Locale/ru-RU/backmen/abilities/vampire.ftl index 53196b3d594..9ca6cc554f1 100644 --- a/Resources/Locale/ru-RU/backmen/abilities/vampire.ftl +++ b/Resources/Locale/ru-RU/backmen/abilities/vampire.ftl @@ -21,3 +21,8 @@ endgame-vamp-conv = обратил { $count } { $count -> *[other] членов } экипажа endgame-vamp-drink = выпил крови { $count } из { $goal } + +vampire-role-greeting = + Я — древний вампир из могущественного Совета Вампиров! + Кровь это жизнь! делится вечной жизнью без вечного должника ничто! Надо найти послушников которые возможно когда-то станут вампирами (что должно быть исключением). + Захватите станцию и нарастите свои силы. Удачи! diff --git a/Resources/Locale/ru-RU/backmen/blob/blob.ftl b/Resources/Locale/ru-RU/backmen/blob/blob.ftl index 29ce21def6e..0a5c4356670 100644 --- a/Resources/Locale/ru-RU/backmen/blob/blob.ftl +++ b/Resources/Locale/ru-RU/backmen/blob/blob.ftl @@ -88,6 +88,8 @@ blob-carrier-role-desc = Сущность зараженная "блобом". blob-carrier-role-rules = Вы антагонист. У вас есть 4 минуты перед тем как вы превратитесь в блоба. Найдите за это время укромное место для стартовой точки заражения станции, ведь вы очень слабы в первые минуты после создания ядра. +blob-carrier-role-greeting = Вы носитель Блоба. Найдите укромное место на станции и превратитесь в Блоба. Превратите станцию в массу, а ее обитателей в ваших слуг. Все мы Блоб. + # Verbs blob-pod-verb-zombify = Зомбировать blob-verb-upgrade-to-strong = Улучшить до сильного блоба