fish-station/Content.Server/_Sunrise/BloodCult/GameRule/BloodCultRuleSystem.cs
A-Mironov abf39617af
Обнова культа крови (#128)
* stable

* sprite

* yes!

* LOCATOR TRANSLATION MISSING

* finally

* stable2

* stable

* stable?

* FIS

* Nerfs

* works

* Трек болгарыча и время призыва теперь 3 минуты

* f

* Update modifier_sets.yml

* Барьер больше не даёт 5 рун металла, ибо почему он должен?
2025-11-30 00:06:35 +02:00

564 lines
20 KiB
C#

using System.Linq;
using Content.Server._Sunrise.BloodCult.Objectives.Components;
using Content.Server._Sunrise.BloodCult.Objectives.Systems;
using Content.Server._Sunrise.BloodCult.Runes.Systems;
using Content.Server._Sunrise.TraitorTarget;
using Content.Server.Antag;
using Content.Server.Bed.Cryostorage;
using Content.Server.Bible.Components;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
using Content.Server.RoundEnd;
using Content.Server.Storage.EntitySystems;
using Content.Shared._Sunrise.BloodCult;
using Content.Shared._Sunrise.BloodCult.Components;
using Content.Shared._Sunrise.CollectiveMind;
using Content.Shared.Actions;
using Content.Shared.Actions.Components;
using Content.Shared.Body.Systems;
using Content.Shared.Clumsy;
using Content.Shared.GameTicking.Components;
using Content.Shared.Humanoid;
using Content.Shared.Inventory;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Mindshield.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.NPC.Systems;
using Content.Shared.Roles;
using Content.Shared.StatusIcon.Components;
using Content.Shared.Tag;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server._Sunrise.BloodCult.GameRule;
public sealed class BloodCultRuleSystem : GameRuleSystem<BloodCultRuleComponent>
{
[Dependency] private readonly AntagSelectionSystem _antagSelection = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly SharedBodySystem _bodySystem = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly NpcFactionSystem _factionSystem = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly StorageSystem _storageSystem = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
[Dependency] private readonly KillCultistTargetsConditionSystem _cultistTargetsConditionSystem = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
private readonly EntProtoId _mindRoleCultistPrototypeId = "MindRoleCultist";
private readonly EntProtoId _cultistKillObjective = "CultistKillObjective";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CultNarsieSummoned>(OnNarsieSummon);
SubscribeLocalEvent<UpdateCultAppearance>(UpdateCultistsAppearance);
SubscribeLocalEvent<BloodCultistComponent, ComponentInit>(OnCultistComponentInit);
SubscribeLocalEvent<BloodCultistComponent, ComponentRemove>(OnCultistComponentRemoved);
SubscribeLocalEvent<BloodCultistComponent, MobStateChangedEvent>(OnCultistsStateChanged);
SubscribeLocalEvent<BloodCultTargetComponent, ComponentShutdown>(OnCultTargetDeleted);
SubscribeLocalEvent<BloodCultTargetComponent, CryostorageEnteredEvent>(OnCultTargetEnterCryo);
SubscribeLocalEvent<BloodCultRuleComponent, AfterAntagEntitySelectedEvent>(AfterEntitySelected);
SubscribeLocalEvent<BloodCultRuleComponent, AntagSelectionCompleteEvent>(OnAfterAntagSelectionComplete);
}
private void OnCultTargetEnterCryo(EntityUid uid, BloodCultTargetComponent component, CryostorageEnteredEvent args)
{
var rule = GetRule();
if (rule == null)
return;
// Логика уровня /b: Если цель культа попадет в крио, значит эту цель надо заменить.
if (rule.CultTargets.ContainsKey(uid))
{
rule.CultTargets.Remove(uid);
RemCompDeferred<BloodCultTargetComponent>(uid);
AddCultTarget(rule);
}
}
private void OnCultTargetDeleted(EntityUid uid, BloodCultTargetComponent component, ComponentShutdown args)
{
var rule = GetRule();
if (rule == null)
return;
// Логика уровня /b: Если цель культа будет удалена, значит эту цель надо заменить.
// Жертвоприношение не будет считаться удалением
if (rule.CultTargets.ContainsKey(uid) && rule.CultTargets[uid] == false)
{
rule.CultTargets.Remove(uid);
RemCompDeferred<BloodCultTargetComponent>(uid);
AddCultTarget(rule);
}
}
public void AddCultTarget(BloodCultRuleComponent rule)
{
var potentialTargets = FindPotentialTargets();
potentialTargets.RemoveAll(rule.CultTargets.Keys.Contains);
var priorityTargets = potentialTargets
.Where(target =>
HasComp<BibleUserComponent>(target) ||
HasComp<MindShieldComponent>(target))
.ToList();
potentialTargets.RemoveAll(priorityTargets.Contains);
var selectedVictims = SelectRandomTargets(priorityTargets, 1);
selectedVictims.AddRange(SelectRandomTargets(potentialTargets, 1 - selectedVictims.Count));
foreach (var selectedVictim in selectedVictims)
{
rule.CultTargets.Add(selectedVictim, false);
}
var query = EntityQueryEnumerator<KillCultistTargetsConditionComponent>();
while (query.MoveNext(out var uid, out var killCultistTargetsComponent))
{
_cultistTargetsConditionSystem.RefresTitle(uid, rule.CultTargets, killCultistTargetsComponent);
}
}
/// <summary>
/// Manually add a specific entity as a cult target.
/// </summary>
public bool AddSpecificCultTarget(EntityUid target, BloodCultRuleComponent rule)
{
if (!Exists(target) || rule.CultTargets.ContainsKey(target))
return false;
rule.CultTargets.Add(target, false);
EnsureComp<BloodCultTargetComponent>(target);
var query = EntityQueryEnumerator<KillCultistTargetsConditionComponent>();
while (query.MoveNext(out var uid, out var killCultistTargetsComponent))
{
_cultistTargetsConditionSystem.RefresTitle(uid, rule.CultTargets, killCultistTargetsComponent);
}
return true;
}
/// <summary>
/// Manually remove a specific entity as a cult target.
/// </summary>
public bool RemoveSpecificCultTarget(EntityUid target, BloodCultRuleComponent rule)
{
if (!rule.CultTargets.ContainsKey(target))
return false;
rule.CultTargets.Remove(target);
if (Exists(target))
RemComp<BloodCultTargetComponent>(target);
var query = EntityQueryEnumerator<KillCultistTargetsConditionComponent>();
while (query.MoveNext(out var uid, out var killCultistTargetsComponent))
{
_cultistTargetsConditionSystem.RefresTitle(uid, rule.CultTargets, killCultistTargetsComponent);
}
return true;
}
protected override void Added(EntityUid uid,
BloodCultRuleComponent component,
GameRuleComponent gameRule,
GameRuleAddedEvent args)
{
base.Added(uid, component, gameRule, args);
var cultTypes = Enum.GetValues(typeof(BloodCultType)).Cast<BloodCultType>().ToArray();
component.CultType = _random.Pick(cultTypes);
}
protected override void AppendRoundEndText(EntityUid uid,
BloodCultRuleComponent component,
GameRuleComponent gameRule,
ref RoundEndTextAppendEvent args)
{
var winText = Loc.GetString($"cult-cond-{component.WinCondition.ToString().ToLower()}");
args.AddLine(winText);
args.AddLine(Loc.GetString("cultists-list-start"));
var antags = _antagSelection.GetAntagIdentifiers(uid);
foreach (var (_, sessionData, name) in antags)
{
var lising = Loc.GetString("cultists-list-name", ("name", name), ("user", sessionData.UserName));
args.AddLine(lising);
}
}
private void AfterEntitySelected(Entity<BloodCultRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
{
Log.Debug($"AfterAntagEntitySelected {ToPrettyString(ent)}");
MakeCultist(args.EntityUid, ent.Comp);
}
private void OnCultistsStateChanged(EntityUid uid, BloodCultistComponent component, MobStateChangedEvent ev)
{
if (ev.NewMobState == MobState.Dead)
{
CheckRoundShouldEnd();
}
}
public BloodCultRuleComponent? GetRule()
{
var rule = EntityQuery<BloodCultRuleComponent>().FirstOrDefault();
return rule;
}
public void ChangeSacrificeCount(BloodCultRuleComponent rule, int count)
{
rule.SacrificeCount = count;
}
private void OnAfterAntagSelectionComplete(Entity<BloodCultRuleComponent> ent, ref AntagSelectionCompleteEvent args)
{
var potentialTargets = FindPotentialTargets();
var priorityTargets = potentialTargets
.Where(target =>
HasComp<BibleUserComponent>(target) ||
HasComp<MindShieldComponent>(target))
.ToList();
potentialTargets.RemoveAll(priorityTargets.Contains);
var numTargets = MathHelper.Clamp(
_playerManager.PlayerCount / ent.Comp.TargetsPerPlayer,
ent.Comp.MinTargets,
ent.Comp.MaxTargets);
var selectedVictims = SelectRandomTargets(priorityTargets, numTargets);
selectedVictims.AddRange(SelectRandomTargets(potentialTargets, numTargets - selectedVictims.Count));
foreach (var selectedVictim in selectedVictims)
{
ent.Comp.CultTargets.Add(selectedVictim, false);
EnsureComp<BloodCultTargetComponent>(selectedVictim);
}
var query = EntityQueryEnumerator<KillCultistTargetsConditionComponent>();
while (query.MoveNext(out var uid, out var killCultistTargetsComponent))
{
_cultistTargetsConditionSystem.RefresTitle(uid, ent.Comp.CultTargets, killCultistTargetsComponent);
}
}
private List<EntityUid> SelectRandomTargets(List<EntityUid> targets, int count)
{
_random.Shuffle(targets);
return targets.Take(count).ToList();
}
public bool CultAwakened()
{
var querry = EntityQueryEnumerator<BloodCultRuleComponent, GameRuleComponent>();
while (querry.MoveNext(out _, out var cultRuleComponent, out _))
{
var cultists = new List<EntityUid>();
var cultisQuery = EntityQueryEnumerator<BloodCultistComponent>();
while (cultisQuery.MoveNext(out var cultistUid, out _))
{
cultists.Add(cultistUid);
}
var constructs = new List<EntityUid>();
var constructQuery = EntityQueryEnumerator<ConstructComponent>();
while (constructQuery.MoveNext(out var constructUid, out _))
{
constructs.Add(constructUid);
}
var totalPlayers = _playerManager.PlayerCount;
var pentagramThreshold = totalPlayers * cultRuleComponent.PentagramThresholdPercentage / 100;
var totalCultMembers = cultists.Count + constructs.Count;
if (totalCultMembers > pentagramThreshold)
{
return true;
}
}
return false;
}
public bool TargetsKill()
{
var query = EntityQueryEnumerator<BloodCultRuleComponent, GameRuleComponent>();
while (query.MoveNext(out _, out var cultRuleComponent, out _))
{
if (cultRuleComponent.CultTargets.Values.All(value => value))
{
return true;
}
}
return false;
}
private void CheckRoundShouldEnd()
{
var query = EntityQueryEnumerator<BloodCultRuleComponent, GameRuleComponent>();
var aliveCultistsCount = 0;
while (query.MoveNext(out _, out var cultRuleComponent, out _))
{
var cultisQuery = EntityQueryEnumerator<BloodCultistComponent>();
while (cultisQuery.MoveNext(out var cultistUid, out _))
{
if (!HasComp<HumanoidAppearanceComponent>(cultistUid))
continue;
if (!TryComp<MobStateComponent>(cultistUid, out var mobState))
continue;
if (_mobStateSystem.IsAlive(cultistUid, mobState))
{
aliveCultistsCount++;
}
}
if (aliveCultistsCount != 0)
continue;
cultRuleComponent.WinCondition = CultWinCondition.CultFailure;
_roundEndSystem.EndRound();
}
}
private void OnCultistComponentInit(EntityUid uid, BloodCultistComponent component, ComponentInit args)
{
var ev = new UpdateCultAppearance();
RaiseLocalEvent(ev);
_actionsSystem.AddAction(uid, BloodCultistComponent.BloodMagicAction);
}
private void OnCultistComponentRemoved(EntityUid uid, BloodCultistComponent component, ComponentRemove args)
{
if (TryComp<CollectiveMindComponent>(uid, out var collectiveMind))
{
collectiveMind.Minds.Remove("BloodCult");
if (collectiveMind.Minds.Count == 0)
RemComp<CollectiveMindComponent>(uid);
}
if (_mindSystem.TryGetMind(uid, out var mindId, out var mind))
{
if (_mindSystem.TryFindObjective((mindId, mind), _cultistKillObjective, out var objective))
{
_mindSystem.TryRemoveObjective(mindId, mind, mind.Objectives.IndexOf(objective.Value));
}
_roles.MindRemoveRole<BloodCultistRoleComponent>((mindId, mind));
}
var query = EntityQueryEnumerator<BloodCultRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var ruleEnt, out var cultRuleComponent, out _))
{
if (!GameTicker.IsGameRuleAdded(ruleEnt))
continue;
RemoveCultistAppearance(uid);
CheckRoundShouldEnd();
}
_actionsSystem.RemoveAction(uid, component.BloodMagicEntity);
if (TryComp<ActionsComponent>(uid, out var actionsComponent))
{
foreach (var userAction in actionsComponent.Actions)
{
var entityPrototypeId = MetaData(userAction).EntityPrototype?.ID;
if (entityPrototypeId != null && BloodCultistComponent.CultistActions.Contains(entityPrototypeId))
_actionsSystem.RemoveAction(uid, userAction);
}
}
}
private void RemoveCultistAppearance(EntityUid cultist)
{
if (TryComp<HumanoidAppearanceComponent>(cultist, out var appearanceComponent))
{
appearanceComponent.EyeColor = Color.White;
Dirty(cultist, appearanceComponent);
}
RemComp<PentagramComponent>(cultist);
}
private void UpdateCultistsAppearance(UpdateCultAppearance ev)
{
var rule = GetRule();
if (rule == null)
return;
var cultists = new List<EntityUid>();
var cultisQuery = EntityQueryEnumerator<BloodCultistComponent>();
while (cultisQuery.MoveNext(out var cultistUid, out _))
{
if (HasComp<HumanoidAppearanceComponent>(cultistUid))
cultists.Add(cultistUid);
}
var constructs = new List<EntityUid>();
var constructQuery = EntityQueryEnumerator<ConstructComponent>();
while (constructQuery.MoveNext(out var constructUid, out _))
{
constructs.Add(constructUid);
}
var totalCultMembers = cultists.Count + constructs.Count;
var totalPlayers = _playerManager.PlayerCount;
var redEyeThreshold = totalPlayers * rule.ReadEyeThresholdPercentage / 100;
var pentagramThreshold = totalPlayers * rule.PentagramThresholdPercentage / 100;
if (totalCultMembers < redEyeThreshold)
return;
foreach (var cultist in cultists)
{
if (TryComp<HumanoidAppearanceComponent>(cultist, out var appearanceComponent))
{
appearanceComponent.EyeColor = rule.EyeColor;
Dirty(cultist, appearanceComponent);
}
if (totalCultMembers < pentagramThreshold)
return;
EnsureComp<PentagramComponent>(cultist);
}
}
private List<EntityUid> FindPotentialTargets()
{
var potentialTargets = new List<EntityUid>();
var query = EntityQueryEnumerator<MindContainerComponent, AntagTargetComponent, HumanoidAppearanceComponent>();
while (query.MoveNext(out var uid, out var mind, out _, out _))
{
if (mind.Mind == null || HasComp<BloodCultistComponent>(uid))
continue;
potentialTargets.Add(uid);
}
return potentialTargets;
}
public bool MakeCultist(EntityUid cultist, BloodCultRuleComponent rule)
{
if (!_mindSystem.TryGetMind(cultist, out var mindId, out var mind))
return false;
_roles.MindAddRole(mindId, _mindRoleCultistPrototypeId);
var isHumanoid = HasComp<HumanoidAppearanceComponent>(cultist);
var cultistComponent = EnsureComp<BloodCultistComponent>(cultist);
if (HasComp<ClumsyComponent>(cultist))
RemComp<ClumsyComponent>(cultist);
EnsureComp<CultMemberComponent>(cultist);
var collectiveMind = EnsureComp<CollectiveMindComponent>(cultist);
collectiveMind.Minds.Add("BloodCult");
_tagSystem.AddTag(cultist, "Cultist");
_factionSystem.RemoveFaction(cultist, "NanoTrasen", false);
_factionSystem.AddFaction(cultist, "BloodCult");
// Для животных нужно добавить компонент StatusIcon, чтобы показывать иконку культиста
if (!isHumanoid && !HasComp<StatusIconComponent>(cultist))
EnsureComp<StatusIconComponent>(cultist);
if (rule.CultType == null ||
!_prototype.TryIndex<BloodCultPrototype>($"{rule.CultType.Value.ToString()}Cult", out var cultPrototype))
return false;
cultistComponent.CultType = rule.CultType;
if (isHumanoid)
{
_inventorySystem.TryGetSlotEntity(cultist, "back", out var backPack);
foreach (var itemPrototype in cultPrototype.StartingItems)
{
var itemEntity = Spawn(itemPrototype, Transform(cultist).Coordinates);
if (backPack != null)
{
_storageSystem.Insert(backPack.Value, itemEntity, out _);
}
}
if (_playerManager.TryGetSessionById(mind.UserId, out var session))
{
_audioSystem.PlayGlobal(rule.GreatingsSound,
Filter.Empty().AddPlayer(session),
false,
AudioParams.Default);
_chatManager.DispatchServerMessage(session, Loc.GetString("cult-role-greeting"));
}
_mindSystem.TryAddObjective(mindId, mind, "CultistKillObjective");
}
Dirty(cultist, cultistComponent);
return true;
}
private void OnNarsieSummon(CultNarsieSummoned ev)
{
var query = EntityQuery<MobStateComponent, MindContainerComponent, BloodCultistComponent>().ToList();
foreach (var (mobState, mindContainer, _) in query)
{
if (!mindContainer.HasMind || mindContainer.Mind is null)
{
continue;
}
var reaper = Spawn(BloodCultSystem.ReaperConstructPrototypeId, Transform(mobState.Owner).Coordinates);
_mindSystem.TransferTo(mindContainer.Mind.Value, reaper);
_bodySystem.GibBody(mobState.Owner);
}
_roundEndSystem.EndRound();
}
}