я устала босс
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Changeling;
|
||||
@@ -9,9 +7,9 @@ namespace Content.Server.Changeling;
|
||||
[RegisterComponent, Access(typeof(ChangelingRuleSystem))]
|
||||
public sealed partial class ChangelingRuleComponent : Component
|
||||
{
|
||||
public readonly List<EntityUid> ChangelingMinds = new();
|
||||
public readonly List<EntityUid> ChangelingMinds = [];
|
||||
|
||||
[DataField("changelingPrototypeId", customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
|
||||
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
|
||||
public string ChangelingPrototypeId = "Changeling";
|
||||
|
||||
public int TotalChangelings => ChangelingMinds.Count;
|
||||
@@ -19,13 +17,12 @@ public sealed partial class ChangelingRuleComponent : Component
|
||||
public enum SelectionState
|
||||
{
|
||||
WaitingForSpawn = 0,
|
||||
ReadyToSelect = 1,
|
||||
SelectionMade = 2,
|
||||
ReadyToStart = 1,
|
||||
Started = 2,
|
||||
}
|
||||
|
||||
public SelectionState SelectionStatus = SelectionState.WaitingForSpawn;
|
||||
public TimeSpan AnnounceAt = TimeSpan.Zero;
|
||||
public Dictionary<ICommonSession, HumanoidCharacterProfile> StartCandidates = new();
|
||||
|
||||
/// <summary>
|
||||
/// Path to antagonist alert sound.
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
using System.Linq;
|
||||
using Content.Server._Miracle.GulagSystem;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.NPC.Systems;
|
||||
using Content.Server.Objectives;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared._White.Mood;
|
||||
using Content.Shared.Changeling;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -23,16 +20,14 @@ namespace Content.Server.Changeling;
|
||||
public sealed class ChangelingRuleSystem : GameRuleSystem<ChangelingRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly AntagSelectionSystem _antagSelection = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
|
||||
[Dependency] private readonly ObjectivesSystem _objectives = default!;
|
||||
[Dependency] private readonly ChangelingNameGenerator _nameGenerator = default!;
|
||||
[Dependency] private readonly GulagSystem _gulag = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
private const int PlayersPerChangeling = 15;
|
||||
private const int MaxChangelings = 4;
|
||||
@@ -40,8 +35,6 @@ public sealed class ChangelingRuleSystem : GameRuleSystem<ChangelingRuleComponen
|
||||
private const float ChangelingStartDelay = 3f * 60;
|
||||
private const float ChangelingStartDelayVariance = 3f * 60;
|
||||
|
||||
private const int ChangelingMinPlayers = 15;
|
||||
|
||||
private const int ChangelingMaxDifficulty = 5;
|
||||
private const int ChangelingMaxPicks = 20;
|
||||
|
||||
@@ -65,197 +58,52 @@ public sealed class ChangelingRuleSystem : GameRuleSystem<ChangelingRuleComponen
|
||||
{
|
||||
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||
|
||||
if (component.SelectionStatus == ChangelingRuleComponent.SelectionState.ReadyToSelect &&
|
||||
_gameTiming.CurTime > component.AnnounceAt)
|
||||
if (component.SelectionStatus < ChangelingRuleComponent.SelectionState.Started &&
|
||||
component.AnnounceAt < _gameTiming.CurTime)
|
||||
{
|
||||
DoChangelingStart(component);
|
||||
component.SelectionStatus = ChangelingRuleComponent.SelectionState.Started;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||
{
|
||||
var query = EntityQueryEnumerator<ChangelingRuleComponent, GameRuleComponent>();
|
||||
while (query.MoveNext(out var uid, out _, out var gameRule))
|
||||
{
|
||||
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
||||
continue;
|
||||
|
||||
if (!ev.Forced && ev.Players.Length < ChangelingMinPlayers)
|
||||
{
|
||||
_chatManager.SendAdminAnnouncement(Loc.GetString("changeling-not-enough-ready-players",
|
||||
("readyPlayersCount", ev.Players.Length), ("minimumPlayers", ChangelingMinPlayers)));
|
||||
|
||||
ev.Cancel();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ev.Players.Length == 0)
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("changeling-no-one-ready"));
|
||||
ev.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DoChangelingStart(ChangelingRuleComponent component)
|
||||
{
|
||||
if (component.StartCandidates.Count == 0)
|
||||
{
|
||||
Log.Error("Tried to start Changeling mode without any candidates.");
|
||||
return;
|
||||
}
|
||||
|
||||
var numChangelings =
|
||||
MathHelper.Clamp(component.StartCandidates.Count / PlayersPerChangeling, 1, MaxChangelings);
|
||||
|
||||
var changelingPool =
|
||||
_antagSelection.FindPotentialAntags(component.StartCandidates, component.ChangelingPrototypeId);
|
||||
|
||||
var selectedChangelings = _antagSelection.PickAntag(numChangelings, changelingPool);
|
||||
|
||||
foreach (var changeling in selectedChangelings)
|
||||
{
|
||||
MakeChangeling(changeling);
|
||||
}
|
||||
|
||||
component.SelectionStatus = ChangelingRuleComponent.SelectionState.SelectionMade;
|
||||
TryRoundStartAttempt(ev, Loc.GetString("changeling-title"));
|
||||
}
|
||||
|
||||
private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev)
|
||||
{
|
||||
var query = EntityQueryEnumerator<ChangelingRuleComponent, GameRuleComponent>();
|
||||
while (query.MoveNext(out var uid, out var changeling, out var gameRule))
|
||||
var query = QueryActiveRules();
|
||||
while (query.MoveNext(out _, out var changeling, out _))
|
||||
{
|
||||
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
||||
continue;
|
||||
|
||||
foreach (var player in ev.Players)
|
||||
{
|
||||
if (!ev.Profiles.ContainsKey(player.UserId))
|
||||
continue;
|
||||
|
||||
changeling.StartCandidates[player] = ev.Profiles[player.UserId];
|
||||
}
|
||||
|
||||
var delay = TimeSpan.FromSeconds(ChangelingStartDelay +
|
||||
_random.NextFloat(0f, ChangelingStartDelayVariance));
|
||||
|
||||
changeling.AnnounceAt = _gameTiming.CurTime + delay;
|
||||
|
||||
changeling.SelectionStatus = ChangelingRuleComponent.SelectionState.ReadyToSelect;
|
||||
changeling.SelectionStatus = ChangelingRuleComponent.SelectionState.ReadyToStart;
|
||||
}
|
||||
}
|
||||
|
||||
public bool MakeChangeling(ICommonSession changeling, bool giveObjectives = true)
|
||||
{
|
||||
var changelingRule = EntityQuery<ChangelingRuleComponent>().FirstOrDefault();
|
||||
if (changelingRule == null)
|
||||
{
|
||||
GameTicker.StartGameRule("Changeling", out var ruleEntity);
|
||||
changelingRule = Comp<ChangelingRuleComponent>(ruleEntity);
|
||||
}
|
||||
|
||||
if (!_mindSystem.TryGetMind(changeling, out var mindId, out var mind))
|
||||
{
|
||||
Log.Info("Failed getting mind for picked changeling.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HasComp<ChangelingRoleComponent>(mindId))
|
||||
{
|
||||
Log.Error($"Player {changeling.Name} is already a changeling.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mind.OwnedEntity is not { } entity)
|
||||
{
|
||||
Log.Error("Mind picked for changeling did not have an attached entity.");
|
||||
return false;
|
||||
}
|
||||
|
||||
_roleSystem.MindAddRole(mindId, new ChangelingRoleComponent
|
||||
{
|
||||
PrototypeId = changelingRule.ChangelingPrototypeId
|
||||
}, mind);
|
||||
|
||||
var briefing = Loc.GetString("changeling-role-briefing-short");
|
||||
|
||||
_roleSystem.MindAddRole(mindId, new RoleBriefingComponent
|
||||
{
|
||||
Briefing = briefing
|
||||
}, mind, true);
|
||||
|
||||
_roleSystem.MindPlaySound(mindId, changelingRule.GreetSoundNotification, mind);
|
||||
SendChangelingBriefing(mindId);
|
||||
changelingRule.ChangelingMinds.Add(mindId);
|
||||
|
||||
// Change the faction
|
||||
_npcFaction.RemoveFaction(entity, "NanoTrasen", false);
|
||||
_npcFaction.AddFaction(entity, "Syndicate");
|
||||
|
||||
EnsureComp<ChangelingComponent>(entity, out var readyChangeling);
|
||||
|
||||
readyChangeling.HiveName = _nameGenerator.GetName();
|
||||
Dirty(entity, readyChangeling);
|
||||
|
||||
if (!giveObjectives)
|
||||
return true;
|
||||
|
||||
var difficulty = 0f;
|
||||
for (var pick = 0; pick < ChangelingMaxPicks && ChangelingMaxDifficulty > difficulty; pick++)
|
||||
{
|
||||
var objective = _objectives.GetRandomObjective(mindId, mind, "ChangelingObjectiveGroups");
|
||||
if (objective == null)
|
||||
continue;
|
||||
|
||||
_mindSystem.AddObjective(mindId, mind, objective.Value);
|
||||
difficulty += Comp<ObjectiveComponent>(objective.Value).Difficulty;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SendChangelingBriefing(EntityUid mind)
|
||||
{
|
||||
if (!_mindSystem.TryGetSession(mind, out var session))
|
||||
return;
|
||||
|
||||
_chatManager.DispatchServerMessage(session, Loc.GetString("changeling-role-greeting"));
|
||||
}
|
||||
|
||||
private void HandleLatejoin(PlayerSpawnCompleteEvent ev)
|
||||
{
|
||||
var query = EntityQueryEnumerator<ChangelingRuleComponent, GameRuleComponent>();
|
||||
while (query.MoveNext(out var uid, out var changeling, out var gameRule))
|
||||
var query = QueryActiveRules();
|
||||
while (query.MoveNext(out _, out var changeling, out _))
|
||||
{
|
||||
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
||||
continue;
|
||||
|
||||
if (changeling.TotalChangelings >= MaxChangelings)
|
||||
continue;
|
||||
|
||||
if (_gulag.IsUserGulaged(ev.Player.UserId, out _))
|
||||
continue;
|
||||
|
||||
if (!ev.LateJoin)
|
||||
continue;
|
||||
|
||||
if (!ev.Profile.AntagPreferences.Contains(changeling.ChangelingPrototypeId))
|
||||
continue;
|
||||
|
||||
if (ev.JobId == null || !_prototypeManager.TryIndex<JobPrototype>(ev.JobId, out var job))
|
||||
continue;
|
||||
|
||||
if (!job.CanBeAntag)
|
||||
if (!_antagSelection.IsPlayerEligible(ev.Player, changeling.ChangelingPrototypeId))
|
||||
continue;
|
||||
|
||||
// Before the announcement is made, late-joiners are considered the same as players who readied.
|
||||
if (changeling.SelectionStatus < ChangelingRuleComponent.SelectionState.SelectionMade)
|
||||
{
|
||||
changeling.StartCandidates[ev.Player] = ev.Profile;
|
||||
if (changeling.SelectionStatus < ChangelingRuleComponent.SelectionState.Started)
|
||||
continue;
|
||||
}
|
||||
|
||||
var target = PlayersPerChangeling * changeling.TotalChangelings + 1;
|
||||
|
||||
var chance = 1f / PlayersPerChangeling;
|
||||
|
||||
if (ev.JoinOrder < target)
|
||||
@@ -272,11 +120,16 @@ public sealed class ChangelingRuleSystem : GameRuleSystem<ChangelingRuleComponen
|
||||
|
||||
if (_random.Prob(chance))
|
||||
{
|
||||
MakeChangeling(ev.Player);
|
||||
MakeChangeling(ev.Mob, changeling);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearUsedNames(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
_nameGenerator.ClearUsed();
|
||||
}
|
||||
|
||||
private void OnObjectivesTextGetInfo(
|
||||
EntityUid uid,
|
||||
ChangelingRuleComponent comp,
|
||||
@@ -286,8 +139,82 @@ public sealed class ChangelingRuleSystem : GameRuleSystem<ChangelingRuleComponen
|
||||
args.AgentName = Loc.GetString("changeling-round-end-agent-name");
|
||||
}
|
||||
|
||||
private void ClearUsedNames(RoundRestartCleanupEvent ev)
|
||||
private void DoChangelingStart(ChangelingRuleComponent component)
|
||||
{
|
||||
_nameGenerator.ClearUsed();
|
||||
var eligiblePlayers =
|
||||
_antagSelection.GetEligiblePlayers(_playerManager.Sessions, component.ChangelingPrototypeId);
|
||||
|
||||
if (eligiblePlayers.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var changelingsToSelect =
|
||||
_antagSelection.CalculateAntagCount(_playerManager.PlayerCount, PlayersPerChangeling, MaxChangelings);
|
||||
|
||||
var selectedChangelings = _antagSelection.ChooseAntags(changelingsToSelect, eligiblePlayers);
|
||||
|
||||
foreach (var changeling in selectedChangelings)
|
||||
{
|
||||
MakeChangeling(changeling, component);
|
||||
}
|
||||
}
|
||||
|
||||
public bool MakeChangeling(EntityUid changeling, ChangelingRuleComponent rule, bool giveObjectives = true)
|
||||
{
|
||||
if (!_mindSystem.TryGetMind(changeling, out var mindId, out var mind))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HasComp<ChangelingRoleComponent>(mindId))
|
||||
{
|
||||
Log.Error($"Player {mind.CharacterName} is already a changeling.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var briefing = Loc.GetString("changeling-role-briefing-short");
|
||||
_antagSelection.SendBriefing(changeling, briefing, null, rule.GreetSoundNotification);
|
||||
|
||||
rule.ChangelingMinds.Add(mindId);
|
||||
|
||||
_roleSystem.MindAddRole(mindId, new ChangelingRoleComponent
|
||||
{
|
||||
PrototypeId = rule.ChangelingPrototypeId
|
||||
}, mind);
|
||||
|
||||
_roleSystem.MindAddRole(mindId, new RoleBriefingComponent
|
||||
{
|
||||
Briefing = briefing
|
||||
}, mind, true);
|
||||
|
||||
// Change the faction
|
||||
_npcFaction.RemoveFaction(changeling, "NanoTrasen", false);
|
||||
_npcFaction.AddFaction(changeling, "Syndicate");
|
||||
|
||||
EnsureComp<ChangelingComponent>(changeling, out var readyChangeling);
|
||||
|
||||
readyChangeling.HiveName = _nameGenerator.GetName();
|
||||
Dirty(changeling, readyChangeling);
|
||||
|
||||
RaiseLocalEvent(mindId, new MoodEffectEvent("TraitorFocused")); // WD edit
|
||||
|
||||
if (!giveObjectives)
|
||||
return true;
|
||||
|
||||
var difficulty = 0f;
|
||||
for (var pick = 0; pick < ChangelingMaxPicks && ChangelingMaxDifficulty > difficulty; pick++)
|
||||
{
|
||||
var objective = _objectives.GetRandomObjective(mindId, mind, "ChangelingObjectiveGroups");
|
||||
if (objective == null)
|
||||
continue;
|
||||
|
||||
_mindSystem.AddObjective(mindId, mind, objective.Value);
|
||||
var adding = Comp<ObjectiveComponent>(objective.Value).Difficulty;
|
||||
difficulty += adding;
|
||||
Log.Debug($"Added objective {ToPrettyString(objective):objective} with {adding} difficulty");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,11 @@ using Content.Shared.Inventory;
|
||||
using Content.Shared.Miracle.UI;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Movement.Pulling.Systems;
|
||||
using Content.Shared.Ninja.Components;
|
||||
using Content.Shared.NPC.Components;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Tag;
|
||||
@@ -74,7 +78,7 @@ public sealed partial class ChangelingSystem
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly ActionContainerSystem _actionContainerSystem = default!;
|
||||
[Dependency] private readonly SharedPullingSystem _pullingSystem = default!;
|
||||
[Dependency] private readonly PullingSystem _pullingSystem = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
[Dependency] private readonly BloodstreamSystem _blood = default!;
|
||||
[Dependency] private readonly CuffableSystem _cuffable = default!;
|
||||
@@ -174,7 +178,7 @@ public sealed partial class ChangelingSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<SharedPullableComponent>(args.Target, out var pulled))
|
||||
if (!TryComp<PullableComponent>(args.Target, out var pulled))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("changeling-popup-absorb-pull"), args.Performer, args.Performer);
|
||||
return;
|
||||
@@ -189,8 +193,7 @@ public sealed partial class ChangelingSystem
|
||||
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.Performer, component.AbsorbDnaDelay,
|
||||
new AbsorbDnaDoAfterEvent(), uid, args.Target, uid)
|
||||
{
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true
|
||||
BreakOnMove = true
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -233,11 +236,10 @@ public sealed partial class ChangelingSystem
|
||||
var selectedDna = args.SelectedItem;
|
||||
var user = GetEntity(args.Entity);
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(
|
||||
new DoAfterArgs(EntityManager, user, component.TransformDelay,
|
||||
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, component.TransformDelay,
|
||||
new TransformDoAfterEvent { SelectedDna = selectedDna }, user, user, user)
|
||||
{
|
||||
BreakOnUserMove = true
|
||||
BreakOnMove = true
|
||||
}
|
||||
);
|
||||
|
||||
@@ -268,14 +270,12 @@ public sealed partial class ChangelingSystem
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("changeling-popup-start-regeneration"), uid, uid);
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(
|
||||
new DoAfterArgs(EntityManager, args.Performer, component.RegenerateDelay,
|
||||
new RegenerateDoAfterEvent(), args.Performer,
|
||||
args.Performer, args.Performer)
|
||||
{
|
||||
RequireCanInteract = false,
|
||||
Hidden = true
|
||||
});
|
||||
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.Performer, component.RegenerateDelay,
|
||||
new RegenerateDoAfterEvent(), args.Performer, args.Performer, args.Performer)
|
||||
{
|
||||
RequireCanInteract = false,
|
||||
Hidden = true
|
||||
});
|
||||
|
||||
component.IsRegenerating = true;
|
||||
}
|
||||
@@ -297,7 +297,7 @@ public sealed partial class ChangelingSystem
|
||||
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.Performer, component.LesserFormDelay,
|
||||
new LesserFormDoAfterEvent(), args.Performer, args.Performer)
|
||||
{
|
||||
BreakOnUserMove = true,
|
||||
BreakOnMove = true,
|
||||
RequireCanInteract = false
|
||||
});
|
||||
}
|
||||
@@ -402,10 +402,10 @@ public sealed partial class ChangelingSystem
|
||||
if (!TakeChemicals(uid, component, 50))
|
||||
return;
|
||||
|
||||
if (TryComp(target, out SharedPullerComponent? puller) && puller.Pulling is { } pulled &&
|
||||
TryComp(pulled, out SharedPullableComponent? pullable))
|
||||
if (TryComp(target, out PullerComponent? puller) && puller.Pulling is { } pulled
|
||||
&& TryComp(pulled, out PullableComponent? pullable))
|
||||
{
|
||||
_pullingSystem.TryStopPull(pullable);
|
||||
_pullingSystem.TryStopPull(pulled, pullable);
|
||||
}
|
||||
|
||||
TransformPerson(target, humanData);
|
||||
@@ -568,7 +568,7 @@ public sealed partial class ChangelingSystem
|
||||
_inventorySystem.TryUnequip(uid, outerName, out var outer, true, true);
|
||||
_inventorySystem.TryUnequip(uid, headName, out var helmet, true, true);
|
||||
|
||||
if (TryComp(outer, out MetaDataComponent? metaData) && metaData.EntityPrototype is {ID: armorName})
|
||||
if (TryComp(outer, out MetaDataComponent? metaData) && metaData.EntityPrototype is { ID: armorName })
|
||||
{
|
||||
args.Handled = true;
|
||||
return;
|
||||
@@ -654,9 +654,11 @@ public sealed partial class ChangelingSystem
|
||||
if (!TakeChemicals(uid, component, 5))
|
||||
return;
|
||||
|
||||
if (TryComp(uid, out SharedPullerComponent? puller) && puller.Pulling is { } pulled &&
|
||||
TryComp(pulled, out SharedPullableComponent? pullable))
|
||||
_pullingSystem.TryStopPull(pullable);
|
||||
if (TryComp(uid, out PullerComponent? puller) && puller.Pulling is { } pulled
|
||||
&& TryComp(pulled, out PullableComponent? pullable))
|
||||
{
|
||||
_pullingSystem.TryStopPull(pulled, pullable);
|
||||
}
|
||||
|
||||
TryTransformChangeling(args.User, args.SelectedDna, component);
|
||||
|
||||
@@ -689,10 +691,10 @@ public sealed partial class ChangelingSystem
|
||||
}
|
||||
}
|
||||
|
||||
if (TryComp(uid, out SharedPullerComponent? puller) && puller.Pulling is { } pulled &&
|
||||
TryComp(pulled, out SharedPullableComponent? pullable))
|
||||
if (TryComp(uid, out PullerComponent? puller) && puller.Pulling is { } pulled
|
||||
&& TryComp(pulled, out PullableComponent? pullable))
|
||||
{
|
||||
_pullingSystem.TryStopPull(pullable);
|
||||
_pullingSystem.TryStopPull(pulled, pullable);
|
||||
}
|
||||
|
||||
if (TryComp<ChangelingComponent>(args.Target.Value, out var changelingComponent))
|
||||
|
||||
@@ -20,6 +20,7 @@ using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Linq;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
|
||||
namespace Content.Server.Chemistry.EntitySystems
|
||||
{
|
||||
@@ -106,8 +107,11 @@ namespace Content.Server.Chemistry.EntitySystems
|
||||
RecheckConnections(uid, component);
|
||||
}
|
||||
|
||||
public void UpdateConnection(EntityUid dispenser, EntityUid chemMaster,
|
||||
ReagentDispenserComponent? dispenserComp = null, ChemMasterComponent? chemMasterComp = null)
|
||||
public void UpdateConnection(
|
||||
EntityUid dispenser,
|
||||
EntityUid chemMaster,
|
||||
ReagentDispenserComponent? dispenserComp = null,
|
||||
ChemMasterComponent? chemMasterComp = null)
|
||||
{
|
||||
if (!Resolve(dispenser, ref dispenserComp) || !Resolve(chemMaster, ref chemMasterComp))
|
||||
return;
|
||||
@@ -224,7 +228,8 @@ namespace Content.Server.Chemistry.EntitySystems
|
||||
|
||||
var outputContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser, SharedReagentDispenser.OutputSlotName);
|
||||
if (outputContainer is not { Valid: true } || !_solutionContainerSystem.TryGetFitsInDispenser(outputContainer.Value, out var solution, out _))
|
||||
{ // WD EDIT START
|
||||
{
|
||||
// WD EDIT START
|
||||
var chemMasterUid = reagentDispenser.Comp.ChemMaster;
|
||||
if (!reagentDispenser.Comp.ChemMasterInRange ||
|
||||
!TryComp(chemMasterUid, out ChemMasterComponent? chemMaster) ||
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Server.Administration.Logs;
|
||||
using Content.Server.Light.Components;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
@@ -17,7 +16,7 @@ using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Jittering;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Speech.EntitySystems;
|
||||
using Content.Shared.StatusEffect;
|
||||
@@ -32,8 +31,6 @@ using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using PullableComponent = Content.Shared.Movement.Pulling.Components.PullableComponent;
|
||||
using PullerComponent = Content.Shared.Movement.Pulling.Components.PullerComponent;
|
||||
|
||||
namespace Content.Server.Electrocution;
|
||||
|
||||
@@ -104,7 +101,8 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
var timePassed = Math.Min(frameTime, electrocution.TimeLeft);
|
||||
|
||||
electrocution.TimeLeft -= timePassed;
|
||||
electrocution.AccumulatedDamage += electrocution.BaseDamage * (consumer.ReceivedPower / consumer.DrawRate) * timePassed;
|
||||
electrocution.AccumulatedDamage +=
|
||||
electrocution.BaseDamage * (consumer.ReceivedPower / consumer.DrawRate) * timePassed;
|
||||
|
||||
if (!MathHelper.CloseTo(electrocution.TimeLeft, 0))
|
||||
continue;
|
||||
@@ -113,15 +111,19 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
{
|
||||
// TODO: damage should be scaled by shock damage multiplier
|
||||
// TODO: better paralyze/jitter timing
|
||||
var damage = new DamageSpecifier(_prototypeManager.Index<DamageTypePrototype>(DamageType), (int) electrocution.AccumulatedDamage);
|
||||
var damage = new DamageSpecifier(_prototypeManager.Index<DamageTypePrototype>(DamageType),
|
||||
(int) electrocution.AccumulatedDamage);
|
||||
|
||||
var actual =
|
||||
_damageable.TryChangeDamage(electrocution.Electrocuting, damage, origin: electrocution.Source);
|
||||
|
||||
var actual = _damageable.TryChangeDamage(electrocution.Electrocuting, damage, origin: electrocution.Source);
|
||||
if (actual != null)
|
||||
{
|
||||
_adminLogger.Add(LogType.Electrocution,
|
||||
$"{ToPrettyString(electrocution.Electrocuting):entity} received {actual.GetTotal():damage} powered electrocution damage from {ToPrettyString(electrocution.Source):source}");
|
||||
}
|
||||
}
|
||||
|
||||
QueueDel(uid);
|
||||
}
|
||||
}
|
||||
@@ -144,19 +146,22 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
{
|
||||
if (!electrified.Enabled)
|
||||
return false;
|
||||
|
||||
if (electrified.NoWindowInTile)
|
||||
{
|
||||
var tileRef = transform.Coordinates.GetTileRef(EntityManager, _mapManager);
|
||||
|
||||
if (tileRef != null)
|
||||
{
|
||||
foreach (var entity in _entityLookup.GetLocalEntitiesIntersecting(tileRef.Value, flags: LookupFlags.StaticSundries))
|
||||
foreach (var entity in _entityLookup.GetLocalEntitiesIntersecting(tileRef.Value,
|
||||
flags: LookupFlags.StaticSundries))
|
||||
{
|
||||
if (_tag.HasTag(entity, "Window"))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (electrified.UsesApcPower)
|
||||
{
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
@@ -199,7 +204,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
if (!_meleeWeapon.GetDamage(args.Used, args.User).Any())
|
||||
return;
|
||||
|
||||
DoCommonElectrocution(args.User, uid, component.UnarmedHitShock, component.UnarmedHitStun, false, 1);
|
||||
DoCommonElectrocution(args.User, uid, component.UnarmedHitShock, component.UnarmedHitStun, false);
|
||||
}
|
||||
|
||||
private void OnElectrifiedInteractUsing(EntityUid uid, ElectrifiedComponent electrified, InteractUsingEvent args)
|
||||
@@ -213,6 +218,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
|
||||
TryDoElectrifiedAct(uid, args.User, siemens, electrified);
|
||||
}
|
||||
|
||||
private bool IsPanelClosed(EntityUid uid) // WD
|
||||
{
|
||||
return TryComp(uid, out WiresPanelComponent? panel) && !panel.Open;
|
||||
@@ -221,24 +227,16 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
private float CalculateElectrifiedDamageScale(float power)
|
||||
{
|
||||
// A logarithm allows a curve of damage that grows quickly, but slows down dramatically past a value. This keeps the damage to a reasonable range.
|
||||
const float DamageShift = 1.67f; // Shifts the curve for an overall higher or lower damage baseline
|
||||
const float CeilingCoefficent = 1.35f; // Adjusts the approach to maximum damage, higher = Higher top damage
|
||||
const float LogGrowth = 0.00001f; // Adjusts the growth speed of the curve
|
||||
const float damageShift = 1.67f; // Shifts the curve for an overall higher or lower damage baseline
|
||||
const float ceilingCoefficent = 1.35f; // Adjusts the approach to maximum damage, higher = Higher top damage
|
||||
const float logGrowth = 0.00001f; // Adjusts the growth speed of the curve
|
||||
|
||||
return DamageShift + MathF.Log(power * LogGrowth) * CeilingCoefficent;
|
||||
return damageShift + MathF.Log(power * logGrowth) * ceilingCoefficent;
|
||||
}
|
||||
|
||||
private float CalculateElectrifiedDamageScale(float power)
|
||||
{
|
||||
// A logarithm allows a curve of damage that grows quickly, but slows down dramatically past a value. This keeps the damage to a reasonable range.
|
||||
const float DamageShift = 1.67f; // Shifts the curve for an overall higher or lower damage baseline
|
||||
const float CeilingCoefficent = 1.35f; // Adjusts the approach to maximum damage, higher = Higher top damage
|
||||
const float LogGrowth = 0.00001f; // Adjusts the growth speed of the curve
|
||||
|
||||
return DamageShift + MathF.Log(power * LogGrowth) * CeilingCoefficent;
|
||||
}
|
||||
|
||||
public bool TryDoElectrifiedAct(EntityUid uid, EntityUid targetUid,
|
||||
public bool TryDoElectrifiedAct(
|
||||
EntityUid uid,
|
||||
EntityUid targetUid,
|
||||
float siemens = 1,
|
||||
ElectrifiedComponent? electrified = null,
|
||||
NodeContainerComponent? nodeContainer = null,
|
||||
@@ -277,6 +275,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
electrified.SiemensCoefficient
|
||||
);
|
||||
}
|
||||
|
||||
return lastRet;
|
||||
}
|
||||
|
||||
@@ -304,21 +303,28 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
entity,
|
||||
uid,
|
||||
node,
|
||||
(int) MathF.Ceiling(electrified.ShockDamage * damageScale * MathF.Pow(RecursiveDamageMultiplier, depth)),
|
||||
TimeSpan.FromSeconds(electrified.ShockTime * MathF.Min(1f + MathF.Log2(1f + damageScale), 3f) * MathF.Pow(RecursiveTimeMultiplier, depth)),
|
||||
(int) MathF.Ceiling(electrified.ShockDamage * damageScale *
|
||||
MathF.Pow(RecursiveDamageMultiplier, depth)),
|
||||
TimeSpan.FromSeconds(electrified.ShockTime * MathF.Min(1f + MathF.Log2(1f + damageScale), 3f) *
|
||||
MathF.Pow(RecursiveTimeMultiplier, depth)),
|
||||
true,
|
||||
electrified.SiemensCoefficient);
|
||||
}
|
||||
|
||||
return lastRet;
|
||||
}
|
||||
}
|
||||
|
||||
private Node? PoweredNode(EntityUid uid, ElectrifiedComponent electrified, NodeContainerComponent? nodeContainer = null)
|
||||
private Node? PoweredNode(
|
||||
EntityUid uid,
|
||||
ElectrifiedComponent electrified,
|
||||
NodeContainerComponent? nodeContainer = null)
|
||||
{
|
||||
if (!Resolve(uid, ref nodeContainer, false))
|
||||
return null;
|
||||
|
||||
return TryNode(electrified.HighVoltageNode) ?? TryNode(electrified.MediumVoltageNode) ?? TryNode(electrified.LowVoltageNode);
|
||||
return TryNode(electrified.HighVoltageNode) ??
|
||||
TryNode(electrified.MediumVoltageNode) ?? TryNode(electrified.LowVoltageNode);
|
||||
|
||||
Node? TryNode(string? id)
|
||||
{
|
||||
@@ -328,14 +334,21 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
{
|
||||
return tryNode;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool TryDoElectrocution(
|
||||
EntityUid uid, EntityUid? sourceUid, int shockDamage, TimeSpan time, bool refresh, float siemensCoefficient = 1f,
|
||||
StatusEffectsComponent? statusEffects = null, bool ignoreInsulation = false)
|
||||
EntityUid uid,
|
||||
EntityUid? sourceUid,
|
||||
int shockDamage,
|
||||
TimeSpan time,
|
||||
bool refresh,
|
||||
float siemensCoefficient = 1f,
|
||||
StatusEffectsComponent? statusEffects = null,
|
||||
bool ignoreInsulation = false)
|
||||
{
|
||||
if (!DoCommonElectrocutionAttempt(uid, sourceUid, ref siemensCoefficient, ignoreInsulation)
|
||||
|| !DoCommonElectrocution(uid, sourceUid, shockDamage, time, refresh, siemensCoefficient, statusEffects))
|
||||
@@ -396,11 +409,15 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool DoCommonElectrocutionAttempt(EntityUid uid, EntityUid? sourceUid, ref float siemensCoefficient, bool ignoreInsulation = false)
|
||||
private bool DoCommonElectrocutionAttempt(
|
||||
EntityUid uid,
|
||||
EntityUid? sourceUid,
|
||||
ref float siemensCoefficient,
|
||||
bool ignoreInsulation = false)
|
||||
{
|
||||
|
||||
var attemptEvent = new ElectrocutionAttemptEvent(uid, sourceUid, siemensCoefficient,
|
||||
ignoreInsulation ? SlotFlags.NONE : ~SlotFlags.POCKET);
|
||||
|
||||
RaiseLocalEvent(uid, attemptEvent, true);
|
||||
|
||||
// Cancel the electrocution early, so we don't recursively electrocute anything.
|
||||
@@ -411,8 +428,13 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool DoCommonElectrocution(EntityUid uid, EntityUid? sourceUid,
|
||||
int? shockDamage, TimeSpan time, bool refresh, float siemensCoefficient = 1f,
|
||||
private bool DoCommonElectrocution(
|
||||
EntityUid uid,
|
||||
EntityUid? sourceUid,
|
||||
int? shockDamage,
|
||||
TimeSpan time,
|
||||
bool refresh,
|
||||
float siemensCoefficient = 1f,
|
||||
StatusEffectsComponent? statusEffects = null)
|
||||
{
|
||||
if (siemensCoefficient <= 0)
|
||||
@@ -432,7 +454,8 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_statusEffects.TryAddStatusEffect<ElectrocutedComponent>(uid, StatusEffectKey, time, refresh, statusEffects))
|
||||
if (!_statusEffects.TryAddStatusEffect<ElectrocutedComponent>(uid, StatusEffectKey, time, refresh,
|
||||
statusEffects))
|
||||
return false;
|
||||
|
||||
var shouldStun = siemensCoefficient > 0.5f;
|
||||
@@ -455,7 +478,8 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
}
|
||||
|
||||
_stuttering.DoStutter(uid, time * StutteringTimeMultiplier, refresh, statusEffects);
|
||||
_jittering.DoJitter(uid, time * JitterTimeMultiplier, refresh, JitterAmplitude, JitterFrequency, true, statusEffects);
|
||||
_jittering.DoJitter(uid, time * JitterTimeMultiplier, refresh, JitterAmplitude, JitterFrequency, true,
|
||||
statusEffects);
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("electrocuted-component-mob-shocked-popup-player"), uid, uid);
|
||||
|
||||
@@ -467,6 +491,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("electrocuted-component-mob-shocked-by-source-popup-others",
|
||||
("mob", identifiedUid), ("source", (sourceUid.Value))), uid, filter, true);
|
||||
|
||||
PlayElectrocutionSound(uid, sourceUid.Value);
|
||||
}
|
||||
else
|
||||
@@ -509,7 +534,9 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRandomInsulationMapInit(EntityUid uid, RandomInsulationComponent randomInsulation,
|
||||
private void OnRandomInsulationMapInit(
|
||||
EntityUid uid,
|
||||
RandomInsulationComponent randomInsulation,
|
||||
MapInitEvent args)
|
||||
{
|
||||
if (!TryComp<InsulatedComponent>(uid, out var insulated))
|
||||
@@ -521,12 +548,16 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
SetInsulatedSiemensCoefficient(uid, _random.Pick(randomInsulation.List), insulated);
|
||||
}
|
||||
|
||||
private void PlayElectrocutionSound(EntityUid targetUid, EntityUid sourceUid, ElectrifiedComponent? electrified = null)
|
||||
private void PlayElectrocutionSound(
|
||||
EntityUid targetUid,
|
||||
EntityUid sourceUid,
|
||||
ElectrifiedComponent? electrified = null)
|
||||
{
|
||||
if (!Resolve(sourceUid, ref electrified, false) || !electrified.PlaySoundOnShock)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_audio.PlayPvs(electrified.ShockNoises, targetUid, AudioParams.Default.WithVolume(electrified.ShockVolume));
|
||||
}
|
||||
}
|
||||
@@ -18,37 +18,35 @@ 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");
|
||||
public readonly SoundSpecifier GreetingsSound = new SoundPathSpecifier("/Audio/White/Cult/blood_cult_greeting.ogg");
|
||||
|
||||
[DataField("cultPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<GamePresetPrototype>))]
|
||||
public static string CultGamePresetPrototype = "Cult";
|
||||
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<GamePresetPrototype>))]
|
||||
public string CultGamePresetPrototype = "Cult";
|
||||
|
||||
[DataField("cultistPrototypeId", customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
|
||||
public static string CultistPrototypeId = "Cultist";
|
||||
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
|
||||
public string CultistPrototypeId = "Cultist";
|
||||
|
||||
[DataField("reaperPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public static string ReaperPrototype = "ReaperConstruct";
|
||||
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string ReaperPrototype = "ReaperConstruct";
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField("tileId")]
|
||||
public static string CultFloor = "CultFloor";
|
||||
public string CultFloor = "CultFloor";
|
||||
|
||||
[DataField("eyeColor")]
|
||||
public static Color EyeColor = Color.FromHex("#f80000");
|
||||
[DataField]
|
||||
public Color EyeColor = Color.FromHex("#f80000");
|
||||
|
||||
public static string HolyWaterReagent = "Holywater";
|
||||
public string HolyWaterReagent = "Holywater";
|
||||
|
||||
[DataField("redEyeThreshold")]
|
||||
public static int ReadEyeThreshold = 5;
|
||||
[DataField]
|
||||
public int ReadEyeThreshold = 5;
|
||||
|
||||
[DataField("pentagramThreshold")]
|
||||
public static int PentagramThreshold = 8;
|
||||
[DataField]
|
||||
public int PentagramThreshold = 8;
|
||||
|
||||
public Dictionary<ICommonSession, HumanoidCharacterProfile> StarCandidates = new();
|
||||
[DataField(customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||
public List<string> StartingItems = [];
|
||||
|
||||
[DataField("cultistStartingItems", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||
public List<string> StartingItems = new();
|
||||
|
||||
[DataField("cultistRolePrototype", customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
|
||||
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
|
||||
public string CultistRolePrototype = "Cultist";
|
||||
|
||||
/// <summary>
|
||||
@@ -58,19 +56,17 @@ public sealed partial class CultRuleComponent : Component
|
||||
|
||||
public EntityUid? CultTarget;
|
||||
|
||||
public List<CultistComponent> CurrentCultists = new();
|
||||
public List<CultistComponent> CurrentCultists = [];
|
||||
|
||||
public List<ConstructComponent> Constructs = new();
|
||||
public List<ConstructComponent> Constructs = [];
|
||||
|
||||
public CultWinCondition WinCondition;
|
||||
}
|
||||
|
||||
public enum CultWinCondition : byte
|
||||
{
|
||||
CultWin,
|
||||
CultFailure
|
||||
Win,
|
||||
Failure
|
||||
}
|
||||
|
||||
public sealed class CultNarsieSummoned : EntityEventArgs
|
||||
{
|
||||
}
|
||||
public sealed class CultNarsieSummoned : EntityEventArgs;
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
using System.Linq;
|
||||
using Content.Server._Miracle.Components;
|
||||
using Content.Server._Miracle.GulagSystem;
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.NPC.Systems;
|
||||
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;
|
||||
@@ -17,7 +12,6 @@ using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Player;
|
||||
@@ -26,40 +20,38 @@ using Content.Shared._White;
|
||||
using Content.Shared._White.Chaplain;
|
||||
using Content.Shared._White.Cult.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Robust.Server.Player;
|
||||
|
||||
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 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!;
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly AntagSelectionSystem _antagSelection = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly GulagSystem _gulag = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private int _minimalCultists;
|
||||
private int _cultGameRuleMinimapPlayers;
|
||||
private const int PlayerPerCultist = 10;
|
||||
private int _minStartingCultists;
|
||||
private int _maxStartingCultists;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_sawmill = Logger.GetSawmill("preset");
|
||||
_minimalCultists = _cfg.GetCVar(WhiteCVars.CultMinStartingPlayers);
|
||||
_cultGameRuleMinimapPlayers = _cfg.GetCVar(WhiteCVars.CultMinPlayers);
|
||||
_minStartingCultists = _cfg.GetCVar(WhiteCVars.CultMinStartingPlayers);
|
||||
_maxStartingCultists = _cfg.GetCVar(WhiteCVars.CultMaxStartingPlayers);
|
||||
|
||||
SubscribeLocalEvent<RoundStartAttemptEvent>(OnStartAttempt);
|
||||
SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(OnPlayersSpawned);
|
||||
@@ -71,6 +63,101 @@ public sealed class CultRuleSystem : GameRuleSystem<CultRuleComponent>
|
||||
SubscribeLocalEvent<CultistComponent, MobStateChangedEvent>(OnCultistsStateChanged);
|
||||
}
|
||||
|
||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||
{
|
||||
TryRoundStartAttempt(ev, "CULT");
|
||||
}
|
||||
|
||||
private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev)
|
||||
{
|
||||
var query = QueryActiveRules();
|
||||
while (query.MoveNext(out _, out var cult, out _))
|
||||
{
|
||||
DoCultistsStart(cult);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
||||
{
|
||||
var query = QueryActiveRules();
|
||||
while (query.MoveNext(out _, out var cult, out _))
|
||||
{
|
||||
var winText = Loc.GetString($"cult-condition-{cult.WinCondition.ToString().ToLower()}");
|
||||
ev.AddLine(winText);
|
||||
|
||||
ev.AddLine(Loc.GetString("cultists-list-start"));
|
||||
|
||||
foreach (var (entityName, ckey) in cult.CultistsCache)
|
||||
{
|
||||
var lising = Loc.GetString("cultists-list-name", ("name", entityName), ("user", ckey));
|
||||
ev.AddLine(lising);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNarsieSummon(CultNarsieSummoned ev)
|
||||
{
|
||||
var query = EntityQueryEnumerator<MobStateComponent, MindContainerComponent, CultistComponent>();
|
||||
var rulesQuery = QueryActiveRules();
|
||||
while (rulesQuery.MoveNext(out _, out var cult, out _))
|
||||
{
|
||||
cult.WinCondition = CultWinCondition.Win;
|
||||
_roundEndSystem.EndRound();
|
||||
|
||||
while (query.MoveNext(out var uid, out _, out var mindContainer, out _))
|
||||
{
|
||||
if (!mindContainer.HasMind || mindContainer.Mind is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var reaper = Spawn(cult.ReaperPrototype, Transform(uid).Coordinates);
|
||||
_mindSystem.TransferTo(mindContainer.Mind.Value, reaper);
|
||||
|
||||
_bodySystem.GibBody(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCultistComponentInit(EntityUid uid, CultistComponent component, ComponentInit args)
|
||||
{
|
||||
var query = QueryActiveRules();
|
||||
while (query.MoveNext(out _, out var cult, out _))
|
||||
{
|
||||
if (!TryComp<MindContainerComponent>(uid, out var mindComponent))
|
||||
return;
|
||||
|
||||
if (!mindComponent.HasMind)
|
||||
return;
|
||||
|
||||
cult.CurrentCultists.Add(component);
|
||||
|
||||
if (TryComp<ActorComponent>(uid, out var actor))
|
||||
{
|
||||
cult.CultistsCache.Add(MetaData(uid).EntityName, actor.PlayerSession.Name);
|
||||
}
|
||||
|
||||
UpdateCultistsAppearance(cult);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCultistComponentRemoved(EntityUid uid, CultistComponent component, ComponentRemove args)
|
||||
{
|
||||
var query = QueryActiveRules();
|
||||
while (query.MoveNext(out _, out var cult, out _))
|
||||
{
|
||||
cult.CurrentCultists.Remove(component);
|
||||
|
||||
foreach (var empower in component.SelectedEmpowers)
|
||||
{
|
||||
_actions.RemoveAction(uid, GetEntity(empower));
|
||||
}
|
||||
|
||||
RemoveCultistAppearance(uid);
|
||||
CheckRoundShouldEnd();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCultistsStateChanged(EntityUid uid, CultistComponent component, MobStateChangedEvent ev)
|
||||
{
|
||||
if (ev.NewMobState == MobState.Dead)
|
||||
@@ -79,111 +166,96 @@ public sealed class CultRuleSystem : GameRuleSystem<CultRuleComponent>
|
||||
}
|
||||
}
|
||||
|
||||
public MindComponent? GetTarget()
|
||||
private void DoCultistsStart(CultRuleComponent rule)
|
||||
{
|
||||
var cultistsRule = EntityQuery<CultRuleComponent>().FirstOrDefault();
|
||||
var eligiblePlayers =
|
||||
_antagSelection.GetEligiblePlayers(_playerManager.Sessions, rule.CultistRolePrototype);
|
||||
|
||||
if (cultistsRule?.CultTarget == null || !TryComp<MindComponent>(cultistsRule.CultTarget.Value, out var mind))
|
||||
eligiblePlayers.RemoveAll(HasComp<HolyComponent>);
|
||||
|
||||
if (eligiblePlayers.Count == 0)
|
||||
{
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
return mind;
|
||||
var cultistsToSelect =
|
||||
Math.Clamp(_playerManager.PlayerCount / PlayerPerCultist, _minStartingCultists, _maxStartingCultists);
|
||||
|
||||
var selectedCultists = _antagSelection.ChooseAntags(cultistsToSelect, eligiblePlayers);
|
||||
|
||||
foreach (var cultist in selectedCultists)
|
||||
{
|
||||
MakeCultist(cultist, rule);
|
||||
}
|
||||
|
||||
var potentialTargets = FindPotentialTargets(selectedCultists);
|
||||
rule.CultTarget = _random.PickAndTake(potentialTargets).Mind;
|
||||
}
|
||||
|
||||
public MindComponent? GetTarget()
|
||||
{
|
||||
var query = QueryActiveRules();
|
||||
while (query.MoveNext(out _, out var cult, out _))
|
||||
{
|
||||
if (cult.CultTarget == null || !TryComp(cult.CultTarget.Value, out MindComponent? mind))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return mind;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool CanSummonNarsie()
|
||||
{
|
||||
var cultistsRule = EntityQuery<CultRuleComponent>().FirstOrDefault();
|
||||
if (cultistsRule is null)
|
||||
var query = QueryActiveRules();
|
||||
while (query.MoveNext(out _, out var cult, out _))
|
||||
{
|
||||
return false;
|
||||
var cultistsAmount = cult.CurrentCultists.Count;
|
||||
var constructsAmount = cult.Constructs.Count;
|
||||
var enoughCultists = cultistsAmount + constructsAmount > 10;
|
||||
|
||||
if (!enoughCultists)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var target = GetTarget();
|
||||
var targetKilled = target == null || _mindSystem.IsCharacterDeadIc(target);
|
||||
|
||||
return targetKilled;
|
||||
}
|
||||
|
||||
var cultistsAmount = cultistsRule.CurrentCultists.Count;
|
||||
var constructsAmount = cultistsRule.Constructs.Count;
|
||||
var enoughCultists = cultistsAmount + constructsAmount > 10;
|
||||
|
||||
if (!enoughCultists)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var target = GetTarget();
|
||||
var targetKilled = target == null || _mindSystem.IsCharacterDeadIc(target);
|
||||
|
||||
return targetKilled;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CheckRoundShouldEnd()
|
||||
{
|
||||
var cultistsRule = EntityQuery<CultRuleComponent>().FirstOrDefault();
|
||||
if (cultistsRule is null)
|
||||
var query = QueryActiveRules();
|
||||
while (query.MoveNext(out _, out var cult, out _))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var aliveCultists = 0;
|
||||
|
||||
var aliveCultists = 0;
|
||||
|
||||
foreach (var cultistComponent in cultistsRule.CurrentCultists)
|
||||
{
|
||||
var owner = cultistComponent.Owner;
|
||||
if (!TryComp<MobStateComponent>(owner, out var mobState))
|
||||
continue;
|
||||
|
||||
if (_mobStateSystem.IsAlive(owner, mobState))
|
||||
foreach (var cultistComponent in cult.CurrentCultists)
|
||||
{
|
||||
aliveCultists++;
|
||||
var owner = cultistComponent.Owner;
|
||||
if (!TryComp<MobStateComponent>(owner, out var mobState))
|
||||
continue;
|
||||
|
||||
if (_mobStateSystem.IsAlive(owner, mobState))
|
||||
{
|
||||
aliveCultists++;
|
||||
}
|
||||
}
|
||||
|
||||
if (aliveCultists != 0)
|
||||
return;
|
||||
|
||||
cult.WinCondition = CultWinCondition.Failure;
|
||||
_roundEndSystem.EndRound();
|
||||
}
|
||||
|
||||
if (aliveCultists != 0)
|
||||
return;
|
||||
|
||||
cultistsRule.WinCondition = CultWinCondition.CultFailure;
|
||||
_roundEndSystem.EndRound();
|
||||
}
|
||||
|
||||
private void OnCultistComponentInit(EntityUid uid, CultistComponent component, ComponentInit args)
|
||||
{
|
||||
var cultistsRule = EntityQuery<CultRuleComponent>().FirstOrDefault();
|
||||
if (cultistsRule is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<MindContainerComponent>(uid, out var mindComponent))
|
||||
return;
|
||||
|
||||
if (!mindComponent.HasMind)
|
||||
return;
|
||||
|
||||
cultistsRule.CurrentCultists.Add(component);
|
||||
|
||||
if (TryComp<ActorComponent>(uid, out var actor))
|
||||
{
|
||||
cultistsRule.CultistsCache.Add(MetaData(uid).EntityName, actor.PlayerSession.Name);
|
||||
}
|
||||
|
||||
UpdateCultistsAppearance(cultistsRule);
|
||||
}
|
||||
|
||||
private void OnCultistComponentRemoved(EntityUid uid, CultistComponent component, ComponentRemove args)
|
||||
{
|
||||
var cultistsRule = EntityQuery<CultRuleComponent>().FirstOrDefault();
|
||||
if (cultistsRule is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cultistsRule.CurrentCultists.Remove(component);
|
||||
|
||||
foreach (var empower in component.SelectedEmpowers)
|
||||
{
|
||||
_actions.RemoveAction(uid, GetEntity(empower));
|
||||
}
|
||||
|
||||
RemoveCultistAppearance(uid);
|
||||
CheckRoundShouldEnd();
|
||||
}
|
||||
|
||||
private void RemoveCultistAppearance(EntityUid cultist)
|
||||
@@ -203,104 +275,32 @@ public sealed class CultRuleSystem : GameRuleSystem<CultRuleComponent>
|
||||
var cultistsCount = cultRuleComponent.CurrentCultists.Count;
|
||||
var constructsCount = cultRuleComponent.Constructs.Count;
|
||||
var totalCultMembers = cultistsCount + constructsCount;
|
||||
if (totalCultMembers < CultRuleComponent.ReadEyeThreshold)
|
||||
if (totalCultMembers < cultRuleComponent.ReadEyeThreshold)
|
||||
return;
|
||||
|
||||
foreach (var cultistComponent in cultRuleComponent.CurrentCultists)
|
||||
{
|
||||
if (TryComp<HumanoidAppearanceComponent>(cultistComponent.Owner, out var appearanceComponent))
|
||||
{
|
||||
appearanceComponent.EyeColor = CultRuleComponent.EyeColor;
|
||||
Dirty(appearanceComponent);
|
||||
appearanceComponent.EyeColor = cultRuleComponent.EyeColor;
|
||||
Dirty(cultistComponent.Owner, appearanceComponent);
|
||||
}
|
||||
|
||||
if (totalCultMembers < CultRuleComponent.PentagramThreshold)
|
||||
if (totalCultMembers < cultRuleComponent.PentagramThreshold)
|
||||
return;
|
||||
|
||||
EnsureComp<PentagramComponent>(cultistComponent.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
||||
private List<MindContainerComponent> FindPotentialTargets(List<EntityUid> exclude = null!)
|
||||
{
|
||||
var cultistsRule = EntityQuery<CultRuleComponent>().FirstOrDefault();
|
||||
if (cultistsRule is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var winText = Loc.GetString($"cult-cond-{cultistsRule.WinCondition.ToString().ToLower()}");
|
||||
ev.AddLine(winText);
|
||||
|
||||
ev.AddLine(Loc.GetString("cultists-list-start"));
|
||||
|
||||
foreach (var (entityName, ckey) in cultistsRule.CultistsCache)
|
||||
{
|
||||
var lising = Loc.GetString("cultists-list-name", ("name", entityName), ("user", ckey));
|
||||
ev.AddLine(lising);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||
{
|
||||
var cultistsRule = EntityQuery<CultRuleComponent>().FirstOrDefault();
|
||||
if (cultistsRule is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.Players.Length != 0)
|
||||
return;
|
||||
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-no-one-ready"));
|
||||
ev.Cancel();
|
||||
}
|
||||
|
||||
private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev)
|
||||
{
|
||||
var cultistsRule = EntityQuery<CultRuleComponent>().FirstOrDefault();
|
||||
if (cultistsRule is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var player in ev.Players)
|
||||
{
|
||||
if (!ev.Profiles.ContainsKey(player.UserId))
|
||||
continue;
|
||||
|
||||
cultistsRule.StarCandidates[player] = ev.Profiles[player.UserId];
|
||||
}
|
||||
|
||||
var potentialCultists = FindPotentialCultist(cultistsRule.StarCandidates);
|
||||
var pickedCultist = PickCultists(potentialCultists);
|
||||
var potentialTargets = FindPotentialTargets(pickedCultist);
|
||||
|
||||
cultistsRule.CultTarget = _random.PickAndTake(potentialTargets).Mind;
|
||||
|
||||
foreach (var pickerCultist in pickedCultist)
|
||||
{
|
||||
MakeCultist(pickerCultist);
|
||||
}
|
||||
}
|
||||
|
||||
private List<MindContainerComponent> FindPotentialTargets(List<ICommonSession> exclude = null!)
|
||||
{
|
||||
var querry = EntityManager.EntityQuery<MindContainerComponent, HumanoidAppearanceComponent, ActorComponent>();
|
||||
var querry =
|
||||
EntityManager.EntityQueryEnumerator<MindContainerComponent, HumanoidAppearanceComponent, ActorComponent>();
|
||||
|
||||
var potentialTargets = new List<MindContainerComponent>();
|
||||
|
||||
foreach (var (mind, _, actor) in querry)
|
||||
while (querry.MoveNext(out var uid, out var mind, out _, out var actor))
|
||||
{
|
||||
var entity = mind.Mind;
|
||||
|
||||
@@ -310,7 +310,7 @@ public sealed class CultRuleSystem : GameRuleSystem<CultRuleComponent>
|
||||
if (_gulag.IsUserGulaged(actor.PlayerSession.UserId, out _))
|
||||
continue;
|
||||
|
||||
if (exclude?.Contains(actor.PlayerSession) is true)
|
||||
if (exclude?.Contains(uid) is true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -321,134 +321,38 @@ public sealed class CultRuleSystem : GameRuleSystem<CultRuleComponent>
|
||||
return potentialTargets;
|
||||
}
|
||||
|
||||
private List<ICommonSession> FindPotentialCultist(
|
||||
in Dictionary<ICommonSession, HumanoidCharacterProfile> candidates)
|
||||
public bool MakeCultist(EntityUid cultist, CultRuleComponent rule)
|
||||
{
|
||||
var list = new List<ICommonSession>();
|
||||
var pendingQuery = GetEntityQuery<PendingClockInComponent>();
|
||||
|
||||
foreach (var player in candidates.Keys)
|
||||
{
|
||||
// Gulag
|
||||
if (_gulag.IsUserGulaged(player.UserId, out _))
|
||||
continue;
|
||||
|
||||
// Role prevents antag.
|
||||
if (!_jobSystem.CanBeAntag(player))
|
||||
continue;
|
||||
|
||||
// Chaplain
|
||||
if (!_mindSystem.TryGetMind(player, out _, out var mind) ||
|
||||
mind.OwnedEntity is not { } ownedEntity || HasComp<HolyComponent>(ownedEntity))
|
||||
continue;
|
||||
|
||||
// Latejoin
|
||||
if (player.AttachedEntity != null && pendingQuery.HasComponent(player.AttachedEntity.Value))
|
||||
continue;
|
||||
|
||||
list.Add(player);
|
||||
}
|
||||
|
||||
var prefList = new List<ICommonSession>();
|
||||
|
||||
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;
|
||||
return prefList;
|
||||
}
|
||||
|
||||
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<ICommonSession> PickCultists(List<ICommonSession> prefList)
|
||||
{
|
||||
var result = new List<ICommonSession>();
|
||||
|
||||
var maxCultists = _cfg.GetCVar(WhiteCVars.CultMaxStartingPlayers);
|
||||
|
||||
if (prefList.Count < _minimalCultists)
|
||||
{
|
||||
_sawmill.Info("Insufficient ready players to fill up with cultists, stopping the selection.");
|
||||
return result;
|
||||
}
|
||||
|
||||
var actualCultistCount = prefList.Count > maxCultists ? maxCultists : _minimalCultists;
|
||||
|
||||
for (var i = 0; i < actualCultistCount; i++)
|
||||
{
|
||||
result.Add(_random.PickAndTake(prefList));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool MakeCultist(ICommonSession cultist)
|
||||
{
|
||||
var cultistRule = EntityQuery<CultRuleComponent>().FirstOrDefault();
|
||||
|
||||
if (cultistRule == null)
|
||||
{
|
||||
GameTicker.StartGameRule(CultRuleComponent.CultGamePresetPrototype, out var ruleEntity);
|
||||
cultistRule = Comp<CultRuleComponent>(ruleEntity);
|
||||
}
|
||||
|
||||
if (!_mindSystem.TryGetMind(cultist, out var mindId, out var mind))
|
||||
{
|
||||
Log.Info("Failed getting mind for picked cultist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mind.OwnedEntity is not { } playerEntity)
|
||||
if (HasComp<CultistRoleComponent>(mindId))
|
||||
{
|
||||
Log.Error("Mind picked for cultist did not have an attached entity.");
|
||||
Log.Error($"Player {mind.CharacterName} is already a cultist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var cultistComponent = new CultistRoleComponent
|
||||
var briefing = Loc.GetString("cult-role-greeting");
|
||||
_antagSelection.SendBriefing(cultist, briefing, null, rule.GreetingsSound);
|
||||
|
||||
_roleSystem.MindAddRole(mindId, new CultistRoleComponent
|
||||
{
|
||||
PrototypeId = cultistRule.CultistRolePrototype
|
||||
};
|
||||
PrototypeId = rule.CultistRolePrototype
|
||||
});
|
||||
|
||||
_roleSystem.MindAddRole(mindId, cultistComponent);
|
||||
EnsureComp<CultistComponent>(playerEntity);
|
||||
EnsureComp<CultistComponent>(cultist);
|
||||
|
||||
_factionSystem.RemoveFaction(playerEntity, "NanoTrasen", false);
|
||||
_factionSystem.AddFaction(playerEntity, "Cultist");
|
||||
_factionSystem.RemoveFaction(cultist, "NanoTrasen", false);
|
||||
_factionSystem.AddFaction(cultist, "Cultist");
|
||||
|
||||
if (_inventorySystem.TryGetSlotEntity(playerEntity, "back", out var backPack))
|
||||
if (_inventorySystem.TryGetSlotEntity(cultist, "back", out var backPack))
|
||||
{
|
||||
foreach (var itemPrototype in cultistRule.StartingItems)
|
||||
foreach (var itemPrototype in rule.StartingItems)
|
||||
{
|
||||
var itemEntity = Spawn(itemPrototype, Transform(playerEntity).Coordinates);
|
||||
var itemEntity = Spawn(itemPrototype, Transform(cultist).Coordinates);
|
||||
|
||||
if (backPack != null)
|
||||
{
|
||||
@@ -457,43 +361,11 @@ public sealed class CultRuleSystem : GameRuleSystem<CultRuleComponent>
|
||||
}
|
||||
}
|
||||
|
||||
// Notificate player about new role assignment
|
||||
if (_mindSystem.TryGetSession(mindId, out var session))
|
||||
{
|
||||
_audioSystem.PlayGlobal(cultistRule.GreatingsSound, session);
|
||||
_chatManager.DispatchServerMessage(session, Loc.GetString("cult-role-greeting"));
|
||||
}
|
||||
|
||||
_mindSystem.TryAddObjective(mindId, mind, "KillCultTargetObjective");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnNarsieSummon(CultNarsieSummoned ev)
|
||||
{
|
||||
foreach (var rule in EntityQuery<CultRuleComponent>())
|
||||
{
|
||||
rule.WinCondition = CultWinCondition.CultWin;
|
||||
}
|
||||
|
||||
_roundEndSystem.EndRound();
|
||||
|
||||
var query = EntityQueryEnumerator<MobStateComponent, MindContainerComponent, CultistComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out _, out var mindContainer, out _))
|
||||
{
|
||||
if (!mindContainer.HasMind || mindContainer.Mind is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var reaper = Spawn(CultRuleComponent.ReaperPrototype, Transform(uid).Coordinates);
|
||||
_mindSystem.TransferTo(mindContainer.Mind.Value, reaper);
|
||||
|
||||
_bodySystem.GibBody(uid);
|
||||
}
|
||||
}
|
||||
|
||||
public void TransferRole(EntityUid transferFrom, EntityUid transferTo)
|
||||
{
|
||||
if (HasComp<PentagramComponent>(transferFrom))
|
||||
@@ -507,6 +379,7 @@ public sealed class CultRuleSystem : GameRuleSystem<CultRuleComponent>
|
||||
{
|
||||
cultRule.CultistsCache.Remove(Name(transferFrom));
|
||||
}
|
||||
|
||||
EnsureComp<CultistComponent>(transferTo);
|
||||
RemComp<CultistComponent>(transferFrom);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
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._White.Cult.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 Content.Shared.Movement.Pulling.Systems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics;
|
||||
@@ -18,7 +18,6 @@ using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using CultistComponent = Content.Shared._White.Cult.Components.CultistComponent;
|
||||
|
||||
namespace Content.Server._White.Cult.Items.Systems;
|
||||
|
||||
@@ -36,6 +35,7 @@ public sealed class TorchCultistsProviderSystem : EntitySystem
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly PullingSystem _pulling = default!;
|
||||
[Dependency] private readonly MapSystem _map = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -104,18 +104,18 @@ public sealed class TorchCultistsProviderSystem : EntitySystem
|
||||
|
||||
provider.ItemSelected = args.Target;
|
||||
|
||||
var cultists = EntityQuery<CultistComponent>();
|
||||
var cultistsQuery = EntityQueryEnumerator<CultistComponent>();
|
||||
var list = new Dictionary<string, string>();
|
||||
|
||||
foreach (var cultist in cultists)
|
||||
while (cultistsQuery.MoveNext(out var cultistUid, out _))
|
||||
{
|
||||
if (!TryComp<MetaDataComponent>(cultist.Owner, out var meta))
|
||||
if (!TryComp<MetaDataComponent>(cultistUid, out var meta))
|
||||
return;
|
||||
|
||||
if (cultist.Owner == args.User)
|
||||
if (cultistUid == args.User)
|
||||
continue;
|
||||
|
||||
list.Add(meta.Owner.ToString(), meta.EntityName);
|
||||
list.Add(cultistUid.ToString(), meta.EntityName);
|
||||
}
|
||||
|
||||
if (list.Count == 0)
|
||||
@@ -138,12 +138,12 @@ public sealed class TorchCultistsProviderSystem : EntitySystem
|
||||
TorchWindowItemSelectedMessage args)
|
||||
{
|
||||
var entityUid = args.Session.AttachedEntity;
|
||||
var cultists = EntityQuery<CultistComponent>();
|
||||
var cultistsQuery = EntityQueryEnumerator<CultistComponent>();
|
||||
|
||||
foreach (var cultist in cultists)
|
||||
while (cultistsQuery.MoveNext(out var cultistUid, out _))
|
||||
{
|
||||
if (cultist.Owner.ToString() == args.EntUid)
|
||||
entityUid = cultist.Owner;
|
||||
if (cultistUid.ToString() == args.EntUid)
|
||||
entityUid = cultistUid;
|
||||
}
|
||||
|
||||
if (entityUid == args.Session.AttachedEntity && entityUid != null)
|
||||
@@ -182,7 +182,7 @@ public sealed class TorchCultistsProviderSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pulling.GetPulled(args.User) != args.Target.Value)
|
||||
if (_pulling.TryGetPulledEntity(args.User, out var pulled) || pulled != args.Target.Value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -226,7 +226,7 @@ public sealed class TorchCultistsProviderSystem : EntitySystem
|
||||
// don't spawn inside of solid objects
|
||||
var physQuery = GetEntityQuery<PhysicsComponent>();
|
||||
var valid = true;
|
||||
foreach (var ent in gridComp.GetAnchoredEntities(tile))
|
||||
foreach (var ent in _map.GetAnchoredEntities(grid, gridComp, tile))
|
||||
{
|
||||
if (!physQuery.TryGetComponent(ent, out var body))
|
||||
continue;
|
||||
@@ -243,7 +243,7 @@ public sealed class TorchCultistsProviderSystem : EntitySystem
|
||||
if (!valid)
|
||||
continue;
|
||||
|
||||
targetCoords = gridComp.GridTileToLocal(tile);
|
||||
targetCoords = _map.GridTileToLocal(grid, gridComp, tile);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
using System.Threading;
|
||||
using Content.Shared._White.Cult.Components;
|
||||
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 Content.Shared.Movement.Pulling.Systems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using CultistComponent = Content.Shared._White.Cult.Components.CultistComponent;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Server._White.Cult.Items.Systems;
|
||||
@@ -27,6 +27,10 @@ public sealed class VoidTeleportSystem : EntitySystem
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly PointLightSystem _pointLight = default!;
|
||||
[Dependency] private readonly PullingSystem _pulling = default!;
|
||||
[Dependency] private readonly RobustRandom _random = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -75,7 +79,7 @@ public sealed class VoidTeleportSystem : EntitySystem
|
||||
{
|
||||
attempts--;
|
||||
//Get coords to where tp
|
||||
var random = new Random().Next(component.MinRange, component.MaxRange);
|
||||
var random = _random.Next(component.MinRange, component.MaxRange);
|
||||
var offset = transform.LocalRotation.ToWorldVec().Normalized();
|
||||
var direction = transform.LocalRotation.GetDir().ToVec();
|
||||
var newOffset = offset + direction * random;
|
||||
@@ -97,20 +101,17 @@ public sealed class VoidTeleportSystem : EntitySystem
|
||||
CreatePulse(uid, component);
|
||||
|
||||
_xform.SetCoordinates(args.User, coords);
|
||||
transform.AttachToGridOrMap();
|
||||
_transform.AttachToGridOrMap(args.User, transform);
|
||||
|
||||
var pulled = GetPulledEntity(args.User);
|
||||
if (pulled != null)
|
||||
if (_pulling.TryGetPulledEntity(args.User, out var pulled))
|
||||
{
|
||||
_xform.SetCoordinates(pulled.Value, coords);
|
||||
|
||||
if (TryComp<TransformComponent>(pulled.Value, out var pulledTransform))
|
||||
pulledTransform.AttachToGridOrMap();
|
||||
_transform.AttachToGridOrMap(pulled.Value);
|
||||
}
|
||||
|
||||
//Play tp sound
|
||||
_audio.PlayPvs(component.TeleportInSound, coords);
|
||||
_audio.PlayPvs(component.TeleportOutSound,oldCoords);
|
||||
_audio.PlayPvs(component.TeleportOutSound, oldCoords);
|
||||
|
||||
//Create tp effect
|
||||
_entMan.SpawnEntity(component.TeleportInEffect, coords);
|
||||
@@ -129,45 +130,31 @@ public sealed class VoidTeleportSystem : EntitySystem
|
||||
_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))
|
||||
#pragma warning disable RA0002
|
||||
light.Energy = 5f;
|
||||
#pragma warning restore RA0002
|
||||
if (_pointLight.TryGetLight(uid, out var light))
|
||||
{
|
||||
_pointLight.SetEnergy(uid, 5f, light);
|
||||
}
|
||||
|
||||
Timer.Spawn(component.TimerDelay, () => TurnOffPulse(uid, component), component.Token.Token);
|
||||
}
|
||||
|
||||
private void TurnOffPulse(EntityUid uid ,VoidTeleportComponent comp)
|
||||
private void TurnOffPulse(EntityUid uid, VoidTeleportComponent comp)
|
||||
{
|
||||
if (!TryComp<PointLightComponent>(uid, out var light))
|
||||
if (!_pointLight.TryGetLight(uid, out var light))
|
||||
return;
|
||||
|
||||
#pragma warning disable RA0002
|
||||
light.Energy = 1f;
|
||||
#pragma warning restore RA0002
|
||||
_pointLight.SetEnergy(uid, 1f, light);
|
||||
|
||||
comp.Token = new CancellationTokenSource();
|
||||
|
||||
if (comp.UsesLeft <= 0)
|
||||
{
|
||||
comp.Active = false;
|
||||
UpdateAppearance(uid, comp);
|
||||
_pointLight.SetEnabled(uid, false);
|
||||
|
||||
#pragma warning disable RA0002
|
||||
light.Enabled = false;
|
||||
#pragma warning restore RA0002
|
||||
UpdateAppearance(uid, comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Threading;
|
||||
using Content.Shared._White.Cult;
|
||||
using Content.Shared._White.Cult;
|
||||
using Robust.Server.GameObjects;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Pulling.Components;
|
||||
using Content.Shared.Rejuvenate;
|
||||
using Content.Shared._White.Cult;
|
||||
using Content.Shared._White.Cult.Components;
|
||||
@@ -37,7 +36,8 @@ using Content.Shared._White.Cult.Runes;
|
||||
using Content.Shared._White.Cult.UI;
|
||||
using Content.Shared.Cuffs;
|
||||
using Content.Shared.Mindshield.Components;
|
||||
using Content.Shared.Pulling;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Movement.Pulling.Systems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
@@ -66,11 +66,10 @@ public sealed partial class CultSystem : EntitySystem
|
||||
[Dependency] private readonly GunSystem _gunSystem = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly FlammableSystem _flammableSystem = default!;
|
||||
[Dependency] private readonly SharedPullingSystem _pulling = default!;
|
||||
[Dependency] private readonly PullingSystem _pulling = default!;
|
||||
[Dependency] private readonly SharedCuffableSystem _cuffable = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -113,8 +112,10 @@ public sealed partial class CultSystem : EntitySystem
|
||||
InitializeConstructs();
|
||||
InitializeBarrierSystem();
|
||||
InitializeConstructsAbilities();
|
||||
InitializeCultists();
|
||||
InitializeActions();
|
||||
InitializeVerb();
|
||||
|
||||
}
|
||||
|
||||
private float _timeToDraw;
|
||||
@@ -139,7 +140,7 @@ public sealed partial class CultSystem : EntitySystem
|
||||
private readonly SoundPathSpecifier _narsie40Sec = new("/Audio/White/Cult/40sec.ogg");
|
||||
|
||||
/*
|
||||
* Rune draw start ----
|
||||
* Rune draw start ----
|
||||
*/
|
||||
|
||||
private void OnRuneDrawerUseInHand(EntityUid uid, RuneDrawerProviderComponent component, UseInHandEvent args)
|
||||
@@ -168,17 +169,13 @@ public sealed partial class CultSystem : EntitySystem
|
||||
var runePrototype = args.SelectedItem;
|
||||
var whoCalled = args.Session.AttachedEntity.Value;
|
||||
|
||||
if (!TryComp<ActorComponent>(whoCalled, out var actorComponent))
|
||||
if (!TryComp<ActorComponent>(whoCalled, out _))
|
||||
return;
|
||||
|
||||
if (!TryDraw(whoCalled, runePrototype))
|
||||
return;
|
||||
|
||||
/*if (component.UserInterface != null)
|
||||
_ui.CloseUi(component.UserInterface, actorComponent.PlayerSession);*/
|
||||
TryDraw(whoCalled, runePrototype);
|
||||
}
|
||||
|
||||
private bool TryDraw(EntityUid whoCalled, string runePrototype)
|
||||
private void TryDraw(EntityUid whoCalled, string runePrototype)
|
||||
{
|
||||
_timeToDraw = 4f;
|
||||
|
||||
@@ -193,7 +190,7 @@ public sealed partial class CultSystem : EntitySystem
|
||||
}
|
||||
|
||||
if (!IsAllowedToDraw(whoCalled))
|
||||
return false;
|
||||
return;
|
||||
|
||||
var ev = new CultDrawEvent
|
||||
{
|
||||
@@ -202,15 +199,14 @@ public sealed partial class CultSystem : EntitySystem
|
||||
|
||||
var argsDoAfterEvent = new DoAfterArgs(_entityManager, whoCalled, _timeToDraw, ev, whoCalled)
|
||||
{
|
||||
BreakOnUserMove = true,
|
||||
BreakOnMove = true,
|
||||
NeedHand = true
|
||||
};
|
||||
|
||||
if (!_doAfterSystem.TryStartDoAfter(argsDoAfterEvent))
|
||||
return false;
|
||||
return;
|
||||
|
||||
_audio.PlayPvs("/Audio/White/Cult/butcher.ogg", whoCalled, AudioParams.Default.WithMaxDistance(2f));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnDraw(EntityUid uid, CultistComponent comp, CultDrawEvent args)
|
||||
@@ -272,7 +268,7 @@ public sealed partial class CultSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
var entityPrototype = _entityManager.GetComponent<MetaDataComponent>(args.Used).EntityPrototype;
|
||||
var entityPrototype = MetaData(args.Used).EntityPrototype;
|
||||
|
||||
if (entityPrototype == null)
|
||||
return;
|
||||
@@ -300,7 +296,7 @@ public sealed partial class CultSystem : EntitySystem
|
||||
|
||||
var argsDoAfterEvent = new DoAfterArgs(_entityManager, user, time, ev, target)
|
||||
{
|
||||
BreakOnUserMove = true,
|
||||
BreakOnMove = true,
|
||||
NeedHand = true
|
||||
};
|
||||
|
||||
@@ -340,7 +336,7 @@ public sealed partial class CultSystem : EntitySystem
|
||||
//Erasing end
|
||||
|
||||
/*
|
||||
* Rune draw end ----
|
||||
* Rune draw end ----
|
||||
*/
|
||||
|
||||
//------------------------------------------//
|
||||
@@ -395,8 +391,8 @@ public sealed partial class CultSystem : EntitySystem
|
||||
}
|
||||
|
||||
/*
|
||||
* Base End ----
|
||||
*/
|
||||
* Base End ----
|
||||
*/
|
||||
|
||||
//------------------------------------------//
|
||||
|
||||
@@ -432,7 +428,7 @@ public sealed partial class CultSystem : EntitySystem
|
||||
if (state.CurrentState != MobState.Dead)
|
||||
{
|
||||
var canBeConverted = _entityManager.TryGetComponent<MindContainerComponent>(victim.Value, out var mind) &&
|
||||
mind is {Mind: { }};
|
||||
mind is { Mind: { } };
|
||||
|
||||
// Проверка, является ли жертва целью
|
||||
_entityManager.TryGetComponent<MindContainerComponent>(target?.CurrentEntity, out var targetMind);
|
||||
@@ -551,7 +547,7 @@ public sealed partial class CultSystem : EntitySystem
|
||||
//------------------------------------------//
|
||||
|
||||
/*
|
||||
* Buff Rune Start ----
|
||||
* Buff Rune Start ----
|
||||
*/
|
||||
|
||||
private void OnInvokeBuff(EntityUid uid, CultRuneBuffComponent component, CultRuneInvokeEvent args)
|
||||
@@ -596,13 +592,13 @@ public sealed partial class CultSystem : EntitySystem
|
||||
}
|
||||
|
||||
/*
|
||||
* Empower Rune End ----
|
||||
* Empower Rune End ----
|
||||
*/
|
||||
|
||||
//------------------------------------------//
|
||||
|
||||
/*
|
||||
* Teleport rune start ----
|
||||
* Teleport rune start ----
|
||||
*/
|
||||
|
||||
private void OnInvokeTeleport(EntityUid uid, CultRuneTeleportComponent component, CultRuneInvokeEvent args)
|
||||
@@ -620,22 +616,19 @@ public sealed partial class CultSystem : EntitySystem
|
||||
|
||||
private bool Teleport(EntityUid rune, EntityUid user, List<EntityUid>? victims = null)
|
||||
{
|
||||
var runes = EntityQuery<CultRuneTeleportComponent>();
|
||||
var runesQuery = EntityQueryEnumerator<CultRuneTeleportComponent>();
|
||||
var list = new List<int>();
|
||||
var labels = new List<string>();
|
||||
|
||||
foreach (var teleportRune in runes)
|
||||
while (runesQuery.MoveNext(out var runeUid, out var teleportComponent))
|
||||
{
|
||||
if (!TryComp<CultRuneTeleportComponent>(teleportRune.Owner, out var teleportComponent))
|
||||
continue;
|
||||
|
||||
if (teleportComponent.Label == null)
|
||||
continue;
|
||||
|
||||
if (teleportRune.Owner == rune)
|
||||
if (runeUid == rune)
|
||||
continue;
|
||||
|
||||
if (!int.TryParse(teleportRune.Owner.ToString(), out var intValue))
|
||||
if (!int.TryParse(runeUid.ToString(), out var intValue))
|
||||
continue;
|
||||
|
||||
list.Add(intValue);
|
||||
@@ -707,13 +700,13 @@ public sealed partial class CultSystem : EntitySystem
|
||||
}
|
||||
|
||||
/*
|
||||
* Teleport rune end ----
|
||||
* Teleport rune end ----
|
||||
*/
|
||||
|
||||
//------------------------------------------//
|
||||
|
||||
/*
|
||||
* Apocalypse rune start ----
|
||||
* Apocalypse rune start ----
|
||||
*/
|
||||
|
||||
private void OnInvokeApocalypse(EntityUid uid, CultRuneApocalypseComponent component, CultRuneInvokeEvent args)
|
||||
@@ -756,7 +749,7 @@ public sealed partial class CultSystem : EntitySystem
|
||||
|
||||
var argsDoAfterEvent = new DoAfterArgs(_entityManager, user, TimeSpan.FromSeconds(40), ev, user)
|
||||
{
|
||||
BreakOnUserMove = true
|
||||
BreakOnMove = true
|
||||
};
|
||||
|
||||
if (!_doAfterSystem.TryStartDoAfter(argsDoAfterEvent))
|
||||
@@ -769,7 +762,8 @@ public sealed partial class CultSystem : EntitySystem
|
||||
_chat.DispatchGlobalAnnouncement(Loc.GetString("cult-ritual-started"), "CULT", false,
|
||||
colorOverride: Color.DarkRed);
|
||||
|
||||
_audio.PlayGlobal(_narsie40Sec, Filter.Broadcast(), false, AudioParams.Default.WithLoop(true).WithVolume(0.15f));
|
||||
_audio.PlayGlobal(_narsie40Sec, Filter.Broadcast(), false,
|
||||
AudioParams.Default.WithLoop(true).WithVolume(0.15f));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -800,14 +794,14 @@ public sealed partial class CultSystem : EntitySystem
|
||||
}
|
||||
|
||||
/*
|
||||
* Apocalypse rune end ----
|
||||
* Apocalypse rune end ----
|
||||
*/
|
||||
|
||||
//------------------------------------------//
|
||||
|
||||
/*
|
||||
* Revive rune start ----
|
||||
*/
|
||||
* Revive rune start ----
|
||||
*/
|
||||
|
||||
private void OnInvokeRevive(EntityUid uid, CultRuneReviveComponent component, CultRuneInvokeEvent args)
|
||||
{
|
||||
@@ -856,13 +850,13 @@ public sealed partial class CultSystem : EntitySystem
|
||||
}
|
||||
|
||||
/*
|
||||
* Revive rune end ----
|
||||
*/
|
||||
* Revive rune end ----
|
||||
*/
|
||||
|
||||
//------------------------------------------//
|
||||
|
||||
/*
|
||||
* Barrier rune start ----
|
||||
* Barrier rune start ----
|
||||
*/
|
||||
|
||||
private void OnInvokeBarrier(EntityUid uid, CultRuneBarrierComponent component, CultRuneInvokeEvent args)
|
||||
@@ -884,14 +878,14 @@ public sealed partial class CultSystem : EntitySystem
|
||||
}
|
||||
|
||||
/*
|
||||
* Barrier rune end ----
|
||||
*/
|
||||
* Barrier rune end ----
|
||||
*/
|
||||
|
||||
//------------------------------------------//
|
||||
|
||||
/*
|
||||
* Summoning rune start ----
|
||||
*/
|
||||
* Summoning rune start ----
|
||||
*/
|
||||
|
||||
private void OnInvokeSummoning(EntityUid uid, CultRuneSummoningComponent component, CultRuneInvokeEvent args)
|
||||
{
|
||||
@@ -904,7 +898,7 @@ public sealed partial class CultSystem : EntitySystem
|
||||
HashSet<EntityUid> cultistHashSet,
|
||||
CultRuneSummoningComponent component)
|
||||
{
|
||||
var cultists = EntityQuery<CultistComponent>();
|
||||
var cultistsQuery = EntityQueryEnumerator<CultistComponent>();
|
||||
var list = new List<int>();
|
||||
var labels = new List<string>();
|
||||
|
||||
@@ -914,15 +908,13 @@ public sealed partial class CultSystem : EntitySystem
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var cultist in cultists)
|
||||
while (cultistsQuery.MoveNext(out var cultistUid, out _))
|
||||
{
|
||||
if (!TryComp<MetaDataComponent>(cultist.Owner, out var meta))
|
||||
var meta = MetaData(cultistUid);
|
||||
if (cultistHashSet.Contains(cultistUid))
|
||||
continue;
|
||||
|
||||
if (cultistHashSet.Contains(cultist.Owner))
|
||||
continue;
|
||||
|
||||
if (!int.TryParse(cultist.Owner.ToString(), out var intValue))
|
||||
if (!int.TryParse(cultistUid.ToString(), out var intValue))
|
||||
continue;
|
||||
|
||||
list.Add(intValue);
|
||||
@@ -964,7 +956,7 @@ public sealed partial class CultSystem : EntitySystem
|
||||
var target = new EntityUid(args.SelectedItem);
|
||||
var baseRune = component.BaseRune;
|
||||
|
||||
if (!TryComp<SharedPullableComponent>(target, out var pullableComponent))
|
||||
if (!TryComp<PullableComponent>(target, out var pullableComponent))
|
||||
return;
|
||||
|
||||
if (!TryComp<CuffableComponent>(target, out var cuffableComponent))
|
||||
@@ -1004,14 +996,14 @@ public sealed partial class CultSystem : EntitySystem
|
||||
}
|
||||
|
||||
/*
|
||||
* Summoning rune end ----
|
||||
*/
|
||||
* Summoning rune end ----
|
||||
*/
|
||||
|
||||
//------------------------------------------//
|
||||
|
||||
/*
|
||||
* BloodBoil rune start ----
|
||||
*/
|
||||
* BloodBoil rune start ----
|
||||
*/
|
||||
|
||||
private void OnInvokeBloodBoil(EntityUid uid, CultRuneBloodBoilComponent component, CultRuneInvokeEvent args)
|
||||
{
|
||||
@@ -1126,14 +1118,14 @@ public sealed partial class CultSystem : EntitySystem
|
||||
}
|
||||
|
||||
/*
|
||||
* BloodBoil rune end ----
|
||||
*/
|
||||
* BloodBoil rune end ----
|
||||
*/
|
||||
|
||||
//------------------------------------------//
|
||||
|
||||
/*
|
||||
* Empower rune start ----
|
||||
*/
|
||||
* Empower rune start ----
|
||||
*/
|
||||
|
||||
private void OnActiveInWorld(EntityUid uid, CultEmpowerComponent component, ActivateInWorldEvent args)
|
||||
{
|
||||
@@ -1183,13 +1175,13 @@ public sealed partial class CultSystem : EntitySystem
|
||||
}
|
||||
|
||||
/*
|
||||
* Empower rune end ----
|
||||
*/
|
||||
* Empower rune end ----
|
||||
*/
|
||||
|
||||
//------------------------------------------//
|
||||
|
||||
/*
|
||||
* Helpers Start ----
|
||||
* Helpers Start ----
|
||||
*/
|
||||
|
||||
private EntityUid? FindNearestTarget(EntityUid uid, List<EntityUid> targets)
|
||||
@@ -1265,7 +1257,7 @@ public sealed partial class CultSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
var pos = transComp.MapPosition;
|
||||
var pos = _transform.GetMapCoordinates(uid, transComp);
|
||||
var x = (int) pos.X;
|
||||
var y = (int) pos.Y;
|
||||
var posText = $"(x = {x}, y = {y})";
|
||||
@@ -1339,20 +1331,19 @@ public sealed partial class CultSystem : EntitySystem
|
||||
private void StopPulling(EntityUid target, bool checkPullable = true)
|
||||
{
|
||||
// break pulls before portal enter so we dont break shit
|
||||
if (checkPullable && TryComp<SharedPullableComponent>(target, out var pullable) && pullable.BeingPulled)
|
||||
if (checkPullable && TryComp<PullableComponent>(target, out var pullable) && pullable.BeingPulled)
|
||||
{
|
||||
_pulling.TryStopPull(pullable);
|
||||
_pulling.TryStopPull(target, pullable);
|
||||
}
|
||||
|
||||
if (TryComp<SharedPullerComponent>(target, out var pulling)
|
||||
&& pulling.Pulling != null &&
|
||||
TryComp<SharedPullableComponent>(pulling.Pulling.Value, out var subjectPulling))
|
||||
if (_pulling.TryGetPulledEntity(target, out var pulledEntity)
|
||||
&& TryComp(pulledEntity, out PullableComponent? subjectPulling))
|
||||
{
|
||||
_pulling.TryStopPull(subjectPulling);
|
||||
_pulling.TryStopPull(pulledEntity.Value, subjectPulling);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Helpers End ----
|
||||
* Helpers End ----
|
||||
*/
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
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 Content.Shared.Pulling;
|
||||
using Content.Shared.Pulling.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Movement.Pulling.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._White.Cult.UI;
|
||||
@@ -16,45 +13,37 @@ namespace Content.Server._White.Cult.UI;
|
||||
public sealed class TeleportSpellEui : BaseEui
|
||||
{
|
||||
[Dependency] private readonly EntityManager _entityManager = default!;
|
||||
private SharedTransformSystem _transformSystem;
|
||||
private SharedPullingSystem _pulling;
|
||||
private PopupSystem _popupSystem;
|
||||
private readonly SharedTransformSystem _transformSystem;
|
||||
private readonly PullingSystem _pulling;
|
||||
private readonly PopupSystem _popupSystem;
|
||||
|
||||
|
||||
private EntityUid _performer;
|
||||
private EntityUid _target;
|
||||
|
||||
private EntityCoordinates _initialOwnerCoords;
|
||||
private EntityCoordinates _initialTargetCoords;
|
||||
private readonly EntityUid _performer;
|
||||
private readonly EntityUid _target;
|
||||
|
||||
private bool _used;
|
||||
|
||||
|
||||
public TeleportSpellEui(EntityUid performer, EntityUid target)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_transformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
_pulling = _entityManager.System<SharedPullingSystem>();
|
||||
_pulling = _entityManager.System<PullingSystem>();
|
||||
_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 );
|
||||
Timer.Spawn(TimeSpan.FromSeconds(10), Close);
|
||||
}
|
||||
|
||||
public override EuiStateBase GetNewState()
|
||||
{
|
||||
var runes = _entityManager.EntityQuery<CultRuneTeleportComponent>();
|
||||
var runesQuery = _entityManager.EntityQueryEnumerator<CultRuneTeleportComponent>();
|
||||
var state = new TeleportSpellEuiState();
|
||||
|
||||
foreach (var rune in runes)
|
||||
while (runesQuery.MoveNext(out var runeUid, out var rune))
|
||||
{
|
||||
state.Runes.Add((int)rune.Owner, rune.Label!);
|
||||
state.Runes.Add((int) runeUid, rune.Label!);
|
||||
}
|
||||
|
||||
return state;
|
||||
@@ -64,7 +53,10 @@ public sealed class TeleportSpellEui : BaseEui
|
||||
{
|
||||
base.HandleMessage(msg);
|
||||
|
||||
if(_used) return;
|
||||
if (_used)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg is not TeleportSpellTargetRuneSelected cast)
|
||||
{
|
||||
@@ -72,23 +64,24 @@ public sealed class TeleportSpellEui : BaseEui
|
||||
}
|
||||
|
||||
var performerPosition = _entityManager.GetComponent<TransformComponent>(_performer).Coordinates;
|
||||
var targetPosition = _entityManager.GetComponent<TransformComponent>(_target).Coordinates;;
|
||||
var targetPosition = _entityManager.GetComponent<TransformComponent>(_target).Coordinates;
|
||||
|
||||
performerPosition.TryDistance(_entityManager, targetPosition, out var distance);
|
||||
|
||||
if(distance > 1.5f)
|
||||
if (distance > 1.5f)
|
||||
{
|
||||
_popupSystem.PopupEntity("Too far", _performer, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
TransformComponent? runeTransform = null!;
|
||||
TransformComponent? runeTransform = null;
|
||||
|
||||
foreach (var runeComponent in _entityManager.EntityQuery<CultRuneTeleportComponent>())
|
||||
var teleportRuneQuery = _entityManager.EntityQueryEnumerator<CultRuneTeleportComponent, TransformComponent>();
|
||||
while (teleportRuneQuery.MoveNext(out var runeUid, out _, out var transformComponent))
|
||||
{
|
||||
if (runeComponent.Owner == new EntityUid(cast.RuneUid))
|
||||
if (runeUid == new EntityUid(cast.RuneUid))
|
||||
{
|
||||
runeTransform = _entityManager.GetComponent<TransformComponent>(runeComponent.Owner);
|
||||
runeTransform = transformComponent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,16 +95,16 @@ public sealed class TeleportSpellEui : BaseEui
|
||||
_used = true;
|
||||
|
||||
// break pulls before portal enter so we dont break shit
|
||||
if (_entityManager.TryGetComponent<SharedPullableComponent>(_target, out var pullable) && pullable.BeingPulled)
|
||||
if (_entityManager.TryGetComponent<PullableComponent>(_target, out var pullable) && pullable.BeingPulled)
|
||||
{
|
||||
_pulling.TryStopPull(pullable);
|
||||
_pulling.TryStopPull(_target, pullable);
|
||||
}
|
||||
|
||||
if (_entityManager.TryGetComponent<SharedPullerComponent>(_target, out var pulling)
|
||||
&& pulling.Pulling != null &&
|
||||
_entityManager.TryGetComponent<SharedPullableComponent>(pulling.Pulling.Value, out var subjectPulling))
|
||||
if (_entityManager.TryGetComponent<PullerComponent>(_target, out var pulling)
|
||||
&& pulling.Pulling != null
|
||||
&& _entityManager.TryGetComponent<PullableComponent>(pulling.Pulling.Value, out var subjectPulling))
|
||||
{
|
||||
_pulling.TryStopPull(subjectPulling);
|
||||
_pulling.TryStopPull(pulling.Pulling.Value, subjectPulling);
|
||||
}
|
||||
|
||||
_transformSystem.SetCoordinates(_target, runeTransform.Coordinates);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Administration.Logs;
|
||||
@@ -273,6 +274,23 @@ public sealed class PullingSystem : EntitySystem
|
||||
return false;
|
||||
}
|
||||
|
||||
// WD ADDED
|
||||
public bool TryGetPulledEntity(
|
||||
EntityUid puller,
|
||||
[NotNullWhen(true)] out EntityUid? pulled,
|
||||
PullerComponent? component = null)
|
||||
{
|
||||
pulled = null;
|
||||
if (!Resolve(puller, ref component))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pulled = component.Pulling;
|
||||
return pulled is not null;
|
||||
}
|
||||
// WD ADDED END
|
||||
|
||||
public bool IsPulling(EntityUid puller, PullerComponent? component = null)
|
||||
{
|
||||
return Resolve(puller, ref component, false) && component.Pulling != null;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
artificer-create-soul-stone-action-name = Создать камень души.
|
||||
artificer-create-soul-stone-action-description = Это заклинание проникает в царство Нар-Си, вызывая один из легендарных фрагментов через время и пространство.
|
||||
artificer-create-construct-shell-action-name = Создать оболочку конструкта.
|
||||
artificer-create-construct-shell-action-description = Это заклинание проникает в царство Нар-Си, вызывая один из легендарных фрагментов через время и пространство.
|
||||
artificer-convert-cultist-floor-action-name = Создать культистский пол.
|
||||
convert-cultist-floor-action-description = Это заклинание возводит под вами культистский пол.
|
||||
artificer-create-cultist-wall-action-name = Создать стену культа.
|
||||
artificer-create-cultist-wall-action-description = Это заклинание возводит стену культа.
|
||||
artificer-create-cultist-airlock-action-name = Создать шлюз культа.
|
||||
artificer-create-cultist-airlock-action-description = Это заклинание возводит шлюз культа.
|
||||
wraith-phase-action-name = Фазовый Сдвиг
|
||||
wraith-phase-action-description = Это заклинание позволяет проходить сквозь стены, подобно бесплотному полету волшебника.
|
||||
juggernaut-create-wall-action-name = Щит
|
||||
juggernaut-create-wall-action-description = Это заклинание создает временное, невидимое, силовое поле для защиты себя и союзников от подавляющего огня.
|
||||
@@ -1,2 +0,0 @@
|
||||
ent-CultClothingBlindfold = повязка Зилота
|
||||
.desc = Повязка, наделенная странной силой.
|
||||
@@ -1,3 +0,0 @@
|
||||
ghost-role-information-soul-shard-name = Осколок Души
|
||||
ghost-role-information-soul-shard-description = Станьте слугой кровавого культа!
|
||||
ghost-role-information-soul-shard-rules = Примите форму одной из конструкций культа и помогите вашим Хозяевам вернуть Нар'Си в этот мир!
|
||||
@@ -1,21 +0,0 @@
|
||||
ent-CultRunicMetal = рунический металл
|
||||
.desc = Необычный лист металла с пульсирующей руной.
|
||||
.suffix = Полный
|
||||
ent-CultRunicMetal1 = рунический металл
|
||||
.desc = Необычный лист металла с пульсирующей руной.
|
||||
.suffix = Один
|
||||
ent-CultRunicMetal20 = рунический металл
|
||||
.desc = Необычный лист металла с пульсирующей руной.
|
||||
.suffix = 20
|
||||
ent-CultBloodAltar = алтарь
|
||||
.desc = Кровавый алтарь, посвященный какому-то существу.
|
||||
ent-CultForge = кузница
|
||||
.desc = Кузница, в которой изготавливается нечестивое оружие.
|
||||
ent-CultGirder = руническая балка
|
||||
.desc = Большой конструктивный элемент, изготовленный из металла. На этом есть руна.
|
||||
ent-AirlockGlassCult = рунический шлюз
|
||||
.desc = Странный стеклянный шлюз с руной.
|
||||
cult-structure-craft-not-enough-metal = Недостаточно металла.
|
||||
cult-structure-craft-craft-failed = Не удалось начать постройку.
|
||||
cult-structure-craft-blocked = Что-то мешает построить.
|
||||
cult-structure-craft-another-structure-nearby = Слишком близко к другой такой постройке.
|
||||
@@ -1,11 +0,0 @@
|
||||
ent-OfferingRune = руна предпонесения
|
||||
ent-BuffRune = руна усиления
|
||||
ent-EmpoweringRune = руна могущества
|
||||
ent-TeleportRune = руна телепортации
|
||||
ent-SummoningRune = руна призыва
|
||||
ent-ReviveRune = руна воскрешения
|
||||
ent-BarrierRune = руна барьера
|
||||
ent-BloodBoilRune = руна кипящей крови
|
||||
ent-ApocalypseRune = ритуал пространственного разрыва
|
||||
|
||||
runes-window-title = Руны
|
||||
@@ -1,8 +0,0 @@
|
||||
shuttle-curse-not-cultist = Сфера выпадает из ваших рук
|
||||
shuttle-curse-shuttle-not-called = Сфера доносит вам, что шаттл не вызван
|
||||
shuttle-curse-max-curses = Сфера доносит, что шаттл больше нельзя проклясть
|
||||
shuttle-curse-cooldown = Сфера доносит, что ей нужно собрать силы на прокльяте
|
||||
shuttle-curse-shuttle-arrived = Сфера доносит, что уже слишком поздно
|
||||
shuttle-curse-shuttle-delayed = Сфера доносит, что прокльяте наложено
|
||||
ent-CultShuttleCurse = сфера проклятия
|
||||
.desc = Cтранная каменная сфера, пульсирующая красным светом.
|
||||
@@ -1,16 +1,23 @@
|
||||
artificer-create-soul-stone-action-name = Создать камень души.
|
||||
artificer-create-soul-stone-action-description = Это заклинание проникает в царство Нар-Си, вызывая один из легендарных фрагментов через время и пространство.
|
||||
|
||||
artificer-create-construct-shell-action-name = Создать оболочку конструкта.
|
||||
artificer-create-construct-shell-action-description = Это заклинание проникает в царство Нар-Си, вызывая один из легендарных фрагментов через время и пространство.
|
||||
artificer-convert-cultist-floor-action-name = Создать культистский пол.
|
||||
convert-cultist-floor-action-description = Это заклинание возводит под вами культистский пол.
|
||||
artificer-create-cultist-wall-action-name = Создать культистскую стену.
|
||||
artificer-create-cultist-wall-action-description = Это заклинание возводит культистскую стену.
|
||||
|
||||
artificer-convert-cultist-floor-action-name = Создать пол культа.
|
||||
artificer-convert-cultist-floor-action-description = Это заклинание возводит под вами пол культа.
|
||||
|
||||
artificer-create-cultist-wall-action-name = Создать стену культа.
|
||||
artificer-create-cultist-wall-action-description = Это заклинание возводит стену культа.
|
||||
|
||||
artificer-create-cultist-airlock-action-name = Создать шлюз культа.
|
||||
artificer-create-cultist-airlock-action-description = Это заклинание возводит шлюз культа.
|
||||
|
||||
wraith-phase-action-name = Фазовый Сдвиг
|
||||
wraith-phase-action-description = Это заклинание позволяет проходить сквозь стены, подобно бесплотному полету волшебника.
|
||||
juggernaut-create-wall-action-name = Щит
|
||||
juggernaut-create-wall-action-description = Это заклинание создает временное, невидимое, силовое поле для защиты себя и союзников от подавляющего огня.
|
||||
|
||||
juggernaut-create-wall-action-name = Щит
|
||||
juggernaut-create-wall-action-description = Это заклинание создает временное невидимое силовое поле для защиты себя и союзников от подавляющего огня.
|
||||
|
||||
ent-ActionCultTwistedConstruction = Искажённое Воздействие
|
||||
.desc = Зловещее заклинание, которое используют для превращения металла в рунический металл.
|
||||
@@ -1,19 +1,11 @@
|
||||
cult-role-greeting =
|
||||
Вы - член культа!
|
||||
Ваши цели перечислены в меню персонажа.
|
||||
В ваш рюкзак были добавлены предметы, которые помогут вам.
|
||||
И помните - вы не единственный.
|
||||
Слава Нар`си!
|
||||
|
||||
cult-cond-cultwin = Культ одержал победу
|
||||
cult-cond-cultfailure = Экипаж уничтожил культ
|
||||
|
||||
cultists-list-start = Культистами были:
|
||||
cultists-list-name = - [color=White]{ $name }[/color] ([color=gray]{ $user }[/color])
|
||||
soul-shard-name = Душа { $soul }
|
||||
soul-shard-name = Душа { $soul }
|
||||
soul-shard-description = В этом камне заключена душа { $soul }
|
||||
cult-too-much-empowers = Слишком много способностей
|
||||
|
||||
ghost-role-information-soul-shard-name = Осколок Души
|
||||
ghost-role-information-soul-shard-description = Станьте слугой кровавого культа!
|
||||
ghost-role-information-soul-shard-rules = Примите форму одной из конструкций культа и помогите вашим Хозяевам вернуть Нар'Си в этот мир!
|
||||
|
||||
cult-started-drawing-rune-end = Культ начал рисовать руну призыва!
|
||||
cult-started-erasing-rune = Вы начали стирать руну.
|
||||
cult-erased-rune = Вы стёрли руну.
|
||||
@@ -42,17 +34,7 @@ cult-teleport-rune-default-label = безымянная метка
|
||||
cult-narsie-summon-drawn-position = Культ закончил рисовать руну ритуала разрыва измерений! Координаты: { $posText }
|
||||
cult-cant-draw-rune = Нельзя рисовать руну в космосе.
|
||||
|
||||
ent-SoulShard = камень душ
|
||||
.desc = Мистический светящийся осколок.
|
||||
|
||||
ent-SoulShardGhost = камень душ
|
||||
.desc = Мистический светящийся осколок.
|
||||
|
||||
ent-WetStone = точильный камень
|
||||
.desc = Используется для заточки кромок стальных инструментов.
|
||||
|
||||
ent-CultSharpener = древний точильный камень
|
||||
.desc = Используется для заточки кромок стальных инструментов.
|
||||
runes-window-title = Руны
|
||||
|
||||
chat-manager-cult-channel-name = Культ
|
||||
chat-manager-send-cult-chat-wrap-message = [bold]\[{ $channelName }\] [BubbleHeader]{ $player }[/BubbleHeader]:[/bold] [BubbleContent]{ $message }[/BubbleContent]
|
||||
57
Resources/Locale/ru-RU/_white/cult/entities.ftl
Normal file
57
Resources/Locale/ru-RU/_white/cult/entities.ftl
Normal file
@@ -0,0 +1,57 @@
|
||||
ent-SoulShard = камень душ
|
||||
.desc = Мистический светящийся осколок.
|
||||
|
||||
ent-SoulShardGhost = камень душ
|
||||
.desc = Мистический светящийся осколок.
|
||||
.suffix = Роль призраков
|
||||
|
||||
ent-WetStone = точильный камень
|
||||
.desc = Используется для заточки кромок стальных инструментов.
|
||||
|
||||
ent-CultSharpener = древний точильный камень
|
||||
.desc = Используется для заточки кромок стальных инструментов.
|
||||
|
||||
ent-CultRunicMetal = рунический металл
|
||||
.desc = Необычный лист металла с пульсирующей руной.
|
||||
.suffix = Полный
|
||||
|
||||
ent-CultRunicMetal1 = рунический металл
|
||||
.desc = Необычный лист металла с пульсирующей руной.
|
||||
.suffix = Один
|
||||
|
||||
ent-CultRunicMetal20 = рунический металл
|
||||
.desc = Необычный лист металла с пульсирующей руной.
|
||||
.suffix = 20
|
||||
|
||||
ent-CultBloodAltar = алтарь
|
||||
.desc = Кровавый алтарь, посвященный какому-то существу.
|
||||
|
||||
ent-CultForge = кузница
|
||||
.desc = Кузница, в которой изготавливается нечестивое оружие.
|
||||
|
||||
ent-CultGirder = руническая балка
|
||||
.desc = Большой конструктивный элемент, изготовленный из металла. На этом есть руна.
|
||||
|
||||
ent-AirlockGlassCult = рунический шлюз
|
||||
.desc = Странный стеклянный шлюз с руной.
|
||||
|
||||
ent-CultClothingBlindfold = повязка Зилота
|
||||
.desc = Повязка, наделенная странной силой.
|
||||
|
||||
ent-OfferingRune = руна предпонесения
|
||||
|
||||
ent-BuffRune = руна усиления
|
||||
|
||||
ent-EmpoweringRune = руна могущества
|
||||
|
||||
ent-TeleportRune = руна телепортации
|
||||
|
||||
ent-SummoningRune = руна призыва
|
||||
|
||||
ent-ReviveRune = руна воскрешения
|
||||
|
||||
ent-BarrierRune = руна барьера
|
||||
|
||||
ent-BloodBoilRune = руна кипящей крови
|
||||
|
||||
ent-ApocalypseRune = ритуал пространственного разрыва
|
||||
@@ -1,11 +1,15 @@
|
||||
cultist-factory-charging = { $name } будут заряжаться ещё { $seconds } секунд
|
||||
cultist-factory-create = Создать { $itemName }
|
||||
cultist-factory-too-far = Слишком далеко
|
||||
|
||||
ent-AltarTome = архивы
|
||||
.desc = Стол, заваленный тайными рукописями и книгами на неизвестных языках.
|
||||
|
||||
ent-CultRobeModify = одеяние флагелланта
|
||||
.desc = Какая-то религиозная роба.
|
||||
|
||||
ent-CultMirrorShield = зеркальный щит
|
||||
.desc = Щит с зеркалом на лицевой стороне, на котором изображен какой-то религиозный знак.
|
||||
|
||||
ent-CultOuterArmor = бронированная мантия
|
||||
.desc = С первого взгляда кажется, что это простая мантия, но на ней имеется элементы брони.
|
||||
14
Resources/Locale/ru-RU/_white/cult/preset.ftl
Normal file
14
Resources/Locale/ru-RU/_white/cult/preset.ftl
Normal file
@@ -0,0 +1,14 @@
|
||||
cult-title = Культ
|
||||
cult-description = Страх — самое древнее и сильное из человеческих чувств, а самый древний и самый сильный страх — страх неведомого
|
||||
cult-role-greeting =
|
||||
Вы - член культа!
|
||||
Ваши цели перечислены в меню персонажа.
|
||||
В ваш рюкзак были добавлены предметы, которые помогут вам.
|
||||
И помните - вы не единственный.
|
||||
Слава Нар`си!
|
||||
|
||||
cult-condition-win = Культ одержал победу
|
||||
cult-condition-failure = Экипаж уничтожил культ
|
||||
|
||||
cultists-list-start = Культистами были:
|
||||
cultists-list-name = - [color=White]{ $name }[/color] ([color=gray]{ $user }[/color])
|
||||
2
Resources/Locale/ru-RU/_white/cult/runes-entities.ftl
Normal file
2
Resources/Locale/ru-RU/_white/cult/runes-entities.ftl
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
|
||||
4
Resources/Locale/ru-RU/_white/cult/structure.ftl
Normal file
4
Resources/Locale/ru-RU/_white/cult/structure.ftl
Normal file
@@ -0,0 +1,4 @@
|
||||
cult-structure-craft-not-enough-metal = Недостаточно металла.
|
||||
cult-structure-craft-craft-failed = Не удалось начать постройку.
|
||||
cult-structure-craft-blocked = Что-то мешает построить.
|
||||
cult-structure-craft-another-structure-nearby = Слишком близко к другой такой постройке.
|
||||
@@ -1,8 +0,0 @@
|
||||
ent-JuggernautConstruct = Джаггернаут
|
||||
.desc = ""
|
||||
ent-ArtificerConstruct = Ремесленник
|
||||
.desc = ""
|
||||
ent-WraithConstruct = Фантом
|
||||
.desc = ""
|
||||
ent-ReaperConstruct = Жнец
|
||||
.desc = ""
|
||||
@@ -1,21 +0,0 @@
|
||||
ent-CultRunicMetal = рунический металл
|
||||
.desc = Необычный лист металла с пульсирующей руной.
|
||||
.suffix = Полный
|
||||
ent-CultRunicMetal1 = рунический металл
|
||||
.desc = Необычный лист металла с пульсирующей руной.
|
||||
.suffix = Один
|
||||
ent-CultRunicMetal20 = рунический металл
|
||||
.desc = Необычный лист металла с пульсирующей руной.
|
||||
.suffix = 20
|
||||
ent-CultBloodAltar = алтарь
|
||||
.desc = Кровавый алтарь, посвященный какому-то существу.
|
||||
ent-CultForge = кузница
|
||||
.desc = Кузница, в которой изготавливается нечестивое оружие.
|
||||
ent-CultGirder = руническая балка
|
||||
.desc = Большой конструктивный элемент, изготовленный из металла. На этом есть руна.
|
||||
ent-AirlockGlassCult = рунический шлюз
|
||||
.desc = Странный стеклянный шлюз с руной.
|
||||
cult-structure-craft-not-enough-metal = Недостаточно металла.
|
||||
cult-structure-craft-craft-failed = Не удалось начать постройку.
|
||||
cult-structure-craft-blocked = Что-то мешает построить.
|
||||
cult-structure-craft-another-structure-nearby = Слишком близко к другой такой постройке.
|
||||
@@ -1,11 +0,0 @@
|
||||
cultist-factory-charging = { $name } будут заряжаться ещё { $seconds } секунд
|
||||
cultist-factory-create = Создать { $itemName }
|
||||
cultist-factory-too-far = Слишком далеко
|
||||
ent-AltarTome = архивы
|
||||
.desc = Стол, заваленный тайными рукописями и книгами на неизвестных языках.
|
||||
ent-CultRobeModify = одеяние флагелланта
|
||||
.desc = Какая-то религиозная роба.
|
||||
ent-CultMirrorShield = зеркальный щит
|
||||
.desc = Щит с зеркалом на лицевой стороне, на котором изображен какой-то религиозный знак.
|
||||
ent-CultOuterArmor = бронированная мантия
|
||||
.desc = С первого взгляда кажется, что это простая мантия, но на ней имеется элементы брони.
|
||||
@@ -1 +0,0 @@
|
||||
reagent-effect-guidebook-deconvert-cultist = Деконвертирует культиста
|
||||
@@ -1,4 +0,0 @@
|
||||
pylon-toggle-on = Кристалл воспаряет над пьедесталом, начиная пульсировать
|
||||
pylon-toggle-off = Кристалл перестаёт пульсировать, опускаясь на пьедестал
|
||||
ent-CultPylon = пилон
|
||||
.desc = Мистический конструкция.
|
||||
@@ -1,5 +0,0 @@
|
||||
void-teleport-not-cultist = Посох выпадает у вас из рук.
|
||||
void-teleport-drained = В этом посохе больше нет энергии.
|
||||
void-teleport-cooldown = Посох накапливает заряд.
|
||||
ent-CultVeilShifter = преобразователь покрова
|
||||
.desc = Посох, излучающий странную энергию.
|
||||
@@ -1,9 +0,0 @@
|
||||
cult-torch-window-title = Отправить предмет
|
||||
cult-torch-not-cultist = Факел выпадает из ваших рук
|
||||
cult-torch-drained = Факел опустошён
|
||||
cult-torch-cooldown = Факел накапливает энергию
|
||||
cult-torch-cultists-not-found = Факел не обнаружил приспешников
|
||||
cult-torch-no-cultist = Факел потерял связь с приспешником
|
||||
cult-torch-item-send = Факел отправил предмет
|
||||
ent-CultTorch = факел пустоты
|
||||
.desc = Странный факел.
|
||||
@@ -1,7 +0,0 @@
|
||||
cult-cond-cultwin = Экипаж пососал
|
||||
cult-cond-cultfailure = Экипаж не пососал
|
||||
cultists-list-start = Культистами были:
|
||||
cultists-list-name = - [color=White]{ $name }[/color] ([color=gray]{ $user }[/color])
|
||||
soul-shard-name = Душа { $soul }
|
||||
soul-shard-description = В этом камне заключена душа { $soul }
|
||||
cult-too-much-empowers = Слишком много способностей
|
||||
@@ -146,7 +146,7 @@
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: CultRule
|
||||
cultistStartingItems:
|
||||
startingItems:
|
||||
- RitualDagger
|
||||
- CultRunicMetal20
|
||||
|
||||
|
||||
@@ -174,9 +174,10 @@
|
||||
id: Cult
|
||||
alias:
|
||||
- cult
|
||||
name: CULT
|
||||
description: CULT
|
||||
name: cult-title
|
||||
description: cult-description
|
||||
showInVote: true
|
||||
minPlayers: 20
|
||||
rules:
|
||||
- Cult
|
||||
- SubGamemodesRule
|
||||
@@ -190,6 +191,7 @@
|
||||
name: changeling-title
|
||||
description: changeling-description
|
||||
showInVote: true
|
||||
minPlayers: 15
|
||||
rules:
|
||||
- Changeling
|
||||
- SubGamemodesRule
|
||||
|
||||
Reference in New Issue
Block a user