main cult

This commit is contained in:
EnefFlow
2024-01-27 15:19:52 +03:00
committed by Aviu00
parent 6310813ce6
commit 4fab8188f0
429 changed files with 12281 additions and 9 deletions

View File

@@ -0,0 +1,12 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Utility;
namespace Content.Server.White.BecomeDustOnDeathSystem;
[RegisterComponent]
public sealed partial class BecomeDustOnDeathComponent : Component
{
[DataField("sprite", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string SpawnOnDeathPrototype = "Ectoplasm";
}

View File

@@ -0,0 +1,19 @@
using Content.Shared.Mobs;
namespace Content.Server.White.BecomeDustOnDeathSystem;
public sealed class BecomeDustOnDeathSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<BecomeDustOnDeathComponent, MobStateChangedEvent>(OnMobStateChanged);
}
private void OnMobStateChanged(EntityUid uid, BecomeDustOnDeathComponent component, MobStateChangedEvent args)
{
var xform = Transform(uid);
Spawn(component.SpawnOnDeathPrototype, xform.Coordinates);
QueueDel(uid);
}
}

View File

@@ -0,0 +1,11 @@
using Content.Shared.Actions.ActionTypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Server.White.Cult;
[RegisterComponent]
public sealed partial class ConstructComponent : Component
{
[DataField("actions", customTypeSerializer: typeof(PrototypeIdListSerializer<InstantActionPrototype>))]
public List<string> Actions = new();
}

View File

@@ -0,0 +1,74 @@
using Content.Server.GameTicking.Presets;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.White.Cult;
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Server.White.Cult.GameRule;
[RegisterComponent]
public sealed partial class CultRuleComponent : Component
{
public readonly SoundSpecifier GreatingsSound = new SoundPathSpecifier("/Audio/White/Cult/blood_cult_greeting.ogg");
[DataField("cultPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<GamePresetPrototype>))]
public static string CultGamePresetPrototype = "Cult";
[DataField("cultistPrototypeId", customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
public static string CultistPrototypeId = "Cultist";
[DataField("reaperPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public static string ReaperPrototype = "ReaperConstruct";
[ViewVariables(VVAccess.ReadOnly), DataField("tileId")]
public static string CultFloor = "CultFloor";
[DataField("eyeColor")]
public static Color EyeColor = Color.FromHex("#f80000");
public static string HolyWaterReagent = "HolyWater";
[DataField("redEyeThreshold")]
public static int ReadEyeThreshold = 5;
[DataField("pentagramThreshold")]
public static int PentagramThreshold = 8;
public Dictionary<IPlayerSession, HumanoidCharacterProfile> StarCandidates = new();
[DataField("cultistStartingItems", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public List<string> StartingItems = new();
[DataField("cultistRolePrototype", customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
public string CultistRolePrototype = "Cultist";
/// <summary>
/// Players who played as an cultist at some point in the round.
/// </summary>
public Dictionary<string, string> CultistsList = new();
public EntityUid? CultTarget;
public List<CultistComponent> Cultists = new();
public List<ConstructComponent> Constructs = new();
public CultWinCondition WinCondition;
}
public enum CultWinCondition : byte
{
CultWin,
CultFailure
}
public sealed class CultNarsieSummoned : EntityEventArgs
{
}

View File

@@ -0,0 +1,492 @@
using System.Linq;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.NPC.Systems;
using Content.Server.Roles;
using Content.Server.Roles.Jobs;
using Content.Server.RoundEnd;
using Content.Server.Shuttles.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Body.Systems;
using Content.Shared.Humanoid;
using Content.Shared.Inventory;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Objectives;
using Content.Shared.Players;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.White.Cult;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Configuration;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using Content.Shared._White;
using Content.Shared.Mind;
namespace Content.Server.White.Cult.GameRule;
public sealed class CultRuleSystem : GameRuleSystem<CultRuleComponent>
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly StorageSystem _storageSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly NpcFactionSystem _factionSystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly SharedBodySystem _bodySystem = default!;
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
[Dependency] private readonly JobSystem _jobSystem = default!;
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
private ISawmill _sawmill = default!;
private int _minimalCultists;
private int _cultGameRuleMinimapPlayers;
public override void Initialize()
{
base.Initialize();
_sawmill = Logger.GetSawmill("preset");
_minimalCultists = _cfg.GetCVar(WhiteCVars.CultMinStartingPlayers);
_cultGameRuleMinimapPlayers = _cfg.GetCVar(WhiteCVars.CultMinPlayers);
SubscribeLocalEvent<RoundStartAttemptEvent>(OnStartAttempt);
SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(OnPlayersSpawned);
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndText);
SubscribeLocalEvent<CultNarsieSummoned>(OnNarsieSummon);
SubscribeLocalEvent<CultistComponent, ComponentInit>(OnCultistComponentInit);
SubscribeLocalEvent<CultistComponent, ComponentRemove>(OnCultistComponentRemoved);
SubscribeLocalEvent<CultistComponent, MobStateChangedEvent>(OnCultistsStateChanged);
}
private void OnCultistsStateChanged(EntityUid uid, CultistComponent component, MobStateChangedEvent ev)
{
if (ev.NewMobState == MobState.Dead)
{
CheckRoundShouldEnd();
}
}
public MindComponent? GetTarget()
{
var querry = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
while (querry.MoveNext(out _, out var cultRuleComponent, out _))
{
if (cultRuleComponent.CultTarget.HasValue && TryComp<MindComponent>(cultRuleComponent.CultTarget.Value, out var mind))
{
return mind;
}
}
return null!;
}
public bool CanSummonNarsie()
{
var querry = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
while (querry.MoveNext(out _, out var cultRuleComponent, out _))
{
var cultistsAmount = cultRuleComponent.Cultists.Count;
var constructsAmount = cultRuleComponent.Constructs.Count;
var enoughCultists = cultistsAmount + constructsAmount > 10;
if (!enoughCultists)
{
return false;
}
var target = GetTarget();
var targetKilled = target == null || _mindSystem.IsCharacterDeadIc(target);
if (targetKilled)
return true;
}
return false;
}
private void CheckRoundShouldEnd()
{
var querry = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
var aliveCultistsCount = 0;
while (querry.MoveNext(out _, out var cultRuleComponent, out _))
{
foreach (var cultistComponent in cultRuleComponent.Cultists)
{
var owner = cultistComponent.Owner;
if (!TryComp<MobStateComponent>(owner, out var mobState))
continue;
if (_mobStateSystem.IsAlive(owner, mobState))
{
aliveCultistsCount++;
}
}
}
if (aliveCultistsCount == 0)
{
_roundEndSystem.EndRound();
}
}
private void OnCultistComponentInit(EntityUid uid, CultistComponent component, ComponentInit args)
{
var query = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var ruleEnt, out var cultRuleComponent, out _))
{
if (!GameTicker.IsGameRuleAdded(ruleEnt))
continue;
if (!TryComp<MindContainerComponent>(uid, out var mindComponent))
return;
if (!mindComponent.HasMind)
return;
cultRuleComponent.Cultists.Add(component);
if (TryComp<ActorComponent>(component.Owner, out var actor))
{
cultRuleComponent.CultistsList.Add(MetaData(component.Owner).EntityName, actor.PlayerSession.Name);
}
var traitorRole = new TraitorRoleComponent()
{
PrototypeId = cultRuleComponent.CultistRolePrototype
};
_roleSystem.MindAddRole(mindComponent.Mind.Value, traitorRole);
UpdateCultistsAppearance(cultRuleComponent);
}
}
private void OnCultistComponentRemoved(EntityUid uid, CultistComponent component, ComponentRemove args)
{
var query = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var ruleEnt, out var cultRuleComponent, out _))
{
if (!GameTicker.IsGameRuleAdded(ruleEnt))
continue;
cultRuleComponent.Cultists.Remove(component);
RemoveCultistAppearance(component);
CheckRoundShouldEnd();
}
}
private void RemoveCultistAppearance(CultistComponent component)
{
if (TryComp<HumanoidAppearanceComponent>(component.Owner, out var appearanceComponent))
{
//Потому что я так сказал
appearanceComponent.EyeColor = Color.White;
Dirty(appearanceComponent);
}
RemComp<PentagramComponent>(component.Owner);
}
private void UpdateCultistsAppearance(CultRuleComponent cultRuleComponent)
{
var cultistsCount = cultRuleComponent.Cultists.Count;
var constructsCount = cultRuleComponent.Constructs.Count;
var totalCultMembers = cultistsCount + constructsCount;
if (totalCultMembers < CultRuleComponent.ReadEyeThreshold)
return;
foreach (var cultistComponent in cultRuleComponent.Cultists)
{
if (TryComp<HumanoidAppearanceComponent>(cultistComponent.Owner, out var appearanceComponent))
{
appearanceComponent.EyeColor = CultRuleComponent.EyeColor;
Dirty(appearanceComponent);
}
if (totalCultMembers < CultRuleComponent.PentagramThreshold)
return;
EnsureComp<PentagramComponent>(cultistComponent.Owner);
}
}
private void OnRoundEndText(RoundEndTextAppendEvent ev)
{
var querry = EntityQuery<CultRuleComponent>();
foreach (var cultRuleComponent in querry)
{
var winText = Loc.GetString($"cult-cond-{cultRuleComponent.WinCondition.ToString().ToLower()}");
ev.AddLine(winText);
ev.AddLine(Loc.GetString("cultists-list-start"));
foreach (var (entityName, ckey) in cultRuleComponent.CultistsList)
{
var lising = Loc.GetString("cultists-list-name", ("name", entityName), ("user", ckey));
ev.AddLine(lising);
}
}
}
private void OnStartAttempt(RoundStartAttemptEvent ev)
{
var query = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var uid, out _, out var gameRule))
{
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
continue;
var minPlayers = _cultGameRuleMinimapPlayers;
if (!ev.Forced && ev.Players.Length < minPlayers)
{
_chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-not-enough-ready-players",
("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
ev.Cancel();
continue;
}
if (ev.Players.Length == 0)
{
_chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-no-one-ready"));
ev.Cancel();
}
}
}
private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev)
{
var query = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var uid, out var cultRule, out var gameRule))
{
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
continue;
foreach (var player in ev.Players)
{
if (!ev.Profiles.ContainsKey(player.UserId))
continue;
cultRule.StarCandidates[player] = ev.Profiles[player.UserId];
}
var potentialCultists = FindPotentialCultist(cultRule.StarCandidates);
var pickedCultist = PickCultists(potentialCultists);
var potentialTargets = FindPotentialTargets(pickedCultist);
cultRule.CultTarget = _random.PickAndTake(potentialTargets).Mind;
foreach (var pickerCultist in pickedCultist)
{
MakeCultist(pickerCultist);
}
}
}
private List<MindContainerComponent> FindPotentialTargets(List<IPlayerSession> exclude = null!)
{
var querry = EntityManager.EntityQuery<MindContainerComponent, HumanoidAppearanceComponent, ActorComponent>();
var potentialTargets = new List<MindContainerComponent>();
foreach (var (mind, _, actor) in querry)
{
var entity = mind.Mind;
if (entity == default)
continue;
if (exclude?.Contains(actor.PlayerSession) is true)
{
continue;
}
potentialTargets.Add(mind);
}
return potentialTargets;
}
private List<IPlayerSession> FindPotentialCultist(in Dictionary<IPlayerSession, HumanoidCharacterProfile> candidates)
{
var list = new List<IPlayerSession>();
var pendingQuery = GetEntityQuery<PendingClockInComponent>();
foreach (var player in candidates.Keys)
{
// Role prevents antag.
if (!_jobSystem.CanBeAntag(player)) continue;
// Latejoin
if (player.AttachedEntity != null && pendingQuery.HasComponent(player.AttachedEntity.Value))
continue;
list.Add(player);
}
var prefList = new List<IPlayerSession>();
foreach (var player in list)
{
var profile = candidates[player];
if (profile.AntagPreferences.Contains(CultRuleComponent.CultistPrototypeId))
{
prefList.Add(player);
}
}
if (prefList.Count == 0)
{
_sawmill.Info("Insufficient preferred cultists, picking at random.");
prefList = list;
}
if (prefList.Count >= _minimalCultists)
{
return prefList;
}
var playersToAdd = _minimalCultists - prefList.Count;
foreach (var prefPlayer in prefList)
{
list.Remove(prefPlayer);
}
for (var i = 0; i < playersToAdd; i++)
{
var randomPlayer = _random.PickAndTake(list);
prefList.Add(randomPlayer);
}
return prefList;
}
private List<IPlayerSession> PickCultists(List<IPlayerSession> prefList)
{
var result = new List<IPlayerSession>();
if (prefList.Count == 0)
{
_sawmill.Info("Insufficient ready players to fill up with cultists, stopping the selection.");
return result;
}
var minCultists = _cfg.GetCVar(WhiteCVars.CultMinPlayers);
var maxCultists = _cfg.GetCVar(WhiteCVars.CultMaxStartingPlayers);
var actualCultistCount = prefList.Count > maxCultists ? maxCultists : minCultists;
for (var i = 0; i < actualCultistCount; i++)
{
result.Add(_random.PickAndTake(prefList));
}
return result;
}
public bool MakeCultist(IPlayerSession cultist)
{
var cultistRule = EntityQuery<CultRuleComponent>().FirstOrDefault();
if (cultistRule == null)
{
GameTicker.StartGameRule(CultRuleComponent.CultGamePresetPrototype, out var ruleEntity);
cultistRule = Comp<CultRuleComponent>(ruleEntity);
}
var mind = cultist.Data.ContentData()?.Mind;
if (mind == null)
{
_sawmill.Info("Failed getting mind for picked cultist.");
return false;
}
var playerEntity = cultist.AttachedEntity;
if (!playerEntity.HasValue)
{
_sawmill.Error("Mind picked for cultist did not have an attached entity.");
return false;
}
var mindComponent = Comp<MindComponent>(mind.Value);
DebugTools.AssertNotNull(playerEntity.Value);
EnsureComp<CultistComponent>(playerEntity.Value);
_factionSystem.RemoveFaction(playerEntity.Value, "NanoTrasen", false);
_factionSystem.AddFaction(playerEntity.Value, "Cultist");
if (_inventorySystem.TryGetSlotEntity(playerEntity.Value, "back", out var backPack))
{
foreach (var itemPrototype in cultistRule.StartingItems)
{
var itemEntity = Spawn(itemPrototype, Transform(playerEntity.Value).Coordinates);
if (backPack != null)
{
_storageSystem.Insert(backPack.Value, itemEntity);
}
}
}
_audioSystem.PlayGlobal(cultistRule.GreatingsSound, Filter.Empty().AddPlayer(cultist), false,
AudioParams.Default);
_chatManager.DispatchServerMessage(cultist, Loc.GetString("cult-role-greeting"));
if (_prototypeManager.TryIndex<ObjectivePrototype>("CultistKillObjective", out var cultistObjective))
{
_mindSystem.TryAddObjective(mind.Value, mindComponent,cultistObjective);
}
return true;
}
private void OnNarsieSummon(CultNarsieSummoned ev)
{
var query = EntityQuery<MobStateComponent, MindContainerComponent, CultistComponent>().ToList();
foreach (var (mobState, mindContainer, _) in query)
{
if (!mindContainer.HasMind || mindContainer.Mind is null)
{
continue;
}
var reaper = Spawn(CultRuleComponent.ReaperPrototype, Transform(mobState.Owner).Coordinates);
_mindSystem.TransferTo(mindContainer.Mind.Value, reaper);
_bodySystem.GibBody(mobState.Owner);
}
_roundEndSystem.EndRound();
}
}

View File

@@ -0,0 +1,11 @@
namespace Content.Server.White.Cult.HolyWater;
[RegisterComponent]
public sealed partial class BibleWaterConvertComponent : Component
{
[DataField("convertedId"), ViewVariables(VVAccess.ReadWrite)]
public string ConvertedId = "Water";
[DataField("ConvertedToId"), ViewVariables(VVAccess.ReadWrite)]
public string ConvertedToId = "HolyWater";
}

View File

@@ -0,0 +1,54 @@
using System.Threading;
using Content.Server.Popups;
using Content.Server.Stunnable;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.IdentityManagement;
using Content.Shared.White.Cult;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.White.Cult.HolyWater;
[ImplicitDataDefinitionForInheritors]
[MeansImplicitUse]
public sealed partial class DeconvertCultist : ReagentEffect
{
public override bool ShouldLog => true;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
return Loc.GetString("reagent-effect-guidebook-deconvert-cultist");
}
public override void Effect(ReagentEffectArgs args)
{
var uid = args.SolutionEntity;
if (!args.EntityManager.TryGetComponent(uid, out CultistComponent? component))
return;
if (component.HolyConvertToken != null)
return;
args.EntityManager.System<StunSystem>()
.TryParalyze(uid, TimeSpan.FromSeconds(component.HolyConvertTime + 5f), true);
var target = Identity.Name(uid, args.EntityManager);
args.EntityManager.System<PopupSystem>()
.PopupEntity(Loc.GetString("holy-water-started-converting", ("target", target)), uid);
component.HolyConvertToken = new CancellationTokenSource();
Timer.Spawn(TimeSpan.FromSeconds(component.HolyConvertTime), () => ConvertCultist(uid, args.EntityManager),
component.HolyConvertToken.Token);
}
private void ConvertCultist(EntityUid uid, IEntityManager entityManager)
{
if (!entityManager.TryGetComponent<CultistComponent>(uid, out var cultist))
return;
cultist.HolyConvertToken = null;
entityManager.RemoveComponent<CultistComponent>(uid);
entityManager.RemoveComponent<PentagramComponent>(uid);
}
}

View File

@@ -0,0 +1,54 @@
using System.Linq;
using Content.Server.Chemistry.Components.SolutionManager;
using Content.Server.Stunnable;
using Content.Shared.Interaction;
using Content.Shared.Mobs.Components;
using Content.Shared.Popups;
using Robust.Server.GameObjects;
namespace Content.Server.White.Cult.HolyWater;
public sealed class HolyWaterSystem : EntitySystem
{
[Dependency] private readonly StunSystem _stun = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly AudioSystem _audio = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BibleWaterConvertComponent, AfterInteractEvent>(OnBibleInteract);
}
private void OnBibleInteract(EntityUid uid, BibleWaterConvertComponent component, AfterInteractEvent args)
{
if (HasComp<MobStateComponent>(uid))
return;
if (!TryComp<SolutionContainerManagerComponent>(args.Target, out var container))
return;
foreach (var solution in container.Solutions.Values.Where(solution => solution.ContainsReagent(component.ConvertedId, null)))
{
foreach (var reagent in solution.Contents)
{
if (reagent.Reagent.Prototype != component.ConvertedId)
continue;
var amount = reagent.Quantity;
solution.RemoveReagent(reagent.Reagent.Prototype, reagent.Quantity);
solution.AddReagent(component.ConvertedToId, amount);
if (args.Target == null)
return;
_popup.PopupEntity(Loc.GetString("holy-water-converted"), args.Target.Value, args.User);
_audio.PlayPvs("/Audio/Effects/holy.ogg", args.Target.Value);
return;
}
}
}
}

View File

@@ -0,0 +1,13 @@
namespace Content.Server.White.Cult.Items.Components;
[RegisterComponent]
public sealed partial class CultRobeModifierComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("speedModifier")]
public float SpeedModifier = 1.45f;
[ViewVariables(VVAccess.ReadOnly), DataField("damageModifierSetId")]
public string DamageModifierSetId = "CultRobe";
public string? StoredDamageSetId { get; set; }
}

View File

@@ -0,0 +1,8 @@
namespace Content.Server.White.Cult.Items.Components;
[RegisterComponent]
public sealed partial class ReturnItemOnThrowComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("stunTime")]
public float StunTime = 1f;
}

View File

@@ -0,0 +1,11 @@
namespace Content.Server.White.Cult.Items.Components;
[RegisterComponent]
public sealed partial class ShuttleCurseComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("delayTime")]
public TimeSpan DelayTime = TimeSpan.FromSeconds(120);
[ViewVariables(VVAccess.ReadWrite), DataField("cooldown")]
public TimeSpan Cooldown = TimeSpan.FromSeconds(180);
}

View File

@@ -0,0 +1,27 @@
using Content.Server.UserInterface;
using Content.Shared.White.Cult.Items;
using Robust.Server.GameObjects;
namespace Content.Server.White.Cult.Items.Components;
[RegisterComponent]
public sealed partial class TorchCultistsProviderComponent : Component
{
[ViewVariables(VVAccess.ReadOnly)]
public BoundUserInterface? UserInterface => Owner.GetUIOrNull(CultTeleporterUiKey.Key);
[ViewVariables(VVAccess.ReadOnly)]
public EntityUid? ItemSelected;
[ViewVariables(VVAccess.ReadWrite), DataField("cooldown")]
public TimeSpan Cooldown = TimeSpan.FromSeconds(30);
[ViewVariables(VVAccess.ReadWrite), DataField("usesLeft")]
public int UsesLeft = 3;
[ViewVariables(VVAccess.ReadOnly)]
public TimeSpan NextUse = TimeSpan.Zero;
[ViewVariables(VVAccess.ReadOnly)]
public bool Active = true;
}

View File

@@ -0,0 +1,81 @@
using Content.Server.White.Cult.Items.Components;
using Content.Shared.Damage;
using Content.Shared.Inventory.Events;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.White.Cult;
namespace Content.Server.White.Cult.Items.Systems;
public sealed class CultRobeModifierSystem : EntitySystem
{
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CultRobeModifierComponent, GotEquippedEvent>(OnEquip);
SubscribeLocalEvent<CultRobeModifierComponent, GotUnequippedEvent>(OnUnequip);
}
private void OnEquip(EntityUid uid, CultRobeModifierComponent component, GotEquippedEvent args)
{
if (!HasComp<CultistComponent>(args.Equipee))
return;
if (args.Slot != "outerClothing")
return;
ModifySpeed(args.Equipee, component, true);
ModifyDamage(args.Equipee, component, true);
}
private void OnUnequip(EntityUid uid, CultRobeModifierComponent component, GotUnequippedEvent args)
{
if (!HasComp<CultistComponent>(args.Equipee))
return;
if (args.Slot != "outerClothing")
return;
ModifySpeed(args.Equipee, component, false);
ModifyDamage(args.Equipee, component, false);
}
private void ModifySpeed(EntityUid uid, CultRobeModifierComponent comp, bool increase)
{
if (!TryComp<MovementSpeedModifierComponent>(uid, out var move))
return;
var walkSpeed = increase ? move.BaseWalkSpeed * comp.SpeedModifier : move.BaseWalkSpeed / comp.SpeedModifier;
var sprintSpeed =
increase ? move.BaseSprintSpeed * comp.SpeedModifier : move.BaseSprintSpeed / comp.SpeedModifier;
_movement.ChangeBaseSpeed(uid, walkSpeed, sprintSpeed, move.Acceleration, move);
}
private void ModifyDamage(EntityUid uid, CultRobeModifierComponent comp, bool increase)
{
var damageSet = string.Empty;
if (increase)
{
if (!TryComp<DamageableComponent>(uid, out var damage))
return;
comp.StoredDamageSetId = damage.DamageModifierSetId;
damageSet = comp.DamageModifierSetId;
}
else
{
if (comp.StoredDamageSetId != null)
damageSet = comp.StoredDamageSetId;
comp.StoredDamageSetId = null;
}
_damageable.SetDamageModifierSetId(uid, damageSet);
}
}

View File

@@ -0,0 +1,42 @@
using Content.Server.Hands.Systems;
using Content.Server.Stunnable;
using Content.Server.White.Cult.Items.Components;
using Content.Shared.Mobs.Components;
using Content.Shared.Throwing;
using Content.Shared.White.Cult;
namespace Content.Server.White.Cult.Items.Systems;
public sealed class ReturnItemOnThrowSystem : EntitySystem
{
[Dependency] private readonly StunSystem _stun = default!;
[Dependency] private readonly HandsSystem _hands = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ReturnItemOnThrowComponent, ThrowDoHitEvent>(OnThrowHit);
}
private void OnThrowHit(EntityUid uid, ReturnItemOnThrowComponent component, ThrowDoHitEvent args)
{
var isCultist = HasComp<CultistComponent>(args.Target);
var thrower = args.Component.Thrower;
if (!HasComp<CultistComponent>(thrower))
return;
if (!HasComp<MobStateComponent>(args.Target))
return;
if (!_stun.IsParalyzed(args.Target))
{
if (!isCultist)
{
_stun.TryParalyze(args.Target, TimeSpan.FromSeconds(component.StunTime), true);
}
}
_hands.PickupOrDrop(thrower, uid);
}
}

View File

@@ -0,0 +1,81 @@
using Content.Server.Chat.Systems;
using Content.Server.RoundEnd;
using Content.Server.Shuttles.Systems;
using Content.Server.White.Cult.Items.Components;
using Content.Shared.GameTicking;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Events;
using Content.Shared.Popups;
using Content.Shared.White.Cult;
using Robust.Shared.Timing;
namespace Content.Server.White.Cult.Items.Systems;
public sealed class ShuttleCurseSystem : EntitySystem
{
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
private const int MaxCurses = 3;
private int _currentCurses = 0;
private TimeSpan? _nextCurse = TimeSpan.Zero;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ShuttleCurseComponent, UseInHandEvent>(OnUse);
SubscribeLocalEvent<RoundEndedEvent>(OnRoundEnd);
}
private void OnRoundEnd(RoundEndedEvent ev)
{
_currentCurses = 0;
_nextCurse = TimeSpan.Zero;
}
private void OnUse(EntityUid uid, ShuttleCurseComponent component, UseInHandEvent args)
{
if (!HasComp<CultistComponent>(args.User))
{
_hands.TryDrop(args.User);
_popup.PopupEntity(Loc.GetString("shuttle-curse-not-cultist"), args.User, args.User);
return;
}
if (!_roundEnd.ShuttleCalled())
{
_popup.PopupEntity(Loc.GetString("shuttle-curse-shuttle-not-called"), args.User, args.User);
return;
}
if (_currentCurses >= MaxCurses)
{
_popup.PopupEntity(Loc.GetString("shuttle-curse-max-curses"), args.User, args.User);
return;
}
if (_nextCurse > _gameTiming.CurTime)
{
_popup.PopupEntity(Loc.GetString("shuttle-curse-cooldown"), args.User, args.User);
return;
}
var shuttle = _entMan.System<EmergencyShuttleSystem>();
if (shuttle.EmergencyShuttleArrived)
{
_popup.PopupEntity(Loc.GetString("shuttle-curse-shuttle-arrived"), args.User, args.User);
return;
}
_roundEnd.DelayCursedShuttle(component.DelayTime);
_popup.PopupEntity(Loc.GetString("shuttle-curse-shuttle-delayed"), args.User, args.User);
_currentCurses++;
_nextCurse = _gameTiming.CurTime + component.Cooldown;
}
}

View File

@@ -0,0 +1,274 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.Popups;
using Content.Server.Pulling;
using Content.Server.Station.Systems;
using Content.Server.Station.Components;
using Content.Server.White.Cult.Items.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Item;
using Content.Shared.Mind.Components;
using Content.Shared.Physics;
using Content.Shared.White.Cult;
using Content.Shared.White.Cult.Items;
using Robust.Server.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.White.Cult.Items.Systems;
public sealed class TorchCultistsProviderSystem : EntitySystem
{
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly SharedTransformSystem _xform = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly SharedPointLightSystem _pointLight = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly PullingSystem _pulling = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TorchCultistsProviderComponent, AfterInteractEvent>(OnInteract);
SubscribeLocalEvent<TorchCultistsProviderComponent, TorchWindowItemSelectedMessage>(OnCultistSelected);
SubscribeLocalEvent<TorchCultistsProviderComponent, ComponentInit>(OnInit);
}
private void OnInit(EntityUid uid, TorchCultistsProviderComponent component, ComponentInit args)
{
UpdateAppearance(uid, component);
}
private void OnInteract(EntityUid uid, TorchCultistsProviderComponent comp, AfterInteractEvent args)
{
if (!args.Target.HasValue)
{
return;
}
if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target.Value))
{
return;
}
if (!TryComp<TorchCultistsProviderComponent>(uid, out var provider))
return;
if (!HasComp<CultistComponent>(args.User))
{
_hands.TryDrop(args.User);
_popup.PopupEntity(Loc.GetString("cult-torch-not-cultist"), args.User, args.User);
return;
}
if (!provider.Active || provider.UsesLeft <= 0)
{
_popup.PopupEntity(Loc.GetString("cult-torch-drained"), args.User, args.User);
return;
}
if (provider.NextUse > _timing.CurTime)
{
_popup.PopupEntity(Loc.GetString("cult-torch-cooldown"), args.User, args.User);
return;
}
if (HasComp<MindContainerComponent>(args.Target))
{
TeleportToRandomLocation(uid, args, comp);
return;
}
if (!HasComp<ItemComponent>(args.Target))
{
return;
}
if (provider.UserInterface == null)
return;
provider.ItemSelected = args.Target;
var cultists = EntityQuery<CultistComponent>();
var list = new Dictionary<string, string>();
foreach (var cultist in cultists)
{
if (!TryComp<MetaDataComponent>(cultist.Owner, out var meta))
return;
if (cultist.Owner == args.User)
continue;
list.Add(meta.Owner.ToString(), meta.EntityName);
}
if (list.Count == 0)
{
_popup.PopupEntity(Loc.GetString("cult-torch-cultists-not-found"), args.User, args.User);
return;
}
UserInterfaceSystem.SetUiState(provider.UserInterface, new TorchWindowBUIState(list));
if (!TryComp<ActorComponent>(args.User, out var actorComponent))
return;
_ui.ToggleUi(provider.UserInterface, actorComponent.PlayerSession);
}
private void OnCultistSelected(
EntityUid uid,
TorchCultistsProviderComponent component,
TorchWindowItemSelectedMessage args)
{
var entityUid = args.Session.AttachedEntity;
var cultists = EntityQuery<CultistComponent>();
foreach (var cultist in cultists)
{
if (cultist.Owner.ToString() == args.EntUid)
entityUid = cultist.Owner;
}
if (entityUid == args.Session.AttachedEntity && entityUid != null)
{
_popup.PopupEntity(Loc.GetString("cult-torch-no-cultist"), entityUid.Value, entityUid.Value);
return;
}
if (component.ItemSelected != null)
{
var item = component.ItemSelected.Value;
if (!TryComp<TransformComponent>(entityUid, out var xForm))
return;
_xform.SetCoordinates(item, xForm.Coordinates);
_hands.PickupOrDrop(entityUid, item);
}
UpdateUsesCount(uid, args.Session.AttachedEntity, component);
}
private void UpdateAppearance(EntityUid uid, TorchCultistsProviderComponent component)
{
AppearanceComponent? appearance = null;
if (!Resolve(uid, ref appearance, false))
return;
_appearance.SetData(uid, VoidTorchVisuals.Activated, component.Active, appearance);
}
private void TeleportToRandomLocation(EntityUid torch, InteractEvent args, TorchCultistsProviderComponent component)
{
if (!args.Target.HasValue)
{
return;
}
if (_pulling.GetPulled(args.User) != args.Target.Value)
{
return;
}
var ownerTransform = Transform(args.User);
if (_station.GetStationInMap(ownerTransform.MapID) is not { } station ||
!TryComp<StationDataComponent>(station, out var data) ||
_station.GetLargestGrid(data) is not { } grid)
{
if (ownerTransform.GridUid == null)
return;
grid = ownerTransform.GridUid.Value;
}
if (!TryComp<MapGridComponent>(grid, out var gridComp))
{
return;
}
var gridTransform = Transform(grid);
var gridBounds = gridComp.LocalAABB.Scale(0.7f); // чтобы не заспавнить на самом краю станции
var targetCoords = gridTransform.Coordinates;
for (var i = 0; i < 25; i++)
{
var randomX = _random.Next((int) gridBounds.Left, (int) gridBounds.Right);
var randomY = _random.Next((int) gridBounds.Bottom, (int) gridBounds.Top);
var tile = new Vector2i(randomX, randomY);
// no air-blocked areas.
if (_atmosphere.IsTileSpace(grid, gridTransform.MapUid, tile, mapGridComp: gridComp) ||
_atmosphere.IsTileAirBlocked(grid, tile, mapGridComp: gridComp))
{
continue;
}
// don't spawn inside of solid objects
var physQuery = GetEntityQuery<PhysicsComponent>();
var valid = true;
foreach (var ent in gridComp.GetAnchoredEntities(tile))
{
if (!physQuery.TryGetComponent(ent, out var body))
continue;
if (body.BodyType != BodyType.Static ||
!body.Hard ||
(body.CollisionLayer & (int) CollisionGroup.LargeMobMask) == 0)
continue;
valid = false;
break;
}
if (!valid)
continue;
targetCoords = gridComp.GridTileToLocal(tile);
break;
}
_xform.SetCoordinates(args.User, targetCoords);
_xform.SetCoordinates(args.Target.Value, targetCoords);
UpdateUsesCount(torch, args.User, component);
}
private void UpdateUsesCount(EntityUid torch, EntityUid? user, TorchCultistsProviderComponent component)
{
component.ItemSelected = null;
component.NextUse = _timing.CurTime + component.Cooldown;
component.UsesLeft--;
if (user.HasValue)
{
_popup.PopupEntity(Loc.GetString("cult-torch-item-send"), user.Value);
}
if (component.UsesLeft <= 0)
{
component.Active = false;
UpdateAppearance(torch, component);
if (!TryComp<PointLightComponent>(torch, out var light))
return;
_pointLight.SetEnabled(torch, false, light);
}
}
}

View File

@@ -0,0 +1,160 @@
using System.Threading;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Events;
using Content.Shared.Maps;
using Content.Shared.Physics;
using Content.Shared.Popups;
using Content.Shared.Pulling.Components;
using Content.Shared.White.Cult;
using Content.Shared.White.Cult.Items;
using Robust.Server.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Timing;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.White.Cult.Items.Systems;
public sealed class VoidTeleportSystem : EntitySystem
{
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly TurfSystem _turf = default!;
[Dependency] private readonly SharedTransformSystem _xform = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VoidTeleportComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<VoidTeleportComponent, ComponentInit>(OnInit);
}
private void OnInit(EntityUid uid, VoidTeleportComponent component, ComponentInit args)
{
UpdateAppearance(uid, component);
}
private void OnUseInHand(EntityUid uid, VoidTeleportComponent component, UseInHandEvent args)
{
if (!HasComp<CultistComponent>(args.User))
{
_hands.TryDrop(args.User);
_popup.PopupEntity(Loc.GetString("void-teleport-not-cultist"), args.User, args.User);
return;
}
if (!component.Active || component.UsesLeft <= 0)
{
_popup.PopupEntity(Loc.GetString("void-teleport-drained"), args.User, args.User);
return;
}
if (component.NextUse > _timing.CurTime)
{
_popup.PopupEntity(Loc.GetString("void-teleport-cooldown"), args.User, args.User);
return;
}
if (!TryComp<TransformComponent>(args.User, out var transform))
return;
var oldCoords = transform.Coordinates;
EntityCoordinates coords = default;
var attempts = 10;
//Repeat until proper place for tp is found
while (attempts <= 10)
{
attempts--;
//Get coords to where tp
var random = new Random().Next(component.MinRange, component.MaxRange);
var offset = transform.LocalRotation.ToWorldVec().Normalized();
var direction = transform.LocalRotation.GetDir().ToVec();
var newOffset = offset + direction * random;
coords = transform.Coordinates.Offset(newOffset).SnapToGrid(EntityManager);
var tile = coords.GetTileRef();
//Check for walls
if (tile != null && _turf.IsTileBlocked(tile.Value, CollisionGroup.AllMask))
continue;
break;
}
CreatePulse(uid, component);
_xform.SetCoordinates(args.User, coords);
transform.AttachToGridOrMap();
var pulled = GetPulledEntity(args.User);
if (pulled != null)
{
_xform.SetCoordinates(pulled.Value, coords);
if (TryComp<TransformComponent>(pulled.Value, out var pulledTransform))
pulledTransform.AttachToGridOrMap();
}
//Play tp sound
_audio.PlayPvs(component.TeleportInSound, coords);
_audio.PlayPvs(component.TeleportOutSound,oldCoords);
//Create tp effect
_entMan.SpawnEntity(component.TeleportInEffect, coords);
_entMan.SpawnEntity(component.TeleportOutEffect, oldCoords);
component.UsesLeft--;
component.NextUse = _timing.CurTime + component.Cooldown;
}
private void UpdateAppearance(EntityUid uid, VoidTeleportComponent comp)
{
AppearanceComponent? appearance = null;
if (!Resolve(uid, ref appearance, false))
return;
_appearance.SetData(uid, VeilVisuals.Activated, comp.Active, appearance);
}
private EntityUid? GetPulledEntity(EntityUid user)
{
EntityUid? pulled = null;
if (TryComp<SharedPullerComponent>(user, out var puller))
pulled = puller.Pulling;
return pulled;
}
private void CreatePulse(EntityUid uid, VoidTeleportComponent component)
{
if (TryComp<PointLightComponent>(uid, out var light))
light.Energy = 5f;
Timer.Spawn(component.TimerDelay, () => TurnOffPulse(uid, component), component.Token.Token);
}
private void TurnOffPulse(EntityUid uid ,VoidTeleportComponent comp)
{
if (!TryComp<PointLightComponent>(uid, out var light))
return;
light.Energy = 1f;
comp.Token = new CancellationTokenSource();
if (comp.UsesLeft <= 0)
{
comp.Active = false;
UpdateAppearance(uid, comp);
light.Enabled = false;
}
}
}

View File

@@ -0,0 +1,9 @@
using Content.Shared.White.Cult.Pentagram;
using Robust.Shared.GameStates;
namespace Content.Server.White.Cult;
[NetworkedComponent, RegisterComponent]
public sealed partial class PentagramComponent : SharedPentagramComponent
{
}

View File

@@ -0,0 +1,264 @@
using System.Linq;
using System.Numerics;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Maps;
using Content.Shared.Damage;
using Content.Shared.Doors.Components;
using Content.Shared.Interaction;
using Content.Shared.Maps;
using Content.Shared.Mobs.Systems;
using Content.Shared.Physics;
using Content.Shared.Popups;
using Content.Shared.Tag;
using Content.Shared.White.Cult;
using Content.Shared.White.Cult.Pylon;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Timing;
namespace Content.Server.White.Cult.Pylon;
public sealed class PylonSystem : EntitySystem
{
[Dependency] private readonly DamageableSystem _damageSystem = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefinition = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly TileSystem _tile = default!;
[Dependency] private readonly BloodstreamSystem _blood = default!;
[Dependency] private readonly TurfSystem _turf = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SharedPylonComponent, InteractHandEvent>(OnInteract);
SubscribeLocalEvent<SharedPylonComponent, ComponentInit>(OnInit);
}
private void OnInit(EntityUid uid, SharedPylonComponent component, ComponentInit args)
{
UpdateAppearance(uid, component);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var pylonsQuery = EntityQuery<SharedPylonComponent>();
foreach (var comp in pylonsQuery)
{
if (comp.NextTileConvert == TimeSpan.Zero)
comp.NextTileConvert = _timing.CurTime + TimeSpan.FromSeconds(comp.TileConvertCooldown);
if (comp.NextHealTime == TimeSpan.Zero)
comp.NextHealTime = _timing.CurTime + TimeSpan.FromSeconds(comp.HealingAuraCooldown);
if (_timing.CurTime >= comp.NextHealTime)
{
comp.NextHealTime = _timing.CurTime + TimeSpan.FromSeconds(comp.HealingAuraCooldown);
if (comp.Activated)
HealPlayersInRange(comp);
}
if (_timing.CurTime >= comp.NextTileConvert)
{
comp.NextTileConvert = _timing.CurTime + TimeSpan.FromSeconds(comp.TileConvertCooldown);
if (comp.Activated)
ConvertNearbyTiles(comp);
}
}
}
private void ConvertNearbyTiles(SharedPylonComponent comp)
{
var tilesConverted = 0;
var random = new Random().Next(1, 3);
var uid = comp.Owner;
var gridUid = Transform(uid).GridUid;
var pylonPos = Transform(uid).Coordinates;
if (!_mapManager.TryGetGrid(gridUid, out var grid))
return;
var radius = comp.TileConvertRange;
var tilesRefs = grid.GetLocalTilesIntersecting(new Box2(pylonPos.Position + new Vector2(-radius, -radius),
pylonPos.Position + new Vector2(radius, radius)));
var tiles = ShuffleTiles(tilesRefs);
if (comp.ConvertEverything)
ConvertEverything(comp, tiles);
var cultTileDef = (ContentTileDefinition) _tileDefinition[$"{comp.TileId}"];
var cultTile = new Tile(cultTileDef.TileId);
foreach (var tile in tiles)
{
if (tilesConverted >= random)
return;
var tilePos = _turf.GetTileCenter(tile);
if (pylonPos.InRange(EntityManager, tilePos, comp.TileConvertRange))
{
if (tile.Tile.TypeId == cultTile.TypeId)
continue;
_tile.ReplaceTile(tile, cultTileDef);
_entMan.SpawnEntity(comp.TileConvertEffect, tilePos);
_audio.PlayPvs(comp.ConvertTileSound, tilePos, AudioParams.Default.WithVolume(-5));
tilesConverted++;
}
}
}
private void ConvertEverything(SharedPylonComponent comp, IEnumerable<TileRef> tiles)
{
foreach (var tile in tiles)
{
if (!_turf.IsTileBlocked(tile, CollisionGroup.WallLayer)
|| !_turf.IsTileBlocked(tile, CollisionGroup.AirlockLayer))
continue;
var posss = _turf.GetTileCenter(tile);
foreach (var entity in _lookup.GetEntitiesIntersecting(posss))
{
if (TryComp<TagComponent>(entity, out var tag)
&& tag.Tags.Contains("Wall")
&& MetaData(entity).EntityPrototype?.ID != comp.WallId)
{
_entMan.SpawnEntity(comp.WallId, Transform(entity).Coordinates);
_entMan.SpawnEntity(comp.WallConvertEffect, Transform(entity).Coordinates);
_entMan.DeleteEntity(entity);
_audio.PlayPvs(comp.ConvertTileSound, posss, AudioParams.Default.WithVolume(-10));
return;
}
if (HasComp<AirlockComponent>(entity) && MetaData(entity).EntityPrototype?.ID != comp.AirlockId)
{
_entMan.SpawnEntity(comp.AirlockId, Transform(entity).Coordinates);
_entMan.SpawnEntity(comp.AirlockConvertEffect, Transform(entity).Coordinates);
_entMan.DeleteEntity(entity);
_audio.PlayPvs(comp.ConvertTileSound, posss, AudioParams.Default.WithVolume(-10));
return;
}
}
}
}
private void HealPlayersInRange(SharedPylonComponent comp)
{
foreach (var player in _playerManager.Sessions)
{
if (player.AttachedEntity is not { Valid: true } playerEntity)
continue;
if (!EntityManager.TryGetComponent<CultistComponent>(playerEntity, out _))
continue;
if (_mobStateSystem.IsDead(playerEntity))
continue;
var playerDamageComp = EntityManager.TryGetComponent<DamageableComponent>(playerEntity, out var damageComp)
? damageComp
: null;
if (playerDamageComp == null || playerDamageComp.Damage.Total == 0)
continue;
var uid = comp.Owner;
var pylonXForm = Transform(uid);
var playerXForm = Transform(playerEntity);
if (pylonXForm.Coordinates.InRange(EntityManager, playerXForm.Coordinates, comp.HealingAuraRange))
{
var damage = comp.HealingAuraDamage;
_damageSystem.TryChangeDamage(playerEntity, damage, true);
if (!TryComp<BloodstreamComponent>(playerEntity, out var bloodstream))
continue;
if (bloodstream.IsBleeding)
{
_blood.TryModifyBleedAmount(playerEntity, -comp.BleedReductionAmount, bloodstream);
}
if (_blood.GetBloodLevelPercentage(playerEntity, bloodstream) < bloodstream.BloodMaxVolume)
{
_blood.TryModifyBloodLevel(playerEntity, comp.BloodRefreshAmount, bloodstream);
}
}
}
}
private void OnInteract(EntityUid uid, SharedPylonComponent comp, InteractHandEvent args)
{
var user = args.User;
var pylon = args.Target;
if (HasComp<CultistComponent>(user))
{
comp.Activated = !comp.Activated;
UpdateAppearance(uid, comp);
if (!TryComp<PointLightComponent>(uid, out var light))
return;
light.Enabled = comp.Activated;
var toggleMsg = Loc.GetString(comp.Activated ? "pylon-toggle-on" : "pylon-toggle-off");
_popupSystem.PopupEntity(toggleMsg, uid);
return;
}
var damage = comp.BurnDamageOnInteract;
var burnMsg = Loc.GetString("powered-light-component-burn-hand");
_audio.Play(comp.BurnHandSound, Filter.Pvs(pylon), pylon, true);
_popupSystem.PopupEntity(burnMsg, pylon, user);
_damageSystem.TryChangeDamage(user, damage, true);
}
private IEnumerable<TileRef> ShuffleTiles(IEnumerable<TileRef> collection)
{
var random = new Random();
var shuffledList = collection.ToList();
var n = shuffledList.Count;
while (n > 1)
{
n--;
var k = random.Next(n + 1);
(shuffledList[k], shuffledList[n]) = (shuffledList[n], shuffledList[k]);
}
return shuffledList;
}
private void UpdateAppearance(EntityUid uid, SharedPylonComponent comp)
{
AppearanceComponent? appearance = null;
if (!Resolve(uid, ref appearance, false))
return;
_appearance.SetData(uid, PylonVisuals.Activated, comp.Activated, appearance);
}
}

View File

@@ -0,0 +1,7 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultBarrierComponent : Component
{
[DataField("activated")] public bool Activated;
}

View File

@@ -0,0 +1,11 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneApocalypseComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("rangeTarget")]
public float RangeTarget = 1.2f;
[ViewVariables(VVAccess.ReadWrite), DataField("summonMinCount")]
public uint SummonMinCount = 10;
}

View File

@@ -0,0 +1,4 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneBarrierComponent : Component { }

View File

@@ -0,0 +1,17 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneBaseComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("invokersMinCount")]
public uint InvokersMinCount = 1;
[ViewVariables(VVAccess.ReadWrite), DataField("gatherInvokers")]
public bool GatherInvokers = true;
[ViewVariables(VVAccess.ReadWrite), DataField("cultistGatheringRange")]
public float CultistGatheringRange = 0.7f;
[ViewVariables(VVAccess.ReadWrite), DataField("invokePhrase")]
public string InvokePhrase = "";
}

View File

@@ -0,0 +1,26 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneBloodBoilComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("summonMinCount")]
public uint SummonMinCount = 3;
[DataField("projectilePrototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>)), ViewVariables(VVAccess.ReadWrite)]
public string ProjectilePrototype = default!;
[DataField("projectileSpeed"), ViewVariables(VVAccess.ReadWrite)]
public float ProjectileSpeed = 20f;
[DataField("minProjectiles"), ViewVariables(VVAccess.ReadWrite)]
public int MinProjectiles = 3;
[DataField("maxProjectiles"), ViewVariables(VVAccess.ReadWrite)]
public int MaxProjectiles = 9;
[DataField("projectileRange"), ViewVariables(VVAccess.ReadWrite)]
public float ProjectileRange = 50f;
}

View File

@@ -0,0 +1,8 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneBuffComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("rangeTarget")]
public float RangeTarget = 0.3f;
}

View File

@@ -0,0 +1,17 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneOfferingComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("sacrificeDeadMinCount")]
public uint SacrificeDeadMinCount = 1;
[ViewVariables(VVAccess.ReadWrite), DataField("convertMinCount")]
public uint ConvertMinCount = 2;
[ViewVariables(VVAccess.ReadWrite), DataField("sacrificeMinCount")]
public uint SacrificeMinCount = 3;
[ViewVariables(VVAccess.ReadWrite), DataField("rangeTarget")]
public float RangeTarget = 0.3f;
}

View File

@@ -0,0 +1,10 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneReviveComponent : Component
{
public static uint ChargesLeft = 3;
[ViewVariables(VVAccess.ReadWrite), DataField("rangeTarget")]
public float RangeTarget = 0.3f;
}

View File

@@ -0,0 +1,8 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneSummoningComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("summonMinCount")]
public uint SummonMinCount = 2;
}

View File

@@ -0,0 +1,8 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneSummoningProviderComponent : Component
{
[ViewVariables(VVAccess.ReadOnly)]
public EntityUid? BaseRune;
}

View File

@@ -0,0 +1,11 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultRuneTeleportComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("rangeTarget")]
public float RangeTarget = 0.3f;
[ViewVariables(VVAccess.ReadWrite), DataField("label")]
public string? Label;
}

View File

@@ -0,0 +1,11 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class CultTeleportRuneProviderComponent : Component
{
[ViewVariables(VVAccess.ReadOnly)]
public EntityUid? BaseRune;
[ViewVariables(VVAccess.ReadOnly)]
public List<EntityUid>? Targets;
}

View File

@@ -0,0 +1,15 @@
using Content.Server.UserInterface;
using Content.Shared.White.Cult.UI;
using Robust.Server.GameObjects;
namespace Content.Shared.White.Cult;
[RegisterComponent]
public sealed partial class RuneDrawerProviderComponent : Component
{
[ViewVariables]
public BoundUserInterface? UserInterface => Owner.GetUIOrNull(ListViewSelectorUiKey.Key);
[DataField("runePrototypes")]
public List<string> RunePrototypes = new();
}

View File

@@ -0,0 +1,7 @@
namespace Content.Server.White.Cult.Runes.Comps;
[RegisterComponent]
public sealed partial class SoulShardComponent : Component
{
}

View File

@@ -0,0 +1,244 @@
using System.Linq;
using Content.Server.Body.Components;
using Content.Server.Chemistry.EntitySystems;
using Content.Server.Emp;
using Content.Server.EUI;
using Content.Server.White.Cult.UI;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Content.Shared.Fluids.Components;
using Content.Shared.Inventory;
using Content.Shared.Stacks;
using Content.Shared.StatusEffect;
using Content.Shared.Stunnable;
using Content.Shared.White.Cult;
using Content.Shared.White.Cult.Actions;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
namespace Content.Server.White.Cult.Runes.Systems;
public partial class CultSystem
{
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
[Dependency] private readonly SolutionContainerSystem _solutionSystem = default!;
[Dependency] private readonly EmpSystem _empSystem = default!;
[Dependency] private readonly EuiManager _euiManager = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
public void InitializeActions()
{
SubscribeLocalEvent<CultistComponent, CultTwistedConstructionActionEvent>(OnTwistedConstructionAction);
SubscribeLocalEvent<CultistComponent, CultSummonDaggerActionEvent>(OnSummonDaggerAction);
SubscribeLocalEvent<CultistComponent, CultShadowShacklesTargetActionEvent>(OnShadowShackles);
SubscribeLocalEvent<CultistComponent, CultElectromagneticPulseTargetActionEvent>(OnElectromagneticPulse);
SubscribeLocalEvent<CultistComponent, CultSummonCombatEquipmentTargetActionEvent>(OnSummonCombatEquipment);
SubscribeLocalEvent<CultistComponent, CultConcealPresenceWorldActionEvent>(OnConcealPresence);
SubscribeLocalEvent<CultistComponent, CultBloodRitesInstantActionEvent>(OnBloodRites);
SubscribeLocalEvent<CultistComponent, CultTeleportTargetActionEvent>(OnTeleport);
SubscribeLocalEvent<CultistComponent, CultStunTargetActionEvent>(OnStunTarget);
}
private void OnStunTarget(EntityUid uid, CultistComponent component, CultStunTargetActionEvent args)
{
if (args.Target == uid || !HasComp<StatusEffectsComponent>(args.Target))
return;
if (_stunSystem.TryStun(args.Target, TimeSpan.FromSeconds(6), true))
{
args.Handled = true;
}
}
private void OnTeleport(EntityUid uid, CultistComponent component, CultTeleportTargetActionEvent args)
{
if (!TryComp<BloodstreamComponent>(args.Performer, out _) || !TryComp<ActorComponent>(uid, out var actor))
return;
var eui = new TeleportSpellEui(args.Performer, args.Target);
_euiManager.OpenEui(eui, actor.PlayerSession);
eui.StateDirty();
args.Handled = true;
}
private void OnBloodRites(EntityUid uid, CultistComponent component, CultBloodRitesInstantActionEvent args)
{
if (!TryComp<BloodstreamComponent>(args.Performer, out var bloodstreamComponent))
return;
var bruteDamageGroup = _prototypeManager.Index<DamageGroupPrototype>("Brute");
var burnDamageGroup = _prototypeManager.Index<DamageGroupPrototype>("Burn");
var xform = Transform(uid);
var entitiesInRange = _lookup.GetEntitiesInRange(xform.MapPosition, 1.5f);
FixedPoint2 totalBloodAmount = 0f;
var breakLoop = false;
foreach (var solutionEntity in entitiesInRange.ToList())
{
if (breakLoop)
break;
if (!TryComp<PuddleComponent>(solutionEntity, out var puddleComponent))
continue;
if (!_solutionSystem.TryGetSolution(solutionEntity, puddleComponent.SolutionName, out var solution))
continue;
foreach (var solutionContent in solution.Contents.ToList())
{
if (solutionContent.Reagent.Prototype != "Blood")
continue;
totalBloodAmount += solutionContent.Quantity;
_bloodstreamSystem.TryModifyBloodLevel(uid, solutionContent.Quantity / 6f);
_solutionSystem.RemoveReagent(solutionEntity, solution, "Blood", FixedPoint2.MaxValue);
if (GetMissingBloodValue(bloodstreamComponent) == 0)
{
breakLoop = true;
}
}
}
if (totalBloodAmount == 0f)
{
return;
}
_audio.PlayPvs("/Audio/White/Cult/enter_blood.ogg", uid, AudioParams.Default);
_damageableSystem.TryChangeDamage(uid, new DamageSpecifier(bruteDamageGroup, -20));
_damageableSystem.TryChangeDamage(uid, new DamageSpecifier(burnDamageGroup, -20));
args.Handled = true;
}
private static FixedPoint2 GetMissingBloodValue(BloodstreamComponent bloodstreamComponent)
{
return bloodstreamComponent.BloodMaxVolume - bloodstreamComponent.BloodSolution.Volume;
}
private void OnConcealPresence(EntityUid uid, CultistComponent component, CultConcealPresenceWorldActionEvent args)
{
if (!TryComp<BloodstreamComponent>(args.Performer, out _))
return;
}
private void OnSummonCombatEquipment(
EntityUid uid,
CultistComponent component,
CultSummonCombatEquipmentTargetActionEvent args)
{
if (!TryComp<BloodstreamComponent>(args.Performer, out _))
return;
_bloodstreamSystem.TryModifyBloodLevel(uid, -20, createPuddle: false);
var coordinates = Transform(uid).Coordinates;
var helmet = Spawn("ClothingHeadHelmetCult", coordinates);
var armor = Spawn("ClothingOuterArmorCult", coordinates);
var shoes = Spawn("ClothingShoesCult", coordinates);
var blade = Spawn("EldritchBlade", coordinates);
var bola = Spawn("CultBola", coordinates);
_inventorySystem.TryUnequip(uid, "head");
_inventorySystem.TryUnequip(uid, "outerClothing");
_inventorySystem.TryUnequip(uid, "shoes");
_inventorySystem.TryEquip(uid, helmet, "head", force: true);
_inventorySystem.TryEquip(uid, armor, "outerClothing", force: true);
_inventorySystem.TryEquip(uid, shoes, "shoes", force: true);
_handsSystem.PickupOrDrop(uid, blade);
_handsSystem.PickupOrDrop(uid, bola);
args.Handled = true;
}
private void OnElectromagneticPulse(
EntityUid uid,
CultistComponent component,
CultElectromagneticPulseTargetActionEvent args)
{
if (!TryComp<BloodstreamComponent>(args.Performer, out _))
return;
_bloodstreamSystem.TryModifyBloodLevel(uid, -20, createPuddle: false);
var xform = Transform(uid);
_empSystem.EmpPulse(xform.MapPosition, 10, 100000, 10f);
_bloodstreamSystem.TryModifyBloodLevel(uid, -20, createPuddle: false);
args.Handled = true;
}
private void OnShadowShackles(EntityUid uid, CultistComponent component, CultShadowShacklesTargetActionEvent args)
{
if (!TryComp<BloodstreamComponent>(args.Performer, out _))
return;
_bloodstreamSystem.TryModifyBloodLevel(uid, -20, createPuddle: false);
var cuffs = Spawn("CultistCuffs", Transform(uid).Coordinates);
_handsSystem.TryPickupAnyHand(uid, cuffs);
args.Handled = true;
}
private void OnTwistedConstructionAction(
EntityUid uid,
CultistComponent component,
CultTwistedConstructionActionEvent args)
{
if (args.Handled)
return;
if (!TryComp<BloodstreamComponent>(args.Performer, out var bloodstreamComponent))
return;
if (!_entityManager.TryGetComponent<StackComponent>(args.Target, out var stack))
return;
if (stack.StackTypeId != SteelPrototypeId)
return;
var transform = Transform(args.Target).Coordinates;
var count = stack.Count;
_entityManager.DeleteEntity(args.Target);
var material = _entityManager.SpawnEntity(RunicMetalPrototypeId, transform);
_bloodstreamSystem.TryModifyBloodLevel(args.Performer, -15, bloodstreamComponent, false);
if (!_entityManager.TryGetComponent<StackComponent>(material, out var stackNew))
return;
stackNew.Count = count;
_popupSystem.PopupEntity(Loc.GetString("Конвертируем сталь в руиник металл!"), args.Performer, args.Performer);
args.Handled = true;
}
private void OnSummonDaggerAction(EntityUid uid, CultistComponent component, CultSummonDaggerActionEvent args)
{
if (args.Handled)
return;
if (!TryComp<BloodstreamComponent>(args.Performer, out var bloodstreamComponent))
return;
var xform = Transform(args.Performer).Coordinates;
var dagger = _entityManager.SpawnEntity(RitualDaggerPrototypeId, xform);
_bloodstreamSystem.TryModifyBloodLevel(args.Performer, -30, bloodstreamComponent, false);
_handsSystem.TryPickupAnyHand(args.Performer, dagger);
args.Handled = true;
}
}

View File

@@ -0,0 +1,83 @@
using Content.Server.White.Cult.Runes.Comps;
using Content.Shared.Interaction;
using Content.Shared.Stealth.Components;
using Content.Shared.White.Cult;
using Robust.Server.GameObjects;
namespace Content.Server.White.Cult.Runes.Systems;
public partial class CultSystem
{
public void InitializeBarrierSystem()
{
SubscribeLocalEvent<CultBarrierComponent, ActivateInWorldEvent>(OnActivateBarrier);
SubscribeLocalEvent<CultBarrierComponent, InteractUsingEvent>(OnInteract);
}
[Dependency] private readonly PhysicsSystem _physicsSystem = default!;
private void OnActivateBarrier(EntityUid uid, CultBarrierComponent component, ActivateInWorldEvent args)
{
if (!HasComp<CultistComponent>(args.User))
return;
if (component.Activated)
Deactivate(args.Target);
else
Activate(args.Target);
}
private void Activate(EntityUid barrier)
{
if (!TryComp<CultBarrierComponent>(barrier, out var barrierComponent))
return;
if (HasComp<StealthOnMoveComponent>(barrier))
RemComp<StealthOnMoveComponent>(barrier);
if (HasComp<StealthComponent>(barrier))
RemComp<StealthComponent>(barrier);
_physicsSystem.SetCanCollide(barrier, true);
barrierComponent.Activated = true;
}
private void Deactivate(EntityUid barrier)
{
if (!TryComp<CultBarrierComponent>(barrier, out var barrierComponent))
return;
EnsureComp<StealthComponent>(barrier);
EnsureComp<StealthOnMoveComponent>(barrier);
_physicsSystem.SetCanCollide(barrier, false);
barrierComponent.Activated = false;
}
private void OnInteract(EntityUid uid, CultBarrierComponent component, InteractUsingEvent args)
{
var entityPrototype = _entityManager.GetComponent<MetaDataComponent>(args.Used).EntityPrototype;
if (entityPrototype == null)
return;
var used = entityPrototype.ID;
var user = args.User;
var target = args.Target;
if (used != RitualDaggerPrototypeId)
return;
if (!HasComp<CultistComponent>(user))
return;
_popupSystem.PopupEntity("Вы уничтожаете барьер", user, user);
_entityManager.DeleteEntity(target);
}
}

View File

@@ -0,0 +1,113 @@
using System.Linq;
using System.Numerics;
using Content.Server.White.Cult.GameRule;
using Content.Server.White.Cult.Runes.Comps;
using Content.Shared.Alert;
using Content.Shared.Maps;
using Content.Shared.White.Cult;
using Content.Shared.White.Cult.Components;
using Robust.Shared.Map;
namespace Content.Server.White.Cult.Runes.Systems;
public partial class CultSystem
{
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefinition = default!;
public void InitializeBuffSystem()
{
SubscribeLocalEvent<CultBuffComponent, ComponentAdd>(OnAdd);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdateBuffTimers(frameTime);
AnyCultistNearTile();
RemoveExpiredBuffs();
}
private void AnyCultistNearTile()
{
var cultists = EntityQuery<CultistComponent>();
foreach (var cultist in cultists)
{
var uid = cultist.Owner;
if (HasComp<CultBuffComponent>(uid))
continue;
if (!AnyCultTilesNearby(uid))
continue;
var comp = EnsureComp<CultBuffComponent>(uid);
comp.BuffTime = CultBuffComponent.CultTileBuffTime;
}
}
private void OnAdd(EntityUid uid, CultBuffComponent comp, ComponentAdd args)
{
_alertsSystem.ShowAlert(uid, AlertType.CultBuffed);
}
private void UpdateBuffTimers(float frameTime)
{
var buffs = EntityQuery<CultBuffComponent>();
foreach (var buff in buffs)
{
var uid = buff.Owner;
var remainingTime = buff.BuffTime;
remainingTime -= TimeSpan.FromSeconds(frameTime);
if (TryComp<CultistComponent>(uid, out var cultist))
{
if (remainingTime < CultBuffComponent.CultTileBuffTime && AnyCultTilesNearby(uid))
remainingTime = CultBuffComponent.CultTileBuffTime;
}
buff.BuffTime = remainingTime;
}
}
private bool AnyCultTilesNearby(EntityUid uid)
{
var localpos = Transform(uid).Coordinates.Position;
if (!TryComp<CultistComponent>(uid, out var cultist))
return false;
var radius = CultBuffComponent.NearbyTilesBuffRadius;
if (!_mapManager.TryGetGrid(Transform(uid).GridUid, out var grid))
return false;
var tilesRefs = grid.GetLocalTilesIntersecting(new Box2(localpos + new Vector2(-radius, -radius), localpos + new Vector2(radius, radius)));
var cultTileDef = (ContentTileDefinition) _tileDefinition[$"{CultRuleComponent.CultFloor}"];
var cultTile = new Tile(cultTileDef.TileId);
return tilesRefs.Any(tileRef => tileRef.Tile.TypeId == cultTile.TypeId);
}
private void RemoveExpiredBuffs()
{
var buffs = EntityQuery<CultBuffComponent>();
foreach (var buff in buffs)
{
var uid = buff.Owner;
var remainingTime = buff.BuffTime;
if (remainingTime <= TimeSpan.Zero)
{
RemComp<CultBuffComponent>(uid);
_alertsSystem.ClearAlert(uid, AlertType.CultBuffed);
}
}
}
}

View File

@@ -0,0 +1,60 @@
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.White.Cult.Runes.Components;
using Content.Shared.White.Cult.UI;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
namespace Content.Server.White.Cult.Runes.Systems;
public partial class CultSystem
{
[Dependency] private readonly ItemSlotsSystem _slotsSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public void InitializeConstructs()
{
SubscribeLocalEvent<ConstructShellComponent, ContainerIsInsertingAttemptEvent>(OnShardInsertAttempt);
SubscribeLocalEvent<ConstructShellComponent, ComponentInit>(OnShellInit);
SubscribeLocalEvent<ConstructShellComponent, ComponentRemove>(OnShellRemove);
SubscribeLocalEvent<ConstructShellComponent, ConstructFormSelectedEvent>(OnShellSelected);
}
private void OnShellSelected(EntityUid uid, ConstructShellComponent component, ConstructFormSelectedEvent args)
{
var construct = Spawn(args.SelectedForm, Transform(args.Entity).MapPosition);
var mind = Comp<MindContainerComponent>(args.Session.AttachedEntity!.Value);
if(!mind.HasMind)
return;
_mindSystem.TransferTo(mind.Mind.Value, construct);
Del(args.Entity);
}
private void OnShellInit(EntityUid uid, ConstructShellComponent component, ComponentInit args)
{
_slotsSystem.AddItemSlot(uid, component.ShardSlotId, component.ShardSlot);
}
private void OnShellRemove(EntityUid uid, ConstructShellComponent component, ComponentRemove args)
{
_slotsSystem.RemoveItemSlot(uid, component.ShardSlot);
}
private void OnShardInsertAttempt(EntityUid uid, ConstructShellComponent component, ContainerIsInsertingAttemptEvent args)
{
if (!TryComp<MindContainerComponent>(args.EntityUid, out var mindContainer) || !mindContainer.HasMind || !TryComp<ActorComponent>(args.EntityUid, out var actor))
{
_popupSystem.PopupEntity("Нет души", uid);
args.Cancel();
return;
}
_slotsSystem.SetLock(uid, component.ShardSlotId, true);
_ui.TryOpen(uid, SelectConstructUi.Key, actor.PlayerSession);
}
}

View File

@@ -0,0 +1,203 @@
using System.Linq;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Maps;
using Content.Server.Popups;
using Content.Server.White.Cult.GameRule;
using Content.Server.White.IncorporealSystem;
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.Interaction.Events;
using Content.Shared.Maps;
using Content.Shared.Mobs.Components;
using Content.Shared.Physics;
using Content.Shared.StatusEffect;
using Content.Shared.White.Cult;
namespace Content.Server.White.Cult.Runes.Systems;
public partial class CultSystem
{
[Dependency] private readonly TileSystem _tileSystem = default!;
[Dependency] private readonly EntityLookupSystem _lookupSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly TurfSystem _turf = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
public void InitializeConstructsAbilities()
{
SubscribeLocalEvent<ArtificerCreateSoulStoneActionEvent>(OnArtificerCreateSoulStone);
SubscribeLocalEvent<ArtificerCreateConstructShellActionEvent>(OnArtificerCreateConstructShell);
SubscribeLocalEvent<ArtificerConvertCultistFloorActionEvent>(OnArtificerConvertCultistFloor);
SubscribeLocalEvent<ArtificerCreateCultistWallActionEvent>(OnArtificerCreateCultistWall);
SubscribeLocalEvent<ArtificerCreateCultistAirlockActionEvent>(OnArtificerCreateCultistAirlock);
SubscribeLocalEvent<WraithPhaseActionEvent>(OnWraithPhase);
SubscribeLocalEvent<IncorporealComponent, AttackAttemptEvent>(OnAttackAttempt);
SubscribeLocalEvent<JuggernautCreateWallActionEvent>(OnJuggernautCreateWall);
SubscribeLocalEvent<ConstructComponent, ComponentInit>(OnConstructInit);
SubscribeLocalEvent<ConstructComponent, ComponentRemove>(OnConstructComponentRemoved);
}
private void OnConstructInit(EntityUid uid, ConstructComponent component, ComponentInit args)
{
foreach (var action in component.Actions)
{
var actionPrototype = _prototypeManager.Index<InstantActionPrototype>(action);
_actionsSystem.AddAction(uid, new InstantAction(actionPrototype), uid);
}
var query = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var ruleEnt, out var cultRuleComponent, out _))
{
if (!_gameTicker.IsGameRuleAdded(ruleEnt))
continue;
cultRuleComponent.Constructs.Add(component);
}
}
private void OnConstructComponentRemoved(EntityUid uid, ConstructComponent component, ComponentRemove args)
{
var query = EntityQueryEnumerator<CultRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var ruleEnt, out var cultRuleComponent, out _))
{
if (!_gameTicker.IsGameRuleAdded(ruleEnt))
continue;
cultRuleComponent.Constructs.Remove(component);
}
}
private void OnArtificerCreateSoulStone(ArtificerCreateSoulStoneActionEvent ev)
{
var transform = Transform(ev.Performer);
Spawn(ev.SoulStonePrototypeId, transform.Coordinates);
ev.Handled = true;
}
private void OnArtificerCreateConstructShell(ArtificerCreateConstructShellActionEvent ev)
{
var transform = Transform(ev.Performer);
Spawn(ev.ShellPrototypeId, transform.Coordinates);
ev.Handled = true;
}
private void OnArtificerConvertCultistFloor(ArtificerConvertCultistFloorActionEvent ev)
{
var transform = Transform(ev.Performer);
var gridUid = transform.GridUid;
if (!gridUid.HasValue)
{
_popupSystem.PopupEntity("Нельзя строить в космосе...", ev.Performer, ev.Performer);
return;
}
var tileRef = transform.Coordinates.GetTileRef();
if (!tileRef.HasValue)
{
_popupSystem.PopupEntity("Нельзя строить в космосе...", ev.Performer, ev.Performer);
return;
}
var cultistTileDefinition = (ContentTileDefinition) _tileDefinition[ev.FloorTileId];
_tileSystem.ReplaceTile(tileRef.Value, cultistTileDefinition);
Spawn("CultTileSpawnEffect", transform.Coordinates);
ev.Handled = true;
}
private void OnArtificerCreateCultistWall(ArtificerCreateCultistWallActionEvent ev)
{
if (!TrySpawnWall(ev.Performer, ev.WallPrototypeId))
{
return;
}
ev.Handled = true;
}
private void OnArtificerCreateCultistAirlock(ArtificerCreateCultistAirlockActionEvent ev)
{
if (!TrySpawnWall(ev.Performer, ev.AirlockPrototypeId))
{
return;
}
ev.Handled = true;
}
private void OnWraithPhase(WraithPhaseActionEvent ev)
{
if (_statusEffectsSystem.HasStatusEffect(ev.Performer, ev.StatusEffectId))
{
_popupSystem.PopupEntity("Вы уже в потустороннем мире", ev.Performer, ev.Performer);
return;
}
_statusEffectsSystem.TryAddStatusEffect<IncorporealComponent>(ev.Performer, ev.StatusEffectId,
TimeSpan.FromSeconds(ev.Duration), false);
ev.Handled = true;
}
private void OnAttackAttempt(EntityUid uid, IncorporealComponent component, AttackAttemptEvent args)
{
if (_statusEffectsSystem.HasStatusEffect(args.Uid, "Incorporeal"))
{
_statusEffectsSystem.TryRemoveStatusEffect(args.Uid, "Incorporeal");
}
}
private void OnJuggernautCreateWall(JuggernautCreateWallActionEvent ev)
{
if (!TrySpawnWall(ev.Performer, ev.WallPrototypeId))
{
return;
}
ev.Handled = true;
}
private bool TrySpawnWall(EntityUid performer, string wallPrototypeId)
{
var xform = Transform(performer);
var offsetValue = xform.LocalRotation.ToWorldVec().Normalized();
var coords = xform.Coordinates.Offset(offsetValue).SnapToGrid(EntityManager);
var tile = coords.GetTileRef();
if (tile == null)
return false;
// Check there are no walls there
if (_turf.IsTileBlocked(tile.Value, CollisionGroup.Impassable))
{
_popupSystem.PopupEntity(Loc.GetString("mime-invisible-wall-failed"), performer, performer);
return false;
}
// Check there are no mobs there
foreach (var entity in _lookupSystem.GetEntitiesIntersecting(tile.Value))
{
if (HasComp<MobStateComponent>(entity) && entity != performer)
{
_popupSystem.PopupEntity(Loc.GetString("mime-invisible-wall-failed"), performer, performer);
return false;
}
}
_popupSystem.PopupEntity(Loc.GetString("mime-invisible-wall-popup", ("mime", performer)), performer);
// Make sure we set the invisible wall to despawn properly
Spawn(wallPrototypeId, coords);
return true;
}
}

View File

@@ -0,0 +1,26 @@
using System.Threading;
using Content.Shared.White.Cult;
using Robust.Server.GameObjects;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.White.Cult.Runes.Systems;
public partial class CultSystem
{
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
private void InitializeNarsie()
{
SubscribeLocalEvent<NarsieComponent, ComponentInit>(OnNarsieComponentInit);
}
private void OnNarsieComponentInit(EntityUid uid, NarsieComponent component, ComponentInit args)
{
_appearanceSystem.SetData(uid, NarsieVisualState.VisualState, NarsieVisuals.Spawning);
Timer.Spawn(TimeSpan.FromSeconds(6), () =>
{
_appearanceSystem.SetData(uid, NarsieVisualState.VisualState, NarsieVisuals.Spawned);
});
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,69 @@
using Content.Server.Mind;
using Content.Server.Roles;
using Content.Server.White.Cult.Runes.Comps;
using Content.Shared.Humanoid;
using Content.Shared.Interaction;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Roles;
using Content.Shared.White.Cult;
using Content.Shared.White.Cult.Items;
namespace Content.Server.White.Cult.Runes.Systems;
public partial class CultSystem
{
[Dependency] private readonly SharedPointLightSystem _lightSystem = default!;
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly MetaDataSystem _metaDataSystem = default!;
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
public void InitializeSoulShard()
{
SubscribeLocalEvent<SoulShardComponent, AfterInteractEvent>(OnShardInteractUse);
SubscribeLocalEvent<SoulShardComponent, MindAddedMessage>(OnShardMindAdded);
SubscribeLocalEvent<SoulShardComponent, MindRemovedMessage>(OnShardMindRemoved);
}
private void OnShardInteractUse(EntityUid uid, SoulShardComponent component, AfterInteractEvent args)
{
var target = args.Target;
if (!HasComp<CultistComponent>(args.User)) return;
if (!TryComp<MobStateComponent>(target, out var state) || state.CurrentState != MobState.Dead) return;
if (!TryComp<MindContainerComponent>(target, out var mindComponent) || !mindComponent.Mind.HasValue || !TryComp<HumanoidAppearanceComponent>(target, out _)) return;
_mindSystem.TransferTo(mindComponent.Mind.Value, uid);
var targetName = MetaData(target.Value).EntityName;
_metaDataSystem.SetEntityName(uid, Loc.GetString("soul-shard-description", ("soul", targetName)));
_metaDataSystem.SetEntityDescription(uid, Loc.GetString("soul-shard-description", ("soul", targetName)));
}
private void OnShardMindAdded(EntityUid uid, SoulShardComponent component, MindAddedMessage args)
{
if (!TryComp<MindContainerComponent>(uid, out var mindContainer) || !mindContainer.HasMind)
{
return;
}
if (_roleSystem.MindHasRole<TraitorRoleComponent>(mindContainer.Mind.Value))
{
_roleSystem.MindRemoveRole<TraitorRoleComponent>(mindContainer.Mind.Value);
}
_appearanceSystem.SetData(uid, SoulShardVisualState.State, true);
_lightSystem.SetEnabled(uid, true);
}
private void OnShardMindRemoved(EntityUid uid, SoulShardComponent component, MindRemovedMessage args)
{
_appearanceSystem.SetData(uid, SoulShardVisualState.State, false);
_lightSystem.SetEnabled(uid, false);
}
}

View File

@@ -0,0 +1,34 @@
using Content.Shared.Interaction.Events;
using Content.Shared.White.Cult;
using Robust.Server.GameObjects;
using Robust.Server.Player;
namespace Content.Server.White.Cult.Structures;
public sealed class CultStructureCraftSystem : EntitySystem
{
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RunicMetalComponent, UseInHandEvent>(OnUseInHand);
}
private void OnUseInHand(EntityUid uid, RunicMetalComponent component, UseInHandEvent args)
{
if (!HasComp<CultistComponent>(args.User))
return;
if (!_playerManager.TryGetSessionByEntity(args.User, out var session) || session is not IPlayerSession playerSession)
return;
if (component.UserInterface != null)
{
_uiSystem.CloseUi(component.UserInterface, playerSession);
_uiSystem.OpenUi(component.UserInterface, playerSession);
}
}
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.Cult.Structures;
[RegisterComponent]
public sealed partial class RunicDoorComponent : Component
{
}

View File

@@ -0,0 +1,74 @@
using Content.Server.Doors.Systems;
using Content.Shared.Doors;
using Content.Shared.Humanoid;
using Content.Shared.Stunnable;
using Content.Shared.White.Cult;
using Robust.Shared.Physics.Systems;
namespace Content.Server.White.Cult.Structures;
public sealed class RunicDoorSystem : EntitySystem
{
[Dependency] private readonly DoorSystem _doorSystem = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RunicDoorComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
SubscribeLocalEvent<RunicDoorComponent, BeforeDoorClosedEvent>(OnBeforeDoorClosed);
}
private void OnBeforeDoorOpened(EntityUid uid, RunicDoorComponent component, BeforeDoorOpenedEvent args)
{
args.Uncancel();
if (!args.User.HasValue)
{
return;
}
if (!Process(uid, args.User.Value))
{
args.Cancel();
}
}
private void OnBeforeDoorClosed(EntityUid uid, RunicDoorComponent component, BeforeDoorClosedEvent args)
{
args.Uncancel();
if (!args.User.HasValue)
{
return;
}
if (!Process(uid, args.User.Value))
{
args.Cancel();
}
}
private bool Process(EntityUid airlock, EntityUid user)
{
if (HasComp<CultistComponent>(user) || HasComp<ConstructComponent>(user))
{
return true;
}
_doorSystem.Deny(airlock);
if (!HasComp<HumanoidAppearanceComponent>(user))
return false;
var direction = Transform(user).MapPosition.Position - Transform(airlock).MapPosition.Position;
var impulseVector = direction * 7000;
_physics.ApplyLinearImpulse(user, impulseVector);
_stunSystem.TryParalyze(user, TimeSpan.FromSeconds(3), true);
return false;
}
}

View File

@@ -0,0 +1,11 @@
namespace Content.Server.White.Cult.Structures;
[RegisterComponent]
public sealed partial class RunicGirderComponent : Component
{
[ViewVariables(VVAccess.ReadOnly)]
public string UsedItemID = "RitualDagger";
[ViewVariables(VVAccess.ReadOnly)]
public string DropItemID = "CultRunicMetal1";
}

View File

@@ -0,0 +1,30 @@
using Content.Shared.Interaction;
using Content.Shared.White.Cult;
namespace Content.Server.White.Cult.Structures;
public sealed class RunicGirderSystem : EntitySystem
{
[Dependency] private readonly EntityManager _entMan = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RunicGirderComponent, AfterInteractUsingEvent>(OnInteract);
}
private void OnInteract(EntityUid uid, RunicGirderComponent component, AfterInteractUsingEvent args)
{
if (!HasComp<CultistComponent>(args.User))
return;
if (MetaData(args.Used).EntityPrototype?.ID != component.UsedItemID)
return;
if (args.Target == null)
return;
var pos = Transform(args.Target.Value).Coordinates;
_entMan.DeleteEntity(args.Target.Value);
_entMan.SpawnEntity(component.DropItemID, pos);
}
}

View File

@@ -0,0 +1,15 @@
using Content.Server.UserInterface;
using Content.Shared.White.Cult.Structures;
using Robust.Server.GameObjects;
namespace Content.Server.White.Cult.Structures;
[RegisterComponent]
public sealed partial class RunicMetalComponent : Component
{
[ViewVariables(VVAccess.ReadOnly)]
public BoundUserInterface? UserInterface => Owner.GetUIOrNull(CultStructureCraftUiKey.Key);
[ViewVariables(VVAccess.ReadWrite), DataField("delay")]
public float Delay = 1;
}

View File

@@ -0,0 +1,27 @@
using Content.Server.UserInterface;
using Content.Shared.White.Cult;
using Content.Shared.White.Cult.UI;
using Robust.Server.GameObjects;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Server.White.Cult.TimedProduction;
[RegisterComponent]
public sealed partial class CultistFactoryComponent : Component
{
[ViewVariables(VVAccess.ReadOnly)]
[DataField("cooldown")]
public int Cooldown = 240;
[ViewVariables(VVAccess.ReadOnly)]
public TimeSpan? NextTimeUse;
[ViewVariables(VVAccess.ReadOnly)]
[DataField("products", customTypeSerializer: typeof(PrototypeIdListSerializer<CultistFactoryProductionPrototype>))]
public IReadOnlyCollection<string> Products = ArraySegment<string>.Empty;
public BoundUserInterface? UserInterface => Owner.GetUIOrNull(CultistAltarUiKey.Key);
[ViewVariables(VVAccess.ReadOnly)]
public bool Active = true;
}

View File

@@ -0,0 +1,196 @@
using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.White.Cult;
using Content.Shared.White.Cult.Structures;
using Content.Shared.White.Cult.UI;
using Robust.Server.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Server.White.Cult.TimedProduction;
public sealed class CultistFactorySystem : EntitySystem
{
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly EntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
private const string RitualDaggerPrototypeId = "RitualDagger";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CultistFactoryComponent, InteractHandEvent>(OnInteract);
SubscribeLocalEvent<CultistFactoryComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<CultistFactoryComponent, CultistFactoryItemSelectedMessage>(OnSelected);
SubscribeLocalEvent<CultistFactoryComponent, InteractUsingEvent>(TryToggleAnchor);
SubscribeLocalEvent<CultistFactoryComponent, CultAnchorDoAfterEvent>(OnAnchorDoAfter);
SubscribeLocalEvent<CultistFactoryComponent, ExaminedEvent>(OnExamine);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var structures = EntityQuery<CultistFactoryComponent>();
foreach (var structure in structures)
{
if (structure.Active)
continue;
if (_gameTiming.CurTime > structure.NextTimeUse)
{
structure.Active = true;
UpdateAppearance(structure.Owner, structure);
}
}
}
private void OnInit(EntityUid uid, CultistFactoryComponent component, ComponentInit args)
{
UpdateAppearance(uid, component);
}
private void OnInteract(EntityUid uid, CultistFactoryComponent component, InteractHandEvent args)
{
if (!TryComp<ActorComponent>(args.User, out var actor))
return;
if (!HasComp<CultistComponent>(args.User))
return;
if (!CanCraft(uid, component, args.User))
return;
var xform = Transform(uid);
if (!xform.Anchored)
return;
if (_ui.TryGetUi(uid, CultistAltarUiKey.Key, out var bui))
{
UserInterfaceSystem.SetUiState(bui, new CultistFactoryBUIState(component.Products));
_ui.OpenUi(bui, actor.PlayerSession);
}
}
private void OnSelected(EntityUid uid, CultistFactoryComponent component, CultistFactoryItemSelectedMessage args)
{
var user = args.Session.AttachedEntity;
if (user == null)
return;
if (!CanCraft(uid, component, user.Value))
return;
if (!_prototypeManager.TryIndex<CultistFactoryProductionPrototype>(args.Item, out var prototype))
return;
foreach (var item in prototype.Item)
{
var entity = Spawn(item, Transform(user.Value).Coordinates);
_handsSystem.TryPickupAnyHand(user.Value, entity);
}
component.NextTimeUse = _gameTiming.CurTime + TimeSpan.FromSeconds(component.Cooldown);
component.Active = false;
UpdateAppearance(uid, component);
}
private void TryToggleAnchor(EntityUid uid, CultistFactoryComponent component, InteractUsingEvent args)
{
var entityPrototype = _entityManager.GetComponent<MetaDataComponent>(args.Used).EntityPrototype;
if (entityPrototype == null)
return;
var used = entityPrototype.ID;
var user = args.User;
const int time = 2;
if (used != RitualDaggerPrototypeId)
return;
if (!HasComp<CultistComponent>(user))
return;
var xform = Transform(uid);
var ev = new CultAnchorDoAfterEvent(xform.Anchored);
var doArgs = new DoAfterArgs(user, time, ev, uid, uid);
_doAfterSystem.TryStartDoAfter(doArgs);
}
private void OnAnchorDoAfter(EntityUid uid, CultistFactoryComponent component, CultAnchorDoAfterEvent args)
{
if (!args.Target.HasValue)
return;
var target = args.Target.Value;
var xform = Transform(target);
if (args.IsAnchored)
{
_transform.Unanchor(target, xform);
_popup.PopupClient(Loc.GetString("anchorable-unanchored"), uid, args.User);
}
else
{
_transform.AnchorEntity(target, xform);
_popup.PopupClient(Loc.GetString("anchorable-anchored"), uid, args.User);
}
_audio.PlayPvs("/Audio/Items/ratchet.ogg", uid);
}
private void OnExamine(EntityUid uid, CultistFactoryComponent component, ExaminedEvent args)
{
if (!HasComp<CultistComponent>(args.Examiner))
return;
var isAnchored = Comp<TransformComponent>(uid).Anchored;
var messageId = isAnchored ? "examinable-anchored" : "examinable-unanchored";
args.PushMarkup(Loc.GetString(messageId, ("target", uid)));
}
private bool CanCraft(EntityUid uid, CultistFactoryComponent component, EntityUid user)
{
if (component.NextTimeUse == null || _gameTiming.CurTime > component.NextTimeUse)
{
component.Active = true;
UpdateAppearance(uid, component);
return true;
}
var name = MetaData(uid).EntityName;
var totalSeconds = (component.NextTimeUse - _gameTiming.CurTime).Value.TotalSeconds;
var seconds = Convert.ToInt32(totalSeconds);
_popupSystem.PopupEntity(Loc.GetString("cultist-factory-charging", ("name", name),
("seconds", seconds)), uid, user);
UpdateAppearance(uid, component);
return false;
}
private void UpdateAppearance(EntityUid uid, CultistFactoryComponent component)
{
AppearanceComponent? appearance = null;
if (!Resolve(uid, ref appearance, false))
return;
_appearance.SetData(uid, CultCraftStructureVisuals.Activated, component.Active);
}
}

View File

@@ -0,0 +1,103 @@
using Content.Server.EUI;
using Content.Server.Popups;
using Content.Server.White.Cult.Runes.Comps;
using Content.Shared.Actions;
using Content.Shared.Eui;
using Content.Shared.Popups;
using Content.Shared.White.Cult.UI;
using Robust.Server.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Timing;
namespace Content.Server.White.Cult.UI;
public sealed class TeleportSpellEui : BaseEui
{
[Dependency] private readonly EntityManager _entityManager = default!;
private SharedTransformSystem _transformSystem;
private PopupSystem _popupSystem;
private EntityUid _performer;
private EntityUid _target;
private EntityCoordinates _initialOwnerCoords;
private EntityCoordinates _initialTargetCoords;
private bool _used;
public TeleportSpellEui(EntityUid performer, EntityUid target)
{
IoCManager.InjectDependencies(this);
_transformSystem = _entityManager.System<SharedTransformSystem>();
_popupSystem = _entityManager.System<PopupSystem>();
_performer = performer;
_target = target;
_initialOwnerCoords = _entityManager.GetComponent<TransformComponent>(_performer).Coordinates;
_initialTargetCoords = _entityManager.GetComponent<TransformComponent>(_target).Coordinates;
Timer.Spawn(TimeSpan.FromSeconds(10), Close );
}
public override EuiStateBase GetNewState()
{
var runes = _entityManager.EntityQuery<CultRuneTeleportComponent>();
var state = new TeleportSpellEuiState();
foreach (var rune in runes)
{
state.Runes.Add((int)rune.Owner, rune.Label!);
}
return state;
}
public override void HandleMessage(EuiMessageBase msg)
{
base.HandleMessage(msg);
if(_used) return;
if (msg is not TeleportSpellTargetRuneSelected cast)
{
return;
}
var performerPosition = _entityManager.GetComponent<TransformComponent>(_performer).Coordinates;
var targetPosition = _entityManager.GetComponent<TransformComponent>(_target).Coordinates;;
performerPosition.TryDistance(_entityManager, targetPosition, out var distance);
if(distance > 1.5f)
{
_popupSystem.PopupEntity("Too far", _performer, PopupType.Medium);
return;
}
TransformComponent? runeTransform = null!;
foreach (var runeComponent in _entityManager.EntityQuery<CultRuneTeleportComponent>())
{
if (runeComponent.Owner == new EntityUid(cast.RuneUid))
{
runeTransform = _entityManager.GetComponent<TransformComponent>(runeComponent.Owner);
}
}
if (runeTransform is null)
{
_popupSystem.PopupEntity("Rune is gone", _performer);
DoStateUpdate();
return;
}
_used = true;
_transformSystem.SetCoordinates(_target, runeTransform.Coordinates);
Close();
}
}

View File

@@ -0,0 +1,8 @@
namespace Content.Server.White.IncorporealSystem;
[RegisterComponent]
public sealed partial class IncorporealComponent : Component
{
[DataField("movementSpeedBuff")]
public float MovementSpeedBuff = 1.5f;
}

View File

@@ -0,0 +1,72 @@
using System.Linq;
using Content.Server.Visible;
using Content.Shared.Movement.Systems;
using Content.Shared.Physics;
using Robust.Server.GameObjects;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Systems;
namespace Content.Server.White.IncorporealSystem;
public sealed class IncorporealSystem : EntitySystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly VisibilitySystem _visibilitySystem = default!;
public override void Initialize()
{
SubscribeLocalEvent<IncorporealComponent, ComponentStartup>(OnComponentInit);
SubscribeLocalEvent<IncorporealComponent, ComponentShutdown>(OnComponentRemoved);
SubscribeLocalEvent<IncorporealComponent, RefreshMovementSpeedModifiersEvent>(OnRefresh);
}
private void OnComponentInit(EntityUid uid, IncorporealComponent component, ComponentStartup args)
{
if (TryComp<FixturesComponent>(uid, out var fixtures) && fixtures.FixtureCount >= 1)
{
var fixture = fixtures.Fixtures.First();
_physics.SetCollisionMask(uid, fixture.Key, fixture.Value, (int) CollisionGroup.GhostImpassable, fixtures);
_physics.SetCollisionLayer(uid, fixture.Key, fixture.Value, 0, fixtures);
}
if (TryComp<VisibilityComponent>(uid, out var visibility))
{
_visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Ghost, false);
_visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false);
_visibilitySystem.RefreshVisibility(uid);
}
_movement.RefreshMovementSpeedModifiers(uid);
}
private void OnComponentRemoved(EntityUid uid, IncorporealComponent component, ComponentShutdown args)
{
if (TryComp<FixturesComponent>(uid, out var fixtures) && fixtures.FixtureCount >= 1)
{
var fixture = fixtures.Fixtures.First();
_physics.SetCollisionMask(uid, fixture.Key, fixture.Value, (int) (CollisionGroup.FlyingMobMask | CollisionGroup.GhostImpassable), fixtures);
_physics.SetCollisionLayer(uid, fixture.Key, fixture.Value, (int) CollisionGroup.FlyingMobLayer, fixtures);
}
if (TryComp<VisibilityComponent>(uid, out var visibility))
{
_visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Ghost, false);
_visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Normal, false);
_visibilitySystem.RefreshVisibility(uid);
}
component.MovementSpeedBuff = 1;
_movement.RefreshMovementSpeedModifiers(uid);
}
private void OnRefresh(EntityUid uid, IncorporealComponent component, RefreshMovementSpeedModifiersEvent args)
{
args.ModifySpeed(component.MovementSpeedBuff, component.MovementSpeedBuff);
}
}

View File

@@ -0,0 +1,24 @@
namespace Content.Server.White.SharpeningSystem;
[RegisterComponent]
public sealed partial class SharpenerComponent : Component
{
//rn gonna support only slash damage
[ViewVariables(VVAccess.ReadWrite)]
[DataField("damageModifier")]
public int DamageModifier;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("usages")]
public int Usages = 1;
}
[RegisterComponent]
public sealed partial class SharpenedComponent : Component
{
[ViewVariables(VVAccess.ReadWrite)]
public int DamageModifier = 0;
[ViewVariables(VVAccess.ReadWrite)]
public int AttacksLeft = 50;
}

View File

@@ -0,0 +1,99 @@
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Interaction;
using Content.Shared.Item;
using Content.Shared.Popups;
using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Prototypes;
namespace Content.Server.White.SharpeningSystem;
public sealed class SharpeningSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SharpenerComponent, AfterInteractEvent>(OnSharping);
SubscribeLocalEvent<SharpenedComponent, MeleeHitEvent>(OnMeleeHit);
SubscribeLocalEvent<SharpenedComponent, ComponentRemove>(OnSharpenedComponentRemove);
}
private void OnSharping(EntityUid uid, SharpenerComponent component, AfterInteractEvent args)
{
if (!args.Target.HasValue)
return;
var target = args.Target.Value;
if (!TryComp<ItemComponent>(target, out _))
{
_popupSystem.PopupEntity("Вы не можете заточить это", target, args.User);
return;
}
if (!TryComp<MeleeWeaponComponent>(target, out var meleeWeaponComponent))
{
_popupSystem.PopupEntity("Вы не можете заточить это", target, args.User);
return;
}
if (!meleeWeaponComponent.Damage.DamageDict.ContainsKey("Slash"))
{
_popupSystem.PopupEntity("У оружия должно быть остреё", target, args.User);
return;
}
if (HasComp<SharpenedComponent>(target))
{
_popupSystem.PopupEntity("Клинок уже заточен", target, args.User);
return;
}
EnsureComp<SharpenedComponent>(target).DamageModifier = component.DamageModifier;
meleeWeaponComponent.Damage.ExclusiveAdd(
new DamageSpecifier(_prototypeManager.Index<DamageTypePrototype>("Slash"), component.DamageModifier));
component.Usages -= 1;
if (component.Usages <= 0)
{
Del(uid);
}
_popupSystem.PopupEntity("Клинок успешно заточен", target, args.User);
}
private void OnMeleeHit(EntityUid uid, SharpenedComponent component, MeleeHitEvent args)
{
component.AttacksLeft--;
if (component.AttacksLeft == 10)
{
_popupSystem.PopupEntity("Клинок начал затупляться", uid, args.User);
}
if (component.AttacksLeft > 0)
return;
_popupSystem.PopupEntity("Клинок потерял свою заточку", uid, args.User);
RemComp(uid, component);
}
private void OnSharpenedComponentRemove(EntityUid uid, SharpenedComponent component, ComponentRemove args)
{
if (!TryComp(uid, out MeleeWeaponComponent? meleeWeapon))
{
return;
}
meleeWeapon.Damage.ExclusiveAdd(
new DamageSpecifier(_prototypeManager.Index<DamageTypePrototype>("Slash"), -component.DamageModifier));
}
}