Диверсионный отряд (#985)
* Диверсионный отряд * фиксы * фиксы линтера * Спрайт терминала икаруса * фикс пинпоинтера * Доработки ДО * Фиксы удалённого управления шаттлом * Не запускать ДО если на может быть меньше 3 глав
This commit is contained in:
parent
dfa7738f1b
commit
f84969630d
140 changed files with 11290 additions and 83 deletions
|
|
@ -0,0 +1,23 @@
|
|||
using Content.Shared._Sunrise.AssaultOps;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._Sunrise.AssaultOps.AssaultOps;
|
||||
|
||||
public sealed class AssaultOpsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AssaultOperativeComponent, GetStatusIconsEvent>(GetVampireIcon);
|
||||
}
|
||||
|
||||
private void GetVampireIcon(EntityUid uid, AssaultOperativeComponent component, ref GetStatusIconsEvent args)
|
||||
{
|
||||
var iconPrototype = _prototype.Index(component.StatusIcon);
|
||||
args.StatusIcons.Add(iconPrototype);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
using Content.Shared._Sunrise.AssaultOps.Icarus;
|
||||
|
||||
namespace Content.Client._Sunrise.AssaultOps.Icarus;
|
||||
|
||||
public sealed class IcarusTerminalBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private IcarusTerminalWindow? _window;
|
||||
|
||||
public IcarusTerminalBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_window = new IcarusTerminalWindow();
|
||||
_window.OnClose += Close;
|
||||
_window.OpenCentered();
|
||||
|
||||
_window.FireButtonPressed += OnFireButtonPressed;
|
||||
}
|
||||
|
||||
private void OnFireButtonPressed()
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
SendMessage(new IcarusTerminalFireMessage());
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
if (state is not IcarusTerminalUiState cast)
|
||||
return;
|
||||
|
||||
_window.UpdateState(cast);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
if (_window != null)
|
||||
_window.OnClose -= Close;
|
||||
|
||||
_window?.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc 'icarus-ui-window-title'}"
|
||||
MinSize="300 120">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Button Name="FireButton"
|
||||
Text="{Loc 'icarus-ui-fire-button'}"
|
||||
StyleClasses="Caution"
|
||||
MinHeight="50"
|
||||
Disabled="True" />
|
||||
<BoxContainer Name="TimerBox" Orientation="Horizontal" Visible="False">
|
||||
<Label Text="{Loc 'icarus-ui-timer-label'}" />
|
||||
<Label Text=" " />
|
||||
<Label Name="TimerValue" Text="-" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Name="CooldownBox" Orientation="Horizontal" Visible="False">
|
||||
<Label Text="{Loc 'icarus-ui-cooldown-label'}" />
|
||||
<Label Text=" " />
|
||||
<Label Name="CooldownValue" Text="-" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using Content.Shared._Sunrise.AssaultOps.Icarus;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._Sunrise.AssaultOps.Icarus;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class IcarusTerminalWindow : DefaultWindow
|
||||
{
|
||||
public event Action? FireButtonPressed;
|
||||
|
||||
public IcarusTerminalWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
FireButton.OnPressed += _ => FireButtonPressed?.Invoke();
|
||||
}
|
||||
|
||||
public void UpdateState(IcarusTerminalUiState state)
|
||||
{
|
||||
FireButton.Disabled = state.Status != IcarusTerminalStatus.FIRE_READY;
|
||||
TimerBox.Visible = state.Status == IcarusTerminalStatus.FIRE_PREPARING;
|
||||
CooldownBox.Visible = state.Status == IcarusTerminalStatus.COOLDOWN;
|
||||
|
||||
switch (state.Status)
|
||||
{
|
||||
case IcarusTerminalStatus.FIRE_PREPARING:
|
||||
TimerValue.Text = state.RemainingTime.ToString();
|
||||
break;
|
||||
case IcarusTerminalStatus.COOLDOWN:
|
||||
CooldownValue.Text = state.CooldownTime.ToString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Content.Client/_Sunrise/Interrogator/InterrogatorSystem.cs
Normal file
54
Content.Client/_Sunrise/Interrogator/InterrogatorSystem.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
using Content.Shared._Sunrise.Interrogator;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Client.GameObjects;
|
||||
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
||||
|
||||
namespace Content.Client._Sunrise.Interrogator;
|
||||
|
||||
public sealed class InterrogatorSystem: SharedInterrogatorSystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<InterrogatorComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<InterrogatorComponent, GetVerbsEvent<AlternativeVerb>>(AddAlternativeVerbs);
|
||||
|
||||
SubscribeLocalEvent<InterrogatorComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, InterrogatorComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_appearance.TryGetData<bool>(uid, InterrogatorComponent.InterrogatorVisuals.ContainsEntity, out var isOpen, args.Component)
|
||||
|| !_appearance.TryGetData<bool>(uid, InterrogatorComponent.InterrogatorVisuals.IsOn, out var isOn, args.Component))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (isOpen)
|
||||
{
|
||||
args.Sprite.LayerSetState(InterrogatorVisualLayers.Base, "open");
|
||||
args.Sprite.LayerSetVisible(InterrogatorVisualLayers.Extract, false);
|
||||
args.Sprite.DrawDepth = (int) DrawDepth.Objects;
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Sprite.DrawDepth = (int) DrawDepth.Mobs;
|
||||
args.Sprite.LayerSetState(InterrogatorVisualLayers.Extract, isOn ? "extraction-on" : "extraction-off");
|
||||
args.Sprite.LayerSetVisible(InterrogatorVisualLayers.Extract, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum InterrogatorVisualLayers : byte
|
||||
{
|
||||
Base,
|
||||
Extract,
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using Content.Server._Sunrise.AssaultOps;
|
||||
using Content.Server.Administration.Commands;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
|
|
@ -179,5 +180,19 @@ public sealed partial class AdminVerbSystem
|
|||
Message = Loc.GetString("admin-verb-make-vampire"),
|
||||
};
|
||||
args.Verbs.Add(vampire);
|
||||
|
||||
Verb assaultOperative = new()
|
||||
{
|
||||
Text = Loc.GetString("admin-verb-text-make-assault-operative"),
|
||||
Category = VerbCategory.Antag,
|
||||
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Structures/Wallmounts/posters.rsi"), "poster46_contraband"),
|
||||
Act = () =>
|
||||
{
|
||||
_antag.ForceMakeAntag<AssaultOpsRuleComponent>(targetPlayer, "AssaultOps");
|
||||
},
|
||||
Impact = LogImpact.High,
|
||||
Message = Loc.GetString("admin-verb-make-assault-operative"),
|
||||
};
|
||||
args.Verbs.Add(assaultOperative);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
namespace Content.Server.GameTicking.Rules.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Tags grid as nuke ops shuttle
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class NukeOpsShuttleComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public EntityUid AssociatedRule;
|
||||
}
|
||||
|
|
@ -1,7 +1,13 @@
|
|||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Jobs;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Server.Revolutionary.Components;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
|
|
@ -13,6 +19,8 @@ public abstract partial class GameRuleSystem<T> : EntitySystem where T : ICompon
|
|||
[Dependency] protected readonly IChatManager ChatManager = default!;
|
||||
[Dependency] protected readonly GameTicker GameTicker = default!;
|
||||
[Dependency] protected readonly IGameTiming Timing = default!;
|
||||
[Dependency] protected readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||
|
||||
// Not protected, just to be used in utility methods
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
|
|
@ -37,6 +45,55 @@ public abstract partial class GameRuleSystem<T> : EntitySystem where T : ICompon
|
|||
var query = QueryAllRules();
|
||||
while (query.MoveNext(out var uid, out _, out var gameRule))
|
||||
{
|
||||
// Sunrise-Start
|
||||
if (gameRule.MinCommandStaff > 0)
|
||||
{
|
||||
var availableHeads = new List<string>();
|
||||
|
||||
foreach (var playerSession in args.Players)
|
||||
{
|
||||
var userId = playerSession.UserId;
|
||||
var preferencesManager = IoCManager.Resolve<IServerPreferencesManager>();
|
||||
var prefs = preferencesManager.GetPreferences(userId);
|
||||
var profile = prefs.SelectedCharacter as HumanoidCharacterProfile;
|
||||
if (profile == null)
|
||||
continue;
|
||||
foreach (var profileJobPriority in profile.JobPriorities)
|
||||
{
|
||||
if (profileJobPriority.Value == JobPriority.Never)
|
||||
continue;
|
||||
if (!_prototype.TryIndex<JobPrototype>(profileJobPriority.Key.Id, out var job))
|
||||
continue;
|
||||
foreach (var special in job.Special)
|
||||
{
|
||||
if (special is not AddComponentSpecial componentSpecial)
|
||||
continue;
|
||||
|
||||
foreach (var componentSpecialComponent in componentSpecial.Components)
|
||||
{
|
||||
var copy = _componentFactory.GetComponent(componentSpecialComponent.Value);
|
||||
if (copy is CommandStaffComponent)
|
||||
{
|
||||
if (availableHeads.Contains(profileJobPriority.Key))
|
||||
continue;
|
||||
availableHeads.Add(profileJobPriority.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gameRule.CancelPresetOnTooFewPlayers && availableHeads.Count < gameRule.MinCommandStaff)
|
||||
{
|
||||
ChatManager.SendAdminAnnouncement(Loc.GetString("preset-not-enough-ready-command-staff",
|
||||
("readyCommandStaffCount", args.Players.Length),
|
||||
("minimumCommandStaff", gameRule.MinCommandStaff),
|
||||
("presetName", ToPrettyString(uid))));
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
// Sunrise-Edit
|
||||
|
||||
var minPlayers = gameRule.MinPlayers;
|
||||
if (args.Players.Length >= minPlayers)
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Linq;
|
||||
using Content.Server.Cuffs;
|
||||
using Content.Server.Forensics;
|
||||
using Content.Server.Humanoid;
|
||||
|
|
@ -19,9 +20,11 @@ using Robust.Shared.Physics;
|
|||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Random;
|
||||
using System.Numerics;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Movement.Pulling.Systems;
|
||||
using Content.Shared.Store.Components;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
|
|
@ -41,6 +44,7 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem
|
|||
[Dependency] private readonly PullingSystem _pullingSystem = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookupSystem = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private HashSet<Entity<MapGridComponent>> _targetGrids = [];
|
||||
|
|
@ -56,7 +60,37 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem
|
|||
SubscribeLocalEvent<SubdermalImplantComponent, ActivateImplantEvent>(OnActivateImplantEvent);
|
||||
SubscribeLocalEvent<SubdermalImplantComponent, UseScramImplantEvent>(OnScramImplant);
|
||||
SubscribeLocalEvent<SubdermalImplantComponent, UseDnaScramblerImplantEvent>(OnDnaScramblerImplant);
|
||||
SubscribeLocalEvent<ImplantedComponent, BeingGibbedEvent>(OnGibbed);
|
||||
}
|
||||
|
||||
private void OnGibbed(EntityUid uid, ImplantedComponent component, BeingGibbedEvent args)
|
||||
{
|
||||
if (!_container.TryGetContainer(uid, ImplanterComponent.ImplantSlotId, out var implantContainer))
|
||||
return;
|
||||
|
||||
foreach (var implant in implantContainer.ContainedEntities)
|
||||
{
|
||||
if (!TryComp<SubdermalImplantComponent>(implant, out var subdermalImplant))
|
||||
continue;
|
||||
|
||||
if (!subdermalImplant.DropContainerItemsIfGibbed)
|
||||
continue;
|
||||
|
||||
if (!_container.TryGetContainer(implant, BaseStorageId, out var storageImplant))
|
||||
continue;
|
||||
|
||||
var entCoords = Transform(uid).Coordinates;
|
||||
|
||||
var containedEntites = storageImplant.ContainedEntities.ToArray();
|
||||
|
||||
foreach (var entity in containedEntites)
|
||||
{
|
||||
if (Terminating(entity))
|
||||
continue;
|
||||
|
||||
_container.RemoveEntity(storageImplant.Owner, entity, force: true, destination: entCoords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStoreRelay(EntityUid uid, StoreComponent store, ImplantRelayEvent<AfterInteractUsingEvent> implantRelay)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ public sealed class MindShieldSystem : EntitySystem
|
|||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SubdermalImplantComponent, ImplantImplantedEvent>(ImplantCheck);
|
||||
SubscribeLocalEvent<SubdermalImplantComponent, ImplantEjectEvent>(ImplantCheck);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -43,6 +44,16 @@ public sealed class MindShieldSystem : EntitySystem
|
|||
}
|
||||
}
|
||||
|
||||
// Sunrise-Start
|
||||
public void ImplantCheck(EntityUid uid, SubdermalImplantComponent comp, ref ImplantEjectEvent ev)
|
||||
{
|
||||
if (_tag.HasTag(ev.Implant, MindShieldTag) && ev.Implanted != null)
|
||||
{
|
||||
RemCompDeferred<MindShieldComponent>(ev.Implanted.Value);
|
||||
}
|
||||
}
|
||||
// Sunrise-End
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the implanted person was a Rev or Head Rev and remove role or destroy mindshield respectively.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ using Content.Shared.Interaction;
|
|||
using Content.Shared.Pinpointer;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Popups;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Server.Shuttles.Events;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Pinpointer;
|
||||
|
||||
|
|
@ -12,6 +14,8 @@ public sealed class PinpointerSystem : SharedPinpointerSystem
|
|||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly PopupSystem _popups = default!;
|
||||
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
|
|
@ -22,6 +26,44 @@ public sealed class PinpointerSystem : SharedPinpointerSystem
|
|||
|
||||
SubscribeLocalEvent<PinpointerComponent, ActivateInWorldEvent>(OnActivate);
|
||||
SubscribeLocalEvent<FTLCompletedEvent>(OnLocateTarget);
|
||||
|
||||
SubscribeLocalEvent<PinpointerComponent, GetVerbsEvent<AlternativeVerb>>(AddSwitchVerb);
|
||||
}
|
||||
|
||||
private void AddSwitchVerb(EntityUid uid, PinpointerComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess)
|
||||
return;
|
||||
|
||||
AlternativeVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
SwitchTarget(uid, component, args.User);
|
||||
},
|
||||
Text = Loc.GetString("pinpointer-switch-target"),
|
||||
Priority = 2
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void SwitchTarget(EntityUid uid, PinpointerComponent component, EntityUid user)
|
||||
{
|
||||
if (component.IsActive && component.Component != null)
|
||||
{
|
||||
if (!EntityManager.ComponentFactory.TryGetRegistration(component.Component, out var reg))
|
||||
{
|
||||
Logger.Error($"Unable to find component registration for {component.Component} for pinpointer!");
|
||||
DebugTools.Assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var target = FindTargetFromComponent(uid, reg.Type, component.Target);
|
||||
|
||||
SetTarget(uid, target, component);
|
||||
}
|
||||
|
||||
_popups.PopupEntity(Loc.GetString("pinpointer-target-switched"), user, user);
|
||||
}
|
||||
|
||||
public override bool TogglePinpointer(EntityUid uid, PinpointerComponent? pinpointer = null)
|
||||
|
|
@ -85,7 +127,7 @@ public sealed class PinpointerSystem : SharedPinpointerSystem
|
|||
return;
|
||||
}
|
||||
|
||||
var target = FindTargetFromComponent(uid, reg.Type);
|
||||
var target = FindTargetFromComponent(uid, reg.Type, component.Target);
|
||||
SetTarget(uid, target, component);
|
||||
}
|
||||
}
|
||||
|
|
@ -107,7 +149,7 @@ public sealed class PinpointerSystem : SharedPinpointerSystem
|
|||
/// Try to find the closest entity from whitelist on a current map
|
||||
/// Will return null if can't find anything
|
||||
/// </summary>
|
||||
private EntityUid? FindTargetFromComponent(EntityUid uid, Type whitelist, TransformComponent? transform = null)
|
||||
private EntityUid? FindTargetFromComponent(EntityUid uid, Type whitelist, EntityUid? currentTarget, TransformComponent? transform = null)
|
||||
{
|
||||
_xformQuery.Resolve(uid, ref transform, false);
|
||||
|
||||
|
|
@ -128,6 +170,17 @@ public sealed class PinpointerSystem : SharedPinpointerSystem
|
|||
l.TryAdd(dist, otherUid);
|
||||
}
|
||||
|
||||
if (l.Count > 1)
|
||||
{
|
||||
foreach (var target in l.ToList())
|
||||
{
|
||||
if (currentTarget == target.Value)
|
||||
l.Remove(target.Key);
|
||||
}
|
||||
|
||||
return _random.Pick(l).Value;
|
||||
}
|
||||
|
||||
// return uid with a smallest distance
|
||||
return l.Count > 0 ? l.First().Value : null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,5 +20,11 @@ namespace Content.Server.Shuttles.Components
|
|||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("whitelistSpecific")]
|
||||
public List<EntityUid> FTLWhitelist = new List<EntityUid>();
|
||||
|
||||
// Sunrise-Start
|
||||
[ViewVariables]
|
||||
[DataField("portable")]
|
||||
public bool Portable;
|
||||
// Sunrise-End
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,4 +18,10 @@ public sealed partial class DroneConsoleComponent : Component
|
|||
/// </summary>
|
||||
[DataField("entity")]
|
||||
public EntityUid? Entity;
|
||||
|
||||
// Sunrise-Start
|
||||
[ViewVariables]
|
||||
[DataField("portable")]
|
||||
public bool Portable;
|
||||
// Sunrise-End
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ public sealed partial class ShuttleConsoleSystem
|
|||
|
||||
var stationUid = _station.GetOwningStation(uid);
|
||||
|
||||
if (stationUid == null)
|
||||
if (stationUid == null && !component.Portable)
|
||||
return null;
|
||||
|
||||
// I know this sucks but needs device linking or something idunno
|
||||
|
|
@ -69,9 +69,9 @@ public sealed partial class ShuttleConsoleSystem
|
|||
|
||||
while (query.MoveNext(out var cUid, out _, out var xform))
|
||||
{
|
||||
if (xform.GridUid == null ||
|
||||
if ((xform.GridUid == null ||
|
||||
!TryComp<StationMemberComponent>(xform.GridUid, out var member) ||
|
||||
member.Station != stationUid)
|
||||
member.Station != stationUid) && !component.Portable)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,6 +124,13 @@ public sealed partial class ShuttleConsoleSystem
|
|||
{
|
||||
var consoleUid = GetDroneConsole(ent.Owner);
|
||||
|
||||
// Sunrise-Start
|
||||
if (TryComp<DroneConsoleComponent>(consoleUid, out var droneConsole))
|
||||
{
|
||||
consoleUid = droneConsole.Entity;
|
||||
}
|
||||
// Sunrise-End
|
||||
|
||||
if (consoleUid == null)
|
||||
return;
|
||||
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem
|
|||
if (!_tags.HasTag(user, "CanPilot") ||
|
||||
!TryComp<ShuttleConsoleComponent>(uid, out var component) ||
|
||||
!this.IsPowered(uid, EntityManager) ||
|
||||
!Transform(uid).Anchored ||
|
||||
!Transform(uid).Anchored && !component.Portable || // Sunrise-Edit
|
||||
!_blocker.CanInteract(user, uid))
|
||||
{
|
||||
return false;
|
||||
|
|
@ -252,6 +252,13 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem
|
|||
RaiseLocalEvent(entity.Value, ref getShuttleEv);
|
||||
entity = getShuttleEv.Console;
|
||||
|
||||
// Sunrise-Start
|
||||
if (TryComp<DroneConsoleComponent>(entity, out var droneConsole))
|
||||
{
|
||||
entity = droneConsole.Entity;
|
||||
}
|
||||
// Sunrise-End
|
||||
|
||||
TryComp(entity, out TransformComponent? consoleXform);
|
||||
var shuttleGridUid = consoleXform?.GridUid;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server._Sunrise.AssaultOps;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class AssaultOperativeSpawnerComponent : Component
|
||||
{
|
||||
[DataField("rolePrototype", customTypeSerializer:typeof(PrototypeIdSerializer<AntagPrototype>), required:true)]
|
||||
public string OperativeRolePrototype = default!;
|
||||
|
||||
[DataField("startingGearPrototype", customTypeSerializer:typeof(PrototypeIdSerializer<StartingGearPrototype>), required:true)]
|
||||
public string OperativeStartingGear = default!;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server._Sunrise.AssaultOps;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class AssaultOpsRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
using Content.Shared.Dataset;
|
||||
using Content.Shared.NPC.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server._Sunrise.AssaultOps;
|
||||
|
||||
[RegisterComponent, Access(typeof(AssaultOpsRuleSystem))]
|
||||
public sealed partial class AssaultOpsRuleComponent : Component
|
||||
{
|
||||
[DataField("icarusKeyImplant", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string IcarusKeyImplant = "IcarusKey";
|
||||
|
||||
[DataField("requiredKeys")] public int RequiredKeys = 3;
|
||||
|
||||
[DataField("keysCarrierJobs", customTypeSerializer: typeof(PrototypeIdArraySerializer<JobPrototype>))]
|
||||
public string[] KeysCarrierJobs =
|
||||
{
|
||||
"Captain",
|
||||
"HeadOfSecurity",
|
||||
"ChiefEngineer",
|
||||
"ChiefMedicalOfficer",
|
||||
"ResearchDirector",
|
||||
"Quartermaster"
|
||||
};
|
||||
|
||||
[DataField("faction", customTypeSerializer: typeof(PrototypeIdSerializer<NpcFactionPrototype>), required: true)]
|
||||
public string Faction = default!;
|
||||
|
||||
[DataField]
|
||||
public int TCAmountPerOperative = 50;
|
||||
|
||||
[DataField]
|
||||
public int RoundstartOperatives;
|
||||
|
||||
[DataField("greetingSound", customTypeSerializer: typeof(SoundSpecifierTypeSerializer))]
|
||||
public SoundSpecifier? GreetSoundNotification = new SoundPathSpecifier("/Audio/_Sunrise/AssaultOperatives/assault_operatives_greet.ogg",
|
||||
AudioParams.Default.WithVolume(-6f));
|
||||
|
||||
[DataField("winType")] public WinType WinType = WinType.Stalemate;
|
||||
|
||||
[DataField("winConditions")] public List<WinCondition> WinConditions = new ();
|
||||
|
||||
public EntityUid? ShuttleGrid;
|
||||
|
||||
public EntityUid? TargetStation;
|
||||
}
|
||||
|
||||
public enum WinType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Operative major win. Goldeneye activated and all ops alive.
|
||||
/// </summary>
|
||||
OpsMajor,
|
||||
/// <summary>
|
||||
/// Minor win. Goldeneye was activated and some ops alive.
|
||||
/// </summary>
|
||||
OpsMinor,
|
||||
/// <summary>
|
||||
/// Hearty. Goldeneye activated but no ops alive.
|
||||
/// </summary>
|
||||
Hearty,
|
||||
/// <summary>
|
||||
/// Stalemate. Goldeneye not activated and ops still alive.
|
||||
/// </summary>
|
||||
Stalemate,
|
||||
/// <summary>
|
||||
/// Crew major win. Goldeneye not activated and no ops alive.
|
||||
/// </summary>
|
||||
CrewMajor
|
||||
}
|
||||
|
||||
public enum WinCondition
|
||||
{
|
||||
IcarusActivated,
|
||||
AllOpsDead,
|
||||
SomeOpsAlive,
|
||||
AllOpsAlive
|
||||
}
|
||||
349
Content.Server/_Sunrise/AssaultOps/AssaultOpsRuleSystem.cs
Normal file
349
Content.Server/_Sunrise/AssaultOps/AssaultOpsRuleSystem.cs
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
using Content.Server._Sunrise.AssaultOps.Icarus;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.Antag.Components;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Revolutionary.Components;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Traitor.Uplink;
|
||||
using Content.Shared._Sunrise.AssaultOps;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.NPC.Components;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server._Sunrise.AssaultOps;
|
||||
|
||||
public sealed class AssaultOpsRuleSystem : GameRuleSystem<AssaultOpsRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
|
||||
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedSubdermalImplantSystem _subdermalImplant = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly UplinkSystem _uplinkSystem = default!;
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
||||
|
||||
[ValidatePrototypeId<TagPrototype>]
|
||||
private const string UplinkTagPrototype = "AssaultOpsUplink";
|
||||
|
||||
[ValidatePrototypeId<AntagPrototype>]
|
||||
private const string CommanderAntagProto = "AssaultCommander";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnRunLevelChanged);
|
||||
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawned);
|
||||
SubscribeLocalEvent<IcarusTerminalSystem.IcarusActivatedEvent>(OnIcarusActivated);
|
||||
SubscribeLocalEvent<AssaultOperativeComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
|
||||
SubscribeLocalEvent<AssaultOpsRuleComponent, AfterAntagEntitySelectedEvent>(OnAfterAntagEntSelected);
|
||||
SubscribeLocalEvent<AssaultOpsRuleComponent, AntagSelectionCompleteEvent>(OnAfterAntagSelectionComplete); // Sunrise-Edit
|
||||
SubscribeLocalEvent<AssaultOpsRuleComponent, RuleLoadedGridsEvent>(OnRuleLoadedGrids);
|
||||
}
|
||||
|
||||
protected override void Started(EntityUid uid,
|
||||
AssaultOpsRuleComponent component,
|
||||
GameRuleComponent gameRule,
|
||||
GameRuleStartedEvent args)
|
||||
{
|
||||
var eligible = new List<Entity<StationEventEligibleComponent, NpcFactionMemberComponent>>();
|
||||
var eligibleQuery = EntityQueryEnumerator<StationEventEligibleComponent, NpcFactionMemberComponent>();
|
||||
while (eligibleQuery.MoveNext(out var eligibleUid, out var eligibleComp, out var member))
|
||||
{
|
||||
if (!_npcFaction.IsFactionHostile(component.Faction, (eligibleUid, member)))
|
||||
continue;
|
||||
|
||||
eligible.Add((eligibleUid, eligibleComp, member));
|
||||
}
|
||||
|
||||
if (eligible.Count == 0)
|
||||
return;
|
||||
|
||||
component.TargetStation = RobustRandom.Pick(eligible);
|
||||
|
||||
if (GameTicker.RunLevel == GameRunLevel.InRound)
|
||||
{
|
||||
InsertIcarusKeys(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRuleLoadedGrids(Entity<AssaultOpsRuleComponent> ent, ref RuleLoadedGridsEvent args)
|
||||
{
|
||||
var query = EntityQueryEnumerator<AssaultOpsShuttleComponent>();
|
||||
while (query.MoveNext(out var uid, out var shuttle))
|
||||
{
|
||||
if (Transform(uid).MapID == args.Map)
|
||||
{
|
||||
shuttle.AssociatedRule = ent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAfterAntagEntSelected(Entity<AssaultOpsRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
|
||||
{
|
||||
var target = (ent.Comp.TargetStation is not null) ? Name(ent.Comp.TargetStation.Value) : "the target";
|
||||
|
||||
_antag.SendBriefing(args.Session,
|
||||
Loc.GetString("assaultops-welcome",
|
||||
("station", target),
|
||||
("name", Name(ent))),
|
||||
Color.Red,
|
||||
ent.Comp.GreetSoundNotification);
|
||||
|
||||
// Sunrise-Start
|
||||
if (!args.GameRule.Comp.UseSpawners)
|
||||
return;
|
||||
|
||||
ent.Comp.RoundstartOperatives = args.GameRule.Comp.SpawnersCount;
|
||||
var commander = GetCommander(args.GameRule);
|
||||
if (commander != null)
|
||||
SetupUplink(commander.Value, ent.Comp);
|
||||
// Sunrise-End
|
||||
}
|
||||
|
||||
private void OnAfterAntagSelectionComplete(Entity<AssaultOpsRuleComponent> ent, ref AntagSelectionCompleteEvent args)
|
||||
{
|
||||
ent.Comp.RoundstartOperatives = args.GameRule.Comp.SelectedMinds.Count;
|
||||
|
||||
var commander = GetCommander(args.GameRule);
|
||||
if (commander != null)
|
||||
SetupUplink(commander.Value, ent.Comp);
|
||||
}
|
||||
|
||||
private EntityUid? GetCommander(Entity<AntagSelectionComponent> antagSelection)
|
||||
{
|
||||
EntityUid? commander = null;
|
||||
foreach (var compSelectedMind in antagSelection.Comp.SelectedMinds)
|
||||
{
|
||||
if (!TryComp<MindComponent>(compSelectedMind.Item1, out var mindComp))
|
||||
continue;
|
||||
|
||||
foreach (var roleInfo in _roles.MindGetAllRoleInfo((compSelectedMind.Item1, mindComp)))
|
||||
{
|
||||
if (roleInfo.Prototype != CommanderAntagProto || mindComp.CurrentEntity == null)
|
||||
continue;
|
||||
|
||||
commander = mindComp.CurrentEntity.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return commander;
|
||||
}
|
||||
|
||||
private void SetupUplink(EntityUid user, AssaultOpsRuleComponent rule)
|
||||
{
|
||||
var uplink = _uplinkSystem.FindUplinkByTag(user, UplinkTagPrototype);
|
||||
if (uplink != null)
|
||||
_uplinkSystem.SetUplink(user, uplink.Value, rule.TCAmountPerOperative * rule.RoundstartOperatives, true);
|
||||
}
|
||||
|
||||
private void InsertIcarusKeys(EntityUid uid, AssaultOpsRuleComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var query = EntityQueryEnumerator<CommandStaffComponent>();
|
||||
while (query.MoveNext(out var ent, out var mind))
|
||||
{
|
||||
var haveKey = false;
|
||||
if (_container.TryGetContainer(ent, ImplanterComponent.ImplantSlotId, out var implantContainer))
|
||||
{
|
||||
foreach (var implant in implantContainer.ContainedEntities)
|
||||
{
|
||||
if (MetaData(implant).EntityPrototype!.ID == component.IcarusKeyImplant)
|
||||
haveKey = true;
|
||||
}
|
||||
}
|
||||
if (!haveKey)
|
||||
InsertKey(ent, component.IcarusKeyImplant);
|
||||
}
|
||||
}
|
||||
|
||||
private bool InsertKey(EntityUid uid, string icarusKeyImplant)
|
||||
{
|
||||
var ownedCoords = Transform(uid).Coordinates;
|
||||
var implant = Spawn(icarusKeyImplant, ownedCoords);
|
||||
|
||||
if (!TryComp<SubdermalImplantComponent>(implant, out var implantComp))
|
||||
return false;
|
||||
|
||||
_subdermalImplant.ForceImplant(uid, implant, implantComp);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnIcarusActivated(IcarusTerminalSystem.IcarusActivatedEvent ev)
|
||||
{
|
||||
var query = EntityQueryEnumerator<AssaultOpsRuleComponent, GameRuleComponent>();
|
||||
while (query.MoveNext(out var uid, out var assaultops, out var gameRule))
|
||||
{
|
||||
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
||||
{
|
||||
Logger.Info("AssaultopsRule not added");
|
||||
continue;
|
||||
}
|
||||
assaultops.WinConditions.Add(WinCondition.IcarusActivated);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayerSpawned(PlayerSpawnCompleteEvent ev)
|
||||
{
|
||||
var query = EntityQueryEnumerator<AssaultOpsRuleComponent, GameRuleComponent>();
|
||||
while (query.MoveNext(out var uid, out var assaultops, out var gameRule))
|
||||
{
|
||||
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
||||
continue;
|
||||
|
||||
var session = ev.Player;
|
||||
|
||||
var mind = _mind.GetMind(session.UserId);
|
||||
|
||||
if (mind == null)
|
||||
continue;
|
||||
|
||||
if (HasComp<CommandStaffComponent>(ev.Mob))
|
||||
InsertKey(ev.Mob, assaultops.IcarusKeyImplant);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRunLevelChanged(GameRunLevelChangedEvent ev)
|
||||
{
|
||||
if (ev.New is not GameRunLevel.PostRound)
|
||||
return;
|
||||
|
||||
var query = QueryActiveRules();
|
||||
while (query.MoveNext(out var uid, out _, out var rule, out _))
|
||||
{
|
||||
OnRoundEnd(uid, rule);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRoundEnd(EntityUid uid, AssaultOpsRuleComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var total = 0;
|
||||
var alive = 0;
|
||||
foreach (var (_, state) in EntityQuery<AssaultOperativeComponent, MobStateComponent>())
|
||||
{
|
||||
total++;
|
||||
if (state.CurrentState != MobState.Alive)
|
||||
continue;
|
||||
|
||||
alive++;
|
||||
break;
|
||||
}
|
||||
|
||||
var allAlive = alive == total;
|
||||
if (allAlive)
|
||||
{
|
||||
if (component.WinConditions.Contains(WinCondition.IcarusActivated))
|
||||
{
|
||||
component.WinType = WinType.OpsMajor;
|
||||
}
|
||||
else
|
||||
{
|
||||
component.WinType = WinType.OpsMinor;
|
||||
component.WinConditions.Add(WinCondition.AllOpsAlive);
|
||||
}
|
||||
}
|
||||
else if (alive == 0)
|
||||
{
|
||||
if (component.WinConditions.Contains(WinCondition.IcarusActivated))
|
||||
{
|
||||
component.WinType = WinType.Hearty;
|
||||
}
|
||||
else
|
||||
{
|
||||
component.WinType = WinType.CrewMajor;
|
||||
component.WinConditions.Add(WinCondition.AllOpsDead);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (component.WinConditions.Contains(WinCondition.IcarusActivated))
|
||||
{
|
||||
component.WinType = WinType.OpsMinor;
|
||||
}
|
||||
else
|
||||
{
|
||||
component.WinType = WinType.Stalemate;
|
||||
component.WinConditions.Add(WinCondition.SomeOpsAlive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(EntityUid uid, AssaultOperativeComponent component, MobStateChangedEvent ev)
|
||||
{
|
||||
if(ev.NewMobState == MobState.Dead)
|
||||
CheckRoundShouldEnd();
|
||||
}
|
||||
|
||||
private void CheckRoundShouldEnd()
|
||||
{
|
||||
var query = EntityQueryEnumerator<AssaultOpsRuleComponent, GameRuleComponent>();
|
||||
while (query.MoveNext(out var uid, out var assaultops, out var gameRule))
|
||||
{
|
||||
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
||||
continue;
|
||||
|
||||
if (assaultops.WinType == WinType.CrewMajor || assaultops.WinType == WinType.OpsMajor)
|
||||
continue;
|
||||
|
||||
var operativesAlive = false;
|
||||
var operatives = EntityQuery<AssaultOperativeComponent, MobStateComponent>(true);
|
||||
foreach (var (assaultOp, mobState) in operatives)
|
||||
{
|
||||
if (mobState.CurrentState is MobState.Alive)
|
||||
{
|
||||
operativesAlive = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (operativesAlive)
|
||||
continue;
|
||||
|
||||
_roundEndSystem.EndRound();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void AppendRoundEndText(EntityUid uid,
|
||||
AssaultOpsRuleComponent component,
|
||||
GameRuleComponent gameRule,
|
||||
ref RoundEndTextAppendEvent args)
|
||||
{
|
||||
var winText = Loc.GetString($"assaultops-{component.WinType.ToString().ToLower()}");
|
||||
args.AddLine(winText);
|
||||
|
||||
foreach (var cond in component.WinConditions)
|
||||
{
|
||||
var text = Loc.GetString($"assaultops-cond-{cond.ToString().ToLower()}");
|
||||
args.AddLine(text);
|
||||
}
|
||||
|
||||
args.AddLine(Loc.GetString("assaultops-list-start"));
|
||||
|
||||
var antags =_antag.GetAntagIdentifiers(uid);
|
||||
|
||||
foreach (var (_, sessionData, name) in antags)
|
||||
{
|
||||
args.AddLine(Loc.GetString("assaultops-list-name", ("name", name), ("user", sessionData.UserName)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server._Sunrise.AssaultOps.Icarus.Commands;
|
||||
|
||||
[UsedImplicitly]
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class SpawnIcarusCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "spawnicarus";
|
||||
public string Description => "Spawn Icarus beam and direct to specified grid center.";
|
||||
public string Help => "spawnicarus <gridId>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteError("Incorrect number of arguments. " + Help);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityUid.TryParse(args[0], out var uid))
|
||||
{
|
||||
shell.WriteError("Not a valid entity ID.");
|
||||
return;
|
||||
}
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
if (!entityManager.EntityExists(uid))
|
||||
{
|
||||
shell.WriteError("That grid does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
if (entityManager.TryGetComponent<MapGridComponent>(uid, out var grid))
|
||||
{
|
||||
var icarusSystem = IoCManager.Resolve<IEntityManager>().System<IcarusTerminalSystem>();
|
||||
var coords = icarusSystem.FireBeam(xformQuery.GetComponent(uid).WorldMatrix.TransformBox(grid.LocalAABB));
|
||||
shell.WriteLine($"Icarus was spawned: {coords.ToString()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
shell.WriteError($"No grid exists with ID {uid}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
namespace Content.Server._Sunrise.AssaultOps.Icarus;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class IcarusBeamComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Beam moving speed.
|
||||
/// </summary>
|
||||
[DataField("speed")]
|
||||
public float Speed = 8f;
|
||||
|
||||
/// <summary>
|
||||
/// The beam will be automatically cleaned up after this time.
|
||||
/// </summary>
|
||||
[DataField("lifetime")]
|
||||
public TimeSpan Lifetime = TimeSpan.FromSeconds(200);
|
||||
|
||||
/// <summary>
|
||||
/// With this set to true, beam will automatically set the tiles under them to space.
|
||||
/// </summary>
|
||||
[DataField("destroyTiles")]
|
||||
public bool DestroyTiles = true;
|
||||
|
||||
[DataField("destroyRadius")]
|
||||
public float DestroyRadius = 4f;
|
||||
|
||||
[DataField("flameRadius")]
|
||||
public float FlameRadius = 8f;
|
||||
|
||||
public TimeSpan LifetimeEnd;
|
||||
}
|
||||
143
Content.Server/_Sunrise/AssaultOps/Icarus/IcarusBeamSystem.cs
Normal file
143
Content.Server/_Sunrise/AssaultOps/Icarus/IcarusBeamSystem.cs
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
using System.Numerics;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Ghost;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._Sunrise.AssaultOps.Icarus;
|
||||
|
||||
public sealed class IcarusBeamSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly FlammableSystem _flammable = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQuery<IcarusBeamComponent, TransformComponent>(true);
|
||||
foreach (var (comp, xform) in query)
|
||||
{
|
||||
DestroyEntities(comp, xform);
|
||||
BurnEntities(comp.Owner, comp, xform);
|
||||
|
||||
if (comp.DestroyTiles)
|
||||
DestroyTiles(comp, xform);
|
||||
|
||||
if (_timing.CurTime > comp.LifetimeEnd)
|
||||
QueueDel(comp.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<IcarusBeamComponent, ComponentInit>(OnComponentInit);
|
||||
}
|
||||
|
||||
private void OnComponentInit(EntityUid uid, IcarusBeamComponent component, ComponentInit args)
|
||||
{
|
||||
component.LifetimeEnd = _timing.CurTime + component.Lifetime;
|
||||
if (!TryComp(uid, out PhysicsComponent? phys))
|
||||
return;
|
||||
_physics.SetLinearDamping(uid, phys, 0f);
|
||||
_physics.SetFriction(uid, phys, 0f);
|
||||
_physics.SetAngularDamping(uid, phys, 0f);
|
||||
}
|
||||
|
||||
public void LaunchInDirection(EntityUid uid, Vector2 dir, IcarusBeamComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp))
|
||||
return;
|
||||
|
||||
|
||||
if (TryComp(uid, out PhysicsComponent? phys))
|
||||
{
|
||||
var impulseVector = dir.Normalized() * comp.Speed * phys.Mass;
|
||||
|
||||
_physics.ApplyLinearImpulse(uid, impulseVector, body: phys);
|
||||
_transform.SetWorldRotation(uid, impulseVector.ToWorldAngle());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroy any grid tiles in beam radius.
|
||||
/// </summary>
|
||||
private void DestroyTiles(IcarusBeamComponent component, TransformComponent trans)
|
||||
{
|
||||
var radius = component.DestroyRadius;
|
||||
var worldPos = trans.WorldPosition;
|
||||
|
||||
var circle = new Circle(worldPos, radius);
|
||||
var r = new Vector2(radius, radius);
|
||||
var box = new Box2(worldPos - r, worldPos + r);
|
||||
|
||||
foreach (var grid in _map.FindGridsIntersecting(trans.MapID, box))
|
||||
{
|
||||
// Bundle these together so we can use the faster helper to set tiles.
|
||||
var toDestroy = new List<(Vector2i, Tile)>();
|
||||
|
||||
foreach (var tile in grid.GetTilesIntersecting(circle))
|
||||
{
|
||||
if (tile.Tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
toDestroy.Add((tile.GridIndices, Tile.Empty));
|
||||
}
|
||||
|
||||
grid.SetTiles(toDestroy);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle deleting entities in beam radius.
|
||||
/// </summary>
|
||||
private void DestroyEntities(IcarusBeamComponent component, TransformComponent trans)
|
||||
{
|
||||
var radius = component.DestroyRadius - 0.5f;
|
||||
var entitys = _lookup.GetEntitiesInRange(trans.MapID, trans.WorldPosition, radius);
|
||||
foreach (var entity in entitys)
|
||||
{
|
||||
if (!CanDestroy(component, entity))
|
||||
continue;
|
||||
|
||||
QueueDel(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle igniting flammable entities in beam radius.
|
||||
/// </summary>
|
||||
private void BurnEntities(EntityUid beam, IcarusBeamComponent component, TransformComponent trans)
|
||||
{
|
||||
var radius = component.FlameRadius * 2;
|
||||
foreach (var entity in _lookup.GetEntitiesInRange(trans.MapID, trans.WorldPosition, radius))
|
||||
{
|
||||
if (!CanDestroy(component, entity))
|
||||
continue;
|
||||
|
||||
if (!TryComp<FlammableComponent>(entity, out var flammable))
|
||||
continue;
|
||||
|
||||
flammable.FireStacks += 1;
|
||||
if (!flammable.OnFire)
|
||||
_flammable.Ignite(entity, beam);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanDestroy(IcarusBeamComponent component, EntityUid entity)
|
||||
{
|
||||
return entity != component.Owner &&
|
||||
!EntityManager.HasComponent<MapGridComponent>(entity) &&
|
||||
!EntityManager.HasComponent<GhostComponent>(entity);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared._Sunrise.AssaultOps.Icarus;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server._Sunrise.AssaultOps.Icarus;
|
||||
|
||||
/// <summary>
|
||||
/// Handle Icarus activation terminal
|
||||
/// </summary>
|
||||
public sealed class IcarusTerminalSystem : EntitySystem
|
||||
{
|
||||
private const string IcarusBeamPrototypeId = "IcarusBeam";
|
||||
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly IcarusBeamSystem _icarusSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
||||
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQuery<IcarusTerminalComponent>();
|
||||
foreach (var terminal in query)
|
||||
{
|
||||
switch (terminal.Status)
|
||||
{
|
||||
case IcarusTerminalStatus.FIRE_PREPARING:
|
||||
TickTimer(terminal, frameTime);
|
||||
break;
|
||||
case IcarusTerminalStatus.COOLDOWN:
|
||||
TickCooldown(terminal, frameTime);
|
||||
break;
|
||||
}
|
||||
|
||||
if (terminal.Status != IcarusTerminalStatus.AWAIT_DISKS)
|
||||
{
|
||||
TickTimerEndRound(terminal, frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TickTimerEndRound(IcarusTerminalComponent component, float frameTime)
|
||||
{
|
||||
component.TimerRoundEnd -= frameTime;
|
||||
if (!(component.TimerRoundEnd <= 0))
|
||||
return;
|
||||
component.TimerRoundEnd = 0;
|
||||
_roundEndSystem.EndRound();
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<IcarusTerminalComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<IcarusTerminalComponent, EntInsertedIntoContainerMessage>(OnItemSlotInserted);
|
||||
SubscribeLocalEvent<IcarusTerminalComponent, EntRemovedFromContainerMessage>(OnItemSlotRemoved);
|
||||
|
||||
// UI events
|
||||
SubscribeLocalEvent<IcarusTerminalComponent, IcarusTerminalFireMessage>(OnFireButtonPressed);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, IcarusTerminalComponent component, ComponentInit args)
|
||||
{
|
||||
component.RemainingTime = component.Timer;
|
||||
UpdateStatus(component);
|
||||
UpdateUserInterface(component);
|
||||
}
|
||||
|
||||
private void OnItemSlotInserted(EntityUid uid, IcarusTerminalComponent component, ContainerModifiedMessage args)
|
||||
{
|
||||
OnItemSlotChanged(component);
|
||||
}
|
||||
|
||||
private void OnItemSlotRemoved(EntityUid uid, IcarusTerminalComponent component, ContainerModifiedMessage args)
|
||||
{
|
||||
OnItemSlotChanged(component);
|
||||
}
|
||||
|
||||
private void OnItemSlotChanged(IcarusTerminalComponent component)
|
||||
{
|
||||
UpdateStatus(component);
|
||||
UpdateUserInterface(component);
|
||||
}
|
||||
|
||||
private void OnFireButtonPressed(EntityUid uid, IcarusTerminalComponent component, IcarusTerminalFireMessage args)
|
||||
{
|
||||
Fire(component);
|
||||
}
|
||||
|
||||
private void Fire(IcarusTerminalComponent component)
|
||||
{
|
||||
if (component.Status == IcarusTerminalStatus.FIRE_PREPARING)
|
||||
return;
|
||||
|
||||
component.RemainingTime = component.Timer;
|
||||
component.Status = IcarusTerminalStatus.FIRE_PREPARING;
|
||||
|
||||
var stationName = "/NTSS14/";
|
||||
|
||||
var targetStation = _stationSystem.GetStations().FirstOrNull();
|
||||
|
||||
if (targetStation != null)
|
||||
{
|
||||
stationName = Name(targetStation.Value);
|
||||
}
|
||||
|
||||
_chatSystem.DispatchGlobalAnnouncement(
|
||||
Loc.GetString("icarus-fire-announcement", ("seconds", component.Timer),
|
||||
("station", stationName)),
|
||||
Loc.GetString("icarus-announce-sender"),
|
||||
false,
|
||||
colorOverride: Color.Red);
|
||||
_audio.PlayGlobal(component.AlertSound, Filter.Broadcast(), false);
|
||||
}
|
||||
|
||||
private void UpdateStatus(IcarusTerminalComponent component)
|
||||
{
|
||||
switch (component.Status)
|
||||
{
|
||||
case IcarusTerminalStatus.AWAIT_DISKS:
|
||||
if (IsAccessGranted(component.Owner))
|
||||
Authorize(component);
|
||||
break;
|
||||
case IcarusTerminalStatus.FIRE_READY:
|
||||
{
|
||||
if (!IsAccessGranted(component.Owner))
|
||||
{
|
||||
component.Status = IcarusTerminalStatus.AWAIT_DISKS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUserInterface(IcarusTerminalComponent component)
|
||||
{
|
||||
_userInterfaceSystem.SetUiState(component.Owner, IcarusTerminalUiKey.Key, new IcarusTerminalUiState(
|
||||
component.Status,
|
||||
(int) component.RemainingTime,
|
||||
(int) component.CooldownTime)
|
||||
);
|
||||
}
|
||||
|
||||
private bool IsAccessGranted(EntityUid uid)
|
||||
{
|
||||
return TryComp<ItemSlotsComponent>(uid, out var itemSlotsComponent) && itemSlotsComponent.Slots.Values.All(v => v.HasItem);
|
||||
}
|
||||
|
||||
private void Authorize(IcarusTerminalComponent component)
|
||||
{
|
||||
component.Status = IcarusTerminalStatus.FIRE_READY;
|
||||
|
||||
if (!component.AuthorizationNotified)
|
||||
{
|
||||
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("icarus-authorized-announcement"),
|
||||
Loc.GetString("icarus-announce-sender"),
|
||||
false,
|
||||
component.ActiveGoldenEyeAlertSound);
|
||||
component.AuthorizationNotified = true;
|
||||
|
||||
RaiseLocalEvent(new IcarusActivatedEvent()
|
||||
{
|
||||
OwningStation = Transform(component.Owner).GridUid,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void TickCooldown(IcarusTerminalComponent component, float frameTime)
|
||||
{
|
||||
component.CooldownTime -= frameTime;
|
||||
if (component.CooldownTime <= 0)
|
||||
{
|
||||
component.CooldownTime = 0;
|
||||
component.Status = IcarusTerminalStatus.AWAIT_DISKS;
|
||||
UpdateStatus(component);
|
||||
}
|
||||
|
||||
UpdateUserInterface(component);
|
||||
}
|
||||
|
||||
private void TickTimer(IcarusTerminalComponent component, float frameTime)
|
||||
{
|
||||
component.RemainingTime -= frameTime;
|
||||
if (component.RemainingTime <= 0)
|
||||
{
|
||||
component.RemainingTime = 0;
|
||||
ActivateBeamOnStation(component);
|
||||
}
|
||||
|
||||
UpdateUserInterface(component);
|
||||
}
|
||||
|
||||
private void ActivateBeamOnStation(IcarusTerminalComponent component)
|
||||
{
|
||||
component.Status = IcarusTerminalStatus.COOLDOWN;
|
||||
component.CooldownTime = component.Cooldown;
|
||||
|
||||
_audio.PlayGlobal(component.FireSound, Filter.Broadcast(), false);
|
||||
FireBeam(GetStationArea());
|
||||
}
|
||||
|
||||
public MapCoordinates FireBeam(Box2 area)
|
||||
{
|
||||
TryGetBeamSpawnLocation(area, out var coords, out var offset);
|
||||
Logger.DebugS("icarus", $"Try spawn beam on coords: {coords.ToString()}");
|
||||
var entUid = Spawn(IcarusBeamPrototypeId, coords);
|
||||
_icarusSystem.LaunchInDirection(entUid, -offset.Normalized());
|
||||
return coords;
|
||||
}
|
||||
|
||||
private void TryGetBeamSpawnLocation(Box2 area, out MapCoordinates coords,
|
||||
out Vector2 offset)
|
||||
{
|
||||
coords = MapCoordinates.Nullspace;
|
||||
offset = Vector2.Zero;
|
||||
|
||||
var center = area.Center;
|
||||
var distance = (area.TopRight - center).Length();
|
||||
var angle = new Angle(_robustRandom.NextFloat() * MathF.Tau);
|
||||
|
||||
offset = angle.RotateVec(new Vector2(distance + 50f, 0));
|
||||
coords = new MapCoordinates(center + offset, _gameTicker.DefaultMap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine box of all stations and all of they grids. (copy-paste from pirate gamerule)
|
||||
/// </summary>
|
||||
/// <returns>Box of all station grids</returns>
|
||||
private Box2 GetStationArea()
|
||||
{
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var areas = _stationSystem.GetStations().SelectMany(s =>
|
||||
Comp<StationDataComponent>(s).Grids.Select(g =>
|
||||
xformQuery.GetComponent(g).WorldMatrix.TransformBox(Comp<MapGridComponent>(g).LocalAABB))).ToArray();
|
||||
|
||||
var stationArea = areas[0];
|
||||
for (var i = 1; i < areas.Length; i++)
|
||||
stationArea.Union(areas[i]);
|
||||
|
||||
return stationArea;
|
||||
}
|
||||
|
||||
|
||||
public sealed class IcarusActivatedEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid? OwningStation;
|
||||
}
|
||||
}
|
||||
201
Content.Server/_Sunrise/Interrogator/InterrogatorSystem.cs
Normal file
201
Content.Server/_Sunrise/Interrogator/InterrogatorSystem.cs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
using System.Linq;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared._Sunrise.Interrogator;
|
||||
using Content.Shared.Climbing.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server._Sunrise.Interrogator
|
||||
{
|
||||
public sealed class InterrogatorSystem : SharedInterrogatorSystem
|
||||
{
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!;
|
||||
[Dependency] private readonly ClimbSystem _climbSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _light = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<InterrogatorComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<InterrogatorComponent, GetVerbsEvent<AlternativeVerb>>(AddAlternativeVerbs);
|
||||
SubscribeLocalEvent<InterrogatorComponent, DragDropTargetEvent>(HandleDragDropOn);
|
||||
SubscribeLocalEvent<InterrogatorComponent, InterrogatorDragFinished>(OnDragFinished);
|
||||
SubscribeLocalEvent<InterrogatorComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<InterrogatorComponent, AnchorStateChangedEvent>(OnAnchorChanged);
|
||||
SubscribeLocalEvent<InterrogatorComponent, EntRemovedFromContainerMessage>(OnEjected);
|
||||
SubscribeLocalEvent<ActiveInterrogatorComponent, ComponentStartup>(OnExtractStart);
|
||||
SubscribeLocalEvent<ActiveInterrogatorComponent, ComponentShutdown>(OnExtractStop);
|
||||
}
|
||||
|
||||
private void OnAnchorChanged(EntityUid uid, InterrogatorComponent component, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
if (!args.Anchored)
|
||||
_container.EmptyContainer(component.BodyContainer);
|
||||
}
|
||||
|
||||
private void StopExtracting(Entity<InterrogatorComponent> ent)
|
||||
{
|
||||
RemCompDeferred<ActiveInterrogatorComponent>(ent);
|
||||
}
|
||||
|
||||
private void OnExtractStart(Entity<ActiveInterrogatorComponent> ent, ref ComponentStartup args)
|
||||
{
|
||||
if (!TryComp<InterrogatorComponent>(ent, out var interrogatorComponent))
|
||||
return;
|
||||
//SetAppearance(ent.Owner, MicrowaveVisualState.Cooking, microwaveComponent);
|
||||
|
||||
interrogatorComponent.ExtractionProgress = 0;
|
||||
interrogatorComponent.PlayingStream =
|
||||
_audio.PlayPvs(interrogatorComponent.ExtractingSound, ent, AudioParams.Default.WithLoop(true).WithMaxDistance(5))?.Entity;
|
||||
UpdateAppearance(ent);
|
||||
}
|
||||
|
||||
private void OnExtractStop(Entity<ActiveInterrogatorComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (!TryComp<InterrogatorComponent>(ent, out var interrogatorComponent))
|
||||
return;
|
||||
|
||||
interrogatorComponent.ExtractionProgress = 0;
|
||||
//SetAppearance(ent.Owner, MicrowaveVisualState.Idle, microwaveComponent);
|
||||
interrogatorComponent.PlayingStream = _audio.Stop(interrogatorComponent.PlayingStream);
|
||||
_audio.PlayPvs(interrogatorComponent.ExtractDoneSound, ent);
|
||||
}
|
||||
|
||||
private void OnEjected(Entity<InterrogatorComponent> interrogator, ref EntRemovedFromContainerMessage args)
|
||||
{
|
||||
StopExtracting(interrogator);
|
||||
}
|
||||
|
||||
private void OnPowerChanged(Entity<InterrogatorComponent> entity, ref PowerChangedEvent args)
|
||||
{
|
||||
// Needed to avoid adding/removing components on a deleted entity
|
||||
if (Terminating(entity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args.Powered)
|
||||
{
|
||||
StopExtracting(entity);
|
||||
EjectBody(entity.Owner, entity.Comp);
|
||||
|
||||
if (_light.TryGetLight(entity.Owner, out var light))
|
||||
{
|
||||
_light.SetEnabled(entity.Owner, false, light);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateAppearance(entity.Owner, entity.Comp);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<ActiveInterrogatorComponent, InterrogatorComponent>();
|
||||
while (query.MoveNext(out var uid, out var _, out var interrogator))
|
||||
{
|
||||
if (!_powerReceiverSystem.IsPowered(uid))
|
||||
continue;
|
||||
|
||||
if (interrogator.BodyContainer.ContainedEntity == null)
|
||||
continue;
|
||||
|
||||
if (_mobState.IsDead(interrogator.BodyContainer.ContainedEntity.Value))
|
||||
{
|
||||
StopExtracting((uid, interrogator));
|
||||
EjectBody(uid, interrogator);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (interrogator.BodyContainer.ContainedEntity == null)
|
||||
continue;
|
||||
|
||||
interrogator.ExtractionProgress += frameTime;
|
||||
if (interrogator.ExtractionProgress < interrogator.ExtractionTime)
|
||||
continue;
|
||||
|
||||
EjectImplants(interrogator.BodyContainer.ContainedEntity.Value);
|
||||
StopExtracting((uid, interrogator));
|
||||
EjectBody(uid, interrogator);
|
||||
}
|
||||
}
|
||||
|
||||
private void EjectImplants(EntityUid target)
|
||||
{
|
||||
if (_container.TryGetContainer(target, ImplanterComponent.ImplantSlotId, out var implantContainer))
|
||||
{
|
||||
var implantCompQuery = GetEntityQuery<SubdermalImplantComponent>();
|
||||
|
||||
// Create a copy of the ContainedEntities list
|
||||
var implants = implantContainer.ContainedEntities.ToList();
|
||||
|
||||
foreach (var implant in implants)
|
||||
{
|
||||
if (!implantCompQuery.TryGetComponent(implant, out var implantComp))
|
||||
continue;
|
||||
|
||||
// Don't remove a permanent implant and look for the next that can be drawn
|
||||
if (!_container.CanRemove(implant, implantContainer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_container.Remove(implant, implantContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override EntityUid? EjectBody(EntityUid uid, InterrogatorComponent? component)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return null;
|
||||
if (component.BodyContainer.ContainedEntity is not { Valid: true } contained)
|
||||
return null;
|
||||
base.EjectBody(uid, component);
|
||||
_climbSystem.ForciblySetClimbing(contained, uid);
|
||||
return contained;
|
||||
}
|
||||
|
||||
private void OnDragFinished(Entity<InterrogatorComponent> entity, ref InterrogatorDragFinished args)
|
||||
{
|
||||
if (args.Cancelled || args.Handled || args.Args.Target == null)
|
||||
return;
|
||||
|
||||
if (InsertBody(entity.Owner, args.Args.Target.Value, entity.Comp))
|
||||
{
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(args.User)} inserted {ToPrettyString(args.Args.Target.Value)} into {ToPrettyString(entity.Owner)}");
|
||||
}
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void HandleDragDropOn(Entity<InterrogatorComponent> entity, ref DragDropTargetEvent args)
|
||||
{
|
||||
if (entity.Comp.BodyContainer.ContainedEntity != null)
|
||||
return;
|
||||
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, entity.Comp.EntryDelay, new InterrogatorDragFinished(), entity, target: args.Dragged, used: entity)
|
||||
{
|
||||
BreakOnDamage = true,
|
||||
BreakOnMove = true,
|
||||
NeedHand = false,
|
||||
};
|
||||
_doAfterSystem.TryStartDoAfter(doAfterArgs);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -103,7 +103,7 @@ public sealed partial class CCVars
|
|||
/// Any value equal to or less than zero will disable this check.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> FTLMassLimit =
|
||||
CVarDef.Create("shuttle.mass_limit", 300f, CVar.SERVERONLY);
|
||||
CVarDef.Create("shuttle.mass_limit", 0f, CVar.SERVERONLY); // Sunrise-Edit
|
||||
|
||||
/// <summary>
|
||||
/// How long to knock down entities for if they aren't buckled when FTL starts and stops.
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ public sealed partial class GameRuleComponent : Component
|
|||
[DataField]
|
||||
public int MinPlayers;
|
||||
|
||||
[DataField]
|
||||
public int MinCommandStaff;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this rule not having enough players will cancel the preset selection.
|
||||
/// If false, it will simply not run silently.
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public sealed partial class ImplanterComponent : Component
|
|||
/// Good for single-use injectors
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool ImplantOnly;
|
||||
public bool ImplantOnly = true; // Sunrsie-Edit
|
||||
|
||||
/// <summary>
|
||||
/// The current mode of the implanter
|
||||
|
|
|
|||
|
|
@ -49,6 +49,16 @@ public sealed partial class SubdermalImplantComponent : Component
|
|||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist? Blacklist;
|
||||
|
||||
// Sunrise-Start
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("dropContainerItemsIfGibbed"), AutoNetworkedField]
|
||||
public bool DropContainerItemsIfGibbed = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("deleteWhenDraw"), AutoNetworkedField]
|
||||
public bool DeleteWhenDraw;
|
||||
// Sunrise-End
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ public abstract class SharedImplanterSystem : EntitySystem
|
|||
|
||||
SubscribeLocalEvent<ImplanterComponent, ComponentInit>(OnImplanterInit);
|
||||
SubscribeLocalEvent<ImplanterComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
|
||||
SubscribeLocalEvent<ImplanterComponent, EntRemovedFromContainerMessage>(OnEntEjected); // Sunrise-Edit
|
||||
SubscribeLocalEvent<ImplanterComponent, ExaminedEvent>(OnExamine);
|
||||
}
|
||||
|
||||
|
|
@ -43,8 +44,19 @@ public abstract class SharedImplanterSystem : EntitySystem
|
|||
{
|
||||
var implantData = EntityManager.GetComponent<MetaDataComponent>(args.Entity);
|
||||
component.ImplantData = (implantData.EntityName, implantData.EntityDescription);
|
||||
ChangeOnImplantVisualizer(uid, component); // Sunrise-Edit
|
||||
Dirty(uid, component); // Sunrise-Edit
|
||||
}
|
||||
|
||||
// Sunrise-Start
|
||||
private void OnEntEjected(EntityUid uid, ImplanterComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
component.ImplantData = ("", "");
|
||||
ChangeOnImplantVisualizer(uid, component);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
// Sunrise-End
|
||||
|
||||
private void OnExamine(EntityUid uid, ImplanterComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (!component.ImplanterSlot.HasItem || !args.IsInDetailsRange)
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
|
|||
if (component.ImplantedEntity == null || _net.IsClient)
|
||||
return;
|
||||
|
||||
// Sunrise-Start
|
||||
if (args.Container.ID != "implant")
|
||||
return;
|
||||
// Sunrise-End
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(component.ImplantAction))
|
||||
{
|
||||
_actionsSystem.AddAction(component.ImplantedEntity.Value, ref component.Action, component.ImplantAction, uid);
|
||||
|
|
@ -60,6 +65,11 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
|
|||
|
||||
private void OnRemoveAttempt(EntityUid uid, SubdermalImplantComponent component, ContainerGettingRemovedAttemptEvent args)
|
||||
{
|
||||
// Sunrise-Start
|
||||
if (args.Container.ID != "implant")
|
||||
return;
|
||||
// Sunrise-End
|
||||
|
||||
if (component.Permanent && component.ImplantedEntity != null)
|
||||
args.Cancel();
|
||||
}
|
||||
|
|
@ -69,6 +79,11 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
|
|||
if (component.ImplantedEntity == null || Terminating(component.ImplantedEntity.Value))
|
||||
return;
|
||||
|
||||
// Sunrise-Start
|
||||
if (args.Container.ID != "implant")
|
||||
return;
|
||||
// Sunrise-End
|
||||
|
||||
if (component.ImplantAction != null)
|
||||
_actionsSystem.RemoveProvidedActions(component.ImplantedEntity.Value, uid);
|
||||
|
||||
|
|
@ -86,6 +101,11 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
|
|||
|
||||
_container.RemoveEntity(storageImplant.Owner, entity, force: true, destination: entCoords);
|
||||
}
|
||||
|
||||
// Sunrsie-Start
|
||||
var ev = new ImplantEjectEvent(uid, component.ImplantedEntity.Value);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
// Sunrsie-End
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -221,3 +241,18 @@ public readonly struct ImplantImplantedEvent
|
|||
Implanted = implanted;
|
||||
}
|
||||
}
|
||||
|
||||
// Sunrise-Start
|
||||
[ByRefEvent]
|
||||
public readonly struct ImplantEjectEvent
|
||||
{
|
||||
public readonly EntityUid Implant;
|
||||
public readonly EntityUid? Implanted;
|
||||
|
||||
public ImplantEjectEvent(EntityUid implant, EntityUid? implanted)
|
||||
{
|
||||
Implant = implant;
|
||||
Implanted = implanted;
|
||||
}
|
||||
}
|
||||
// Sunrise-End
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ namespace Content.Shared.PDA
|
|||
[DataField("id", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? IdCard;
|
||||
|
||||
[DataField("pen", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? Pen;
|
||||
|
||||
[ViewVariables] public EntityUid? ContainedId;
|
||||
[ViewVariables] public bool FlashlightOn;
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ namespace Content.Shared.PDA
|
|||
{
|
||||
if (pda.IdCard != null)
|
||||
pda.IdSlot.StartingItem = pda.IdCard;
|
||||
if (pda.Pen != null)
|
||||
pda.PenSlot.StartingItem = pda.Pen;
|
||||
|
||||
ItemSlotsSystem.AddItemSlot(uid, PdaComponent.PdaIdSlotId, pda.IdSlot);
|
||||
ItemSlotsSystem.AddItemSlot(uid, PdaComponent.PdaPenSlotId, pda.PenSlot);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
using Content.Shared.StatusIcon;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._Sunrise.AssaultOps
|
||||
{
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class AssaultOperativeComponent : Component
|
||||
{
|
||||
[DataField("statusIcon")]
|
||||
public ProtoId<FactionIconPrototype> StatusIcon { get; set; } = "SyndicateFaction";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Server._Sunrise.AssaultOps;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class AssaultOpsShuttleComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public EntityUid AssociatedRule;
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace Content.Shared._Sunrise.AssaultOps.Icarus;
|
||||
|
||||
/// <summary>
|
||||
/// Used for Icarus terminal activation
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class IcarusKeyComponent : Component {}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared._Sunrise.AssaultOps.Icarus;
|
||||
|
||||
/// <summary>
|
||||
/// Used for Icarus terminal activation
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class IcarusTerminalComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Default fire timer value in seconds.
|
||||
/// </summary>
|
||||
[DataField("timer")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int Timer = 25;
|
||||
|
||||
/// <summary>
|
||||
/// How long until the beam can arm again after fire.
|
||||
/// </summary>
|
||||
[DataField("cooldown")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int Cooldown = 240;
|
||||
|
||||
/// <summary>
|
||||
/// Current status of a terminal.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public IcarusTerminalStatus Status = IcarusTerminalStatus.AWAIT_DISKS;
|
||||
|
||||
/// <summary>
|
||||
/// Time until beam will be spawned in seconds.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float RemainingTime;
|
||||
|
||||
/// <summary>
|
||||
/// Time until beam cooldown will expire in seconds.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float CooldownTime;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("keySlots")]
|
||||
public int KeySlots = 3;
|
||||
|
||||
[ViewVariables]
|
||||
public Container KeyContainer = default!;
|
||||
public const string KeyContainerName = "key_slots";
|
||||
|
||||
[ViewVariables]
|
||||
public float TimerRoundEnd = 1200;
|
||||
|
||||
[DataField("activeGoldenEyeAlertSound")]
|
||||
public SoundSpecifier ActiveGoldenEyeAlertSound = new SoundPathSpecifier("/Audio/_Sunrise/AssaultOperatives/golden_eye_alarm.ogg");
|
||||
|
||||
[DataField("alertSound")]
|
||||
public SoundSpecifier AlertSound = new SoundPathSpecifier("/Audio/_Sunrise/AssaultOperatives/icarus_alarm.ogg");
|
||||
|
||||
[DataField("fireSound")]
|
||||
public SoundSpecifier FireSound = new SoundPathSpecifier("/Audio/_Sunrise/AssaultOperatives/sunbeam_fire.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Check if already notified about system authorization
|
||||
/// </summary>
|
||||
public bool AuthorizationNotified = false;
|
||||
}
|
||||
37
Content.Shared/_Sunrise/AssaultOps/Icarus/SharedIcarus.cs
Normal file
37
Content.Shared/_Sunrise/AssaultOps/Icarus/SharedIcarus.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._Sunrise.AssaultOps.Icarus;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum IcarusTerminalUiKey
|
||||
{
|
||||
Key,
|
||||
}
|
||||
|
||||
public enum IcarusTerminalStatus : byte
|
||||
{
|
||||
AWAIT_DISKS,
|
||||
FIRE_READY,
|
||||
FIRE_PREPARING,
|
||||
COOLDOWN
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class IcarusTerminalUiState : BoundUserInterfaceState
|
||||
{
|
||||
public IcarusTerminalStatus Status { get; }
|
||||
public int RemainingTime { get; }
|
||||
public int CooldownTime { get; }
|
||||
|
||||
public IcarusTerminalUiState(IcarusTerminalStatus status, int remainingTime, int cooldownTime)
|
||||
{
|
||||
Status = status;
|
||||
RemainingTime = remainingTime;
|
||||
CooldownTime = cooldownTime;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class IcarusTerminalFireMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace Content.Shared._Sunrise.Interrogator;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class ActiveInterrogatorComponent : Component
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._Sunrise.Interrogator;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class InterrogatorComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
public ContainerSlot BodyContainer = default!;
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float ExtractionTime = 30f;
|
||||
|
||||
[ViewVariables]
|
||||
public float ExtractionProgress = 0;
|
||||
|
||||
public EntityUid? PlayingStream;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("entryDelay")]
|
||||
public float EntryDelay = 2f;
|
||||
|
||||
// SUNRISE-TODO: Более подходящий звук работы, мейби взять из сс13
|
||||
[DataField("extractingSound")]
|
||||
public SoundSpecifier ExtractingSound = new SoundPathSpecifier("/Audio/Machines/microwave_loop.ogg");
|
||||
|
||||
[DataField("extractDoneSound")]
|
||||
public SoundSpecifier ExtractDoneSound = new SoundPathSpecifier("/Audio/_Sunrise/Interrogator/ding.ogg");
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum InterrogatorVisuals : byte
|
||||
{
|
||||
ContainsEntity,
|
||||
IsOn
|
||||
}
|
||||
}
|
||||
169
Content.Shared/_Sunrise/Interrogator/SharedInterrogatorSystem.cs
Normal file
169
Content.Shared/_Sunrise/Interrogator/SharedInterrogatorSystem.cs
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._Sunrise.Interrogator;
|
||||
|
||||
public abstract partial class SharedInterrogatorSystem: EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly SharedStandingStateSystem _standingStateSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _light = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<InterrogatorComponent, CanDropTargetEvent>(OnInterrogatorCanDropOn);
|
||||
}
|
||||
|
||||
protected void OnComponentInit(EntityUid uid, InterrogatorComponent interrogatorComponent, ComponentInit args)
|
||||
{
|
||||
interrogatorComponent.BodyContainer = _containerSystem.EnsureContainer<ContainerSlot>(uid, "body_container");
|
||||
}
|
||||
|
||||
private void OnInterrogatorCanDropOn(EntityUid uid, InterrogatorComponent component, ref CanDropTargetEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.CanDrop = HasComp<BodyComponent>(args.Dragged);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
protected void UpdateAppearance(EntityUid uid, InterrogatorComponent? component = null, AppearanceComponent? appearance = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var interrogatorEnabled = HasComp<ActiveInterrogatorComponent>(uid);
|
||||
|
||||
if (_light.TryGetLight(uid, out var light))
|
||||
{
|
||||
_light.SetEnabled(uid, interrogatorEnabled && component.BodyContainer.ContainedEntity != null, light);
|
||||
}
|
||||
|
||||
if (!Resolve(uid, ref appearance))
|
||||
return;
|
||||
|
||||
_appearanceSystem.SetData(uid, InterrogatorComponent.InterrogatorVisuals.ContainsEntity, component.BodyContainer.ContainedEntity == null, appearance);
|
||||
_appearanceSystem.SetData(uid, InterrogatorComponent.InterrogatorVisuals.IsOn, interrogatorEnabled, appearance);
|
||||
}
|
||||
|
||||
public bool InsertBody(EntityUid uid, EntityUid target, InterrogatorComponent component)
|
||||
{
|
||||
if (component.BodyContainer.ContainedEntity != null)
|
||||
return false;
|
||||
|
||||
if (!HasComp<MobStateComponent>(target))
|
||||
return false;
|
||||
|
||||
var xform = Transform(target);
|
||||
_containerSystem.Insert((target, xform), component.BodyContainer);
|
||||
|
||||
_standingStateSystem.Stand(target, force: true); // Force-stand the mob so that the cryo pod sprite overlays it fully
|
||||
|
||||
UpdateAppearance(uid, component);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TryEjectBody(EntityUid uid, EntityUid userId, InterrogatorComponent? component)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var ejected = EjectBody(uid, component);
|
||||
if (ejected != null)
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(ejected.Value)} ejected from {ToPrettyString(uid)} by {ToPrettyString(userId)}");
|
||||
}
|
||||
|
||||
protected virtual EntityUid? EjectBody(EntityUid uid, InterrogatorComponent? component)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return null;
|
||||
|
||||
if (component.BodyContainer.ContainedEntity is not {Valid: true} contained)
|
||||
return null;
|
||||
|
||||
_containerSystem.Remove(contained, component.BodyContainer);
|
||||
// Insidecomponent is removed automatically in its EntGotRemovedFromContainerMessage listener
|
||||
// RemComp<Insidecomponent>(contained);
|
||||
|
||||
// Restore the correct position of the patient. Checking the components manually feels hacky, but I did not find a better way for now.
|
||||
if (HasComp<KnockedDownComponent>(contained) || _mobStateSystem.IsIncapacitated(contained))
|
||||
{
|
||||
_standingStateSystem.Down(contained);
|
||||
}
|
||||
else
|
||||
{
|
||||
_standingStateSystem.Stand(contained);
|
||||
}
|
||||
|
||||
UpdateAppearance(uid, component);
|
||||
return contained;
|
||||
}
|
||||
|
||||
private void TryStartExtract(EntityUid uid, EntityUid userId, InterrogatorComponent? interrogatorComponent)
|
||||
{
|
||||
if (!Resolve(uid, ref interrogatorComponent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (interrogatorComponent.BodyContainer.ContainedEntity == null)
|
||||
return;
|
||||
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(uid)} start extract impants from {ToPrettyString(interrogatorComponent.BodyContainer.ContainedEntity)} by {ToPrettyString(userId)}");
|
||||
EnsureComp<ActiveInterrogatorComponent>(uid);
|
||||
}
|
||||
|
||||
protected void AddAlternativeVerbs(EntityUid uid, InterrogatorComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
// Eject verb
|
||||
if (component.BodyContainer.ContainedEntity != null)
|
||||
{
|
||||
args.Verbs.Add(new AlternativeVerb
|
||||
{
|
||||
Text = Loc.GetString("interrogator-verb-noun-occupant"),
|
||||
Category = VerbCategory.Eject,
|
||||
Priority = 1,
|
||||
Act = () => TryEjectBody(uid, args.User, component)
|
||||
});
|
||||
}
|
||||
|
||||
// Extract verb
|
||||
if (component.BodyContainer.ContainedEntity != null && !HasComp<ActiveInterrogatorComponent>(uid))
|
||||
{
|
||||
args.Verbs.Add(new AlternativeVerb
|
||||
{
|
||||
Text = Loc.GetString("interrogator-verb-start-extract"),
|
||||
Priority = 2,
|
||||
Act = () => TryStartExtract(uid, args.User, component)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class InterrogatorDragFinished : SimpleDoAfterEvent
|
||||
{
|
||||
}
|
||||
}
|
||||
10
Content.Shared/_Sunrise/Shuttles/NukeOpsShuttleComponent.cs
Normal file
10
Content.Shared/_Sunrise/Shuttles/NukeOpsShuttleComponent.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class NukeOpsShuttleComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public EntityUid AssociatedRule;
|
||||
}
|
||||
Binary file not shown.
BIN
Resources/Audio/_Sunrise/AssaultOperatives/golden_eye_alarm.ogg
Normal file
BIN
Resources/Audio/_Sunrise/AssaultOperatives/golden_eye_alarm.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/_Sunrise/AssaultOperatives/icarus_alarm.ogg
Normal file
BIN
Resources/Audio/_Sunrise/AssaultOperatives/icarus_alarm.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/_Sunrise/AssaultOperatives/sunbeam_fire.ogg
Normal file
BIN
Resources/Audio/_Sunrise/AssaultOperatives/sunbeam_fire.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/_Sunrise/AssaultOperatives/sunbeam_loop.ogg
Normal file
BIN
Resources/Audio/_Sunrise/AssaultOperatives/sunbeam_loop.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/_Sunrise/Interrogator/ding.ogg
Normal file
BIN
Resources/Audio/_Sunrise/Interrogator/ding.ogg
Normal file
Binary file not shown.
|
|
@ -0,0 +1,2 @@
|
|||
ent-Interrogator = Экстрактор имплантов
|
||||
.desc = Устройство предназначеное для извлечения имплантов из гуманоидов.
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
ent-EnergySwordDoubleBiocode = { ent-EnergySwordDouble }
|
||||
.suffix = БИОКОД
|
||||
.desc = { ent-EnergySwordDouble.desc }
|
||||
ent-EnergyDaggerBiocode = { ent-EnergyDagger }
|
||||
.suffix = БИОКОД
|
||||
.desc = { ent-EnergyDagger.desc }
|
||||
ent-EnergySwordBiocode = { ent-EnergySword }
|
||||
.suffix = БИОКОД
|
||||
.desc = { ent-EnergySword.desc }
|
||||
|
|
|
|||
|
|
@ -153,3 +153,6 @@ ent-PiratePDA = КПК с Адамовой головой
|
|||
ent-SyndiAgentPDA = медицинский ало-красный КПК
|
||||
.suffix = КПК оперативника медика Синдиката, Ядерный Оперативник
|
||||
.desc = Смотря на этот КПК, ваше сердцебиение учащается... словно его владелец проводил немыслимые и ужасные медицинские эксперименты.
|
||||
ent-AssaultOpsPDA = { ent-PassengerPDA }
|
||||
.desc = { ent-PassengerPDA }
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
admin-verb-text-make-assault-operative = Сделать цель членом ДО.
|
||||
admin-verb-make-assault-operative = Сделать цель членом диверсионного отряда.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
roles-antag-assault-operative-name = Член диверсионного отряда
|
||||
roles-antag-assault-operative-objective = Похищайте глав станции, извлекайте из голов ключи и обратите оружие Nanotrasen против них.
|
||||
|
||||
roles-antag-assault-commander-name = Командир диверсионного отряда
|
||||
roles-antag-assault-commander-objective = Руководите секретной операцией по похищению глав станции с целью достать ключи доступа Икарус.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# Announce
|
||||
icarus-announce-sender = Оборонная сеть "Икарус"
|
||||
icarus-authorized-announcement = ОБНАРУЖЕНА НЕСАНКЦИОНИРОВАННАЯ ЗАГРУЗКА КЛЮЧ-КАРТ. ВСЕ КЛЮЧИ ЗАГРУЖЕНЫ!
|
||||
icarus-fire-announcement = /// ВЗЛОМ ЗАЩИЩЕННОЙ СЕТИ "ИКАРУС" ///
|
||||
Обнаружен несанкционированный доступ к оборонной сеть "Икарус"
|
||||
ИКАРУС онлайн.
|
||||
Обнаружено переопределение системы нацеливания...
|
||||
Новая цель: { $station }
|
||||
Активированы протоколы стрельбы ИКАРУС.
|
||||
РАСЧЕТНОЕ ВРЕМЯ выстрела: { $seconds } { $seconds ->
|
||||
[one] секунду
|
||||
[few] секунды
|
||||
*[other] секунд
|
||||
}.
|
||||
|
||||
# UI
|
||||
icarus-ui-window-title = Терминал Icarus
|
||||
icarus-ui-fire-button = Огонь
|
||||
icarus-ui-timer-label = Время до выстрела:
|
||||
icarus-ui-cooldown-label = Перезарядка:
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
assault-ops-document-goal = ####### СОВЕРШЕННО СЕКРЕТНО ########
|
||||
|
||||
Вы - специальный диверсионный отряд синдиката.
|
||||
|
||||
Ваша цель - взлом системы обритального оружия Icarus в секторе Алеф.
|
||||
|
||||
Для взлома системы вам необходимо добыть 3 ключа доступа Golden Key, которые помещены в тела руководящего состава станции.
|
||||
|
||||
Вам был предоставлен шатл, одежда хамелеон и аплинки со всем необходимым для выполнения задачи. Так же у вас есть навигаторы которые указывают местонахождение ближайшего ключа. Навигатор покажет новую цель только после установки целевого ключа в терминал, учтите это.
|
||||
|
||||
Запомните, ваша задача действовать скрытно, не дайте персоналу обнаружить свой шатл или раскрыть ваши цели.
|
||||
|
||||
После взлома системы у вас есть возможность открыть огонь по станции, используйте данную возможность по собственному желанию.
|
||||
|
||||
####### СОВЕРШЕННО СЕКРЕТНО ########
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
assaultops-title = Диверсионный отряд
|
||||
assaultops-description = На станцию нацелился диверсионный отряд с целью завладеть доступом к секретному оружию NanoTransen Icarus.
|
||||
assaultops-welcome =
|
||||
Вы - член элитного диверсионного отряда. Ваша задача - напасть на { $station } и завладеть всеми ключами доступа GoldenEye, расположенных в головах руководства объекта. Ваше руководство, Синдикат, снабдило вас всем необходимым для выполнения этой задачи.
|
||||
Удачи агент!
|
||||
|
||||
assaultops-opsmajor = [bold][color=crimson]Крупная победа Синдиката![/color][/bold]
|
||||
assaultops-opsminor = [bold][color=crimson]Малая победа Синдиката![/color][/bold]
|
||||
assaultops-hearty = [bold][color=crimson]Посмертная победа![/color][/bold]
|
||||
assaultops-stalemate = [bold][color=yellow]Ничейный исход![/color][/bold]
|
||||
assaultops-crewmajor = [bold][color=green]Разгромная победа экипажа![/color][/bold]
|
||||
|
||||
assaultops-cond-icarusactivated = Икарус был активирован.
|
||||
assaultops-cond-allopsdead = Все члены диверсионного отряда погибли.
|
||||
assaultops-cond-someopssalive = Несколько членов диверсионного отряда погибли.
|
||||
assaultops-cond-allopsalive = Все члены диверсионного отряда выжили.
|
||||
|
||||
assaultops-list-start = Членами диверсионного отряда были:
|
||||
assaultops-list-name = - [color=white]{ $name }[/color] ([color=gray]{ $user }[/color])
|
||||
|
||||
assaultops-not-enough-ready-players = Недостаточно игроков готовы к игре! { $readyPlayersCount } игроков из необходимых { $minimumPlayers } готовы. Нельзя начать Диверсионный отряд.
|
||||
assaultops-not-enough-keys = Недостаточно ключей! { $keys } игроков из необходимых { $requiredKeys } являются главами. Нельзя начать Диверсионный отряд.
|
||||
assaultops-no-one-ready = Нет готовых игроков! Нельзя начать Диверсионный отряд.
|
||||
|
||||
assaultops-role-agent = Агент
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
interrogator-verb-noun-occupant = Пациент
|
||||
interrogator-verb-start-extract = Начать извлечение
|
||||
|
|
@ -44,6 +44,7 @@ latejoin-arrivals-direction-time = Шаттл, который доставит
|
|||
latejoin-arrivals-dumped-from-shuttle = Таинственная сила не позволяет вам улететь на шаттле прибытия.
|
||||
latejoin-arrivals-teleport-to-spawn = Таинственная сила телепортирует вас с шаттла прибытия. Удачной смены!
|
||||
preset-not-enough-ready-players = Не удалось запустить пресет { $presetName }. Требуется { $minimumPlayers } игроков, но готовы только { $readyPlayersCount }.
|
||||
preset-not-enough-ready-command-staff = Не удалось запустить пресет { $presetName }. Требуется { $minimumCommandStaff } членов командного состава, но может быть только { $readyCommandStaffCount }.
|
||||
preset-no-one-ready = Не удалось запустить режим { $presetName }. Нет готовых игроков.
|
||||
game-run-level-PreRoundLobby = Лобби до начала раунда
|
||||
game-run-level-InRound = В раунде
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
examine-pinpointer-linked = Он отслеживает: { $target }
|
||||
|
||||
pinpointer-target-switched = Цель переключена
|
||||
pinpointer-switch-target = Сменить цель
|
||||
|
|
|
|||
8091
Resources/Maps/_Sunrise/Shuttles/assaultops.yml
Normal file
8091
Resources/Maps/_Sunrise/Shuttles/assaultops.yml
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -79,6 +79,13 @@
|
|||
Telecrystal: 8
|
||||
categories:
|
||||
- UplinkWeaponry
|
||||
#Sunrise-start
|
||||
conditions:
|
||||
- !type:StoreWhitelistCondition
|
||||
whitelist:
|
||||
tags:
|
||||
- AssaultOpsUplink
|
||||
#Sunrise-end
|
||||
|
||||
- type: listing
|
||||
id: UplinkEnergyDagger
|
||||
|
|
@ -1267,6 +1274,7 @@
|
|||
blacklist:
|
||||
tags:
|
||||
- NukeOpsUplink
|
||||
- AssaultOpsUplink
|
||||
|
||||
- type: listing
|
||||
id: UplinkReinforcementRadioSyndicateNukeops # Version for Nukeops that spawns another nuclear operative without the uplink.
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@
|
|||
- Guardian # no holoparasite macrobomb wombo combo
|
||||
tags:
|
||||
- Unimplantable
|
||||
currentMode: Draw
|
||||
currentMode: Inject #Sunrise-Edit
|
||||
implanterSlot:
|
||||
name: Implant
|
||||
locked: True
|
||||
priority: 0
|
||||
ejectOnBreak: true #Sunrise-Edit
|
||||
swap: false #Sunrise-Edit
|
||||
whitelist:
|
||||
tags:
|
||||
- SubdermalImplant
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
- type: entity
|
||||
id: BaseSubdermalImplant
|
||||
parent: BaseItem
|
||||
name: implant
|
||||
description: A microscopic chip that's injected under the skin.
|
||||
abstract: true
|
||||
|
|
@ -8,7 +9,13 @@
|
|||
- type: Tag
|
||||
tags:
|
||||
- SubdermalImplant
|
||||
- HideContextMenu
|
||||
# Sunrsie-Start
|
||||
- type: Sprite
|
||||
sprite: _Sunrise/Entities/Objects/Implants/implants.rsi
|
||||
state: reviver_implant
|
||||
- type: Item
|
||||
size: Tiny
|
||||
# Sunrsie-End
|
||||
|
||||
#Fun implants
|
||||
|
||||
|
|
@ -17,7 +24,7 @@
|
|||
id: SadTromboneImplant
|
||||
name: sad trombone implant
|
||||
description: This implant plays a sad tune when the user dies.
|
||||
categories: [ HideSpawnMenu ]
|
||||
#categories: [ HideSpawnMenu ] Sunrsie-Edit
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
whitelist:
|
||||
|
|
@ -37,7 +44,7 @@
|
|||
id: LightImplant
|
||||
name: light implant
|
||||
description: This implant emits light from the user's skin on activation.
|
||||
categories: [ HideSpawnMenu ]
|
||||
#categories: [ HideSpawnMenu ] Sunrsie-Edit
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
implantAction: ActionToggleLight
|
||||
|
|
@ -50,7 +57,6 @@
|
|||
- type: Tag
|
||||
tags:
|
||||
- SubdermalImplant
|
||||
- HideContextMenu
|
||||
- Flashlight
|
||||
- type: UnpoweredFlashlight
|
||||
|
||||
|
|
@ -59,7 +65,7 @@
|
|||
id: BikeHornImplant
|
||||
name: bike horn implant
|
||||
description: This implant lets the user honk anywhere at any time.
|
||||
categories: [ HideSpawnMenu ]
|
||||
#categories: [ HideSpawnMenu ] Sunrsie-Edit
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
implantAction: ActionActivateHonkImplant
|
||||
|
|
@ -72,6 +78,7 @@
|
|||
- type: Tag
|
||||
tags:
|
||||
- BikeHorn
|
||||
- SubdermalImplant # Sunrsie-Edit
|
||||
|
||||
#Security implants
|
||||
|
||||
|
|
@ -80,7 +87,7 @@
|
|||
id: TrackingImplant
|
||||
name: tracking implant
|
||||
description: This implant has a tracking device attached to the suit sensor network, as well as a condition monitor for the Security radio channel.
|
||||
categories: [ HideSpawnMenu ]
|
||||
#categories: [ HideSpawnMenu ] Sunrsie-Edit
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
whitelist:
|
||||
|
|
@ -109,7 +116,7 @@
|
|||
# id: UplinkImplantErt
|
||||
# name: uplink implant
|
||||
# description: This implant lets the user access a hidden Syndicate uplink at will.
|
||||
# categories: [ HideSpawnMenu ]
|
||||
# categories: [ HideSpawnMenu ] Sunrsie-Edit
|
||||
# components:
|
||||
# - type: SubdermalImplant
|
||||
# implantAction: ActionOpenUplinkImplantErt
|
||||
|
|
@ -127,13 +134,14 @@
|
|||
# - type: Tag
|
||||
# tags:
|
||||
# - NTUplink
|
||||
# - SubdermalImplant
|
||||
|
||||
- type: entity
|
||||
parent: BaseSubdermalImplant
|
||||
id: TrackingImplantErt
|
||||
name: tracking ERT implant
|
||||
description: This implant has a tracking device attached to the suit sensor network, as well as a condition monitor for the Centcom radio channel.
|
||||
categories: [ HideSpawnMenu ]
|
||||
#categories: [ HideSpawnMenu ] Sunrsie-Edit
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
whitelist:
|
||||
|
|
@ -153,7 +161,7 @@
|
|||
id: StorageImplant
|
||||
name: storage implant
|
||||
description: This implant grants hidden storage within a person's body using bluespace technology.
|
||||
categories: [ HideSpawnMenu ]
|
||||
#categories: [ HideSpawnMenu ] Sunrsie-Edit
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
implantAction: ActionOpenStorageImplant
|
||||
|
|
@ -177,7 +185,7 @@
|
|||
id: FreedomImplant
|
||||
name: freedom implant
|
||||
description: This implant lets the user break out of hand restraints up to three times before ceasing to function anymore.
|
||||
categories: [ HideSpawnMenu ]
|
||||
#categories: [ HideSpawnMenu ] Sunrsie-Edit
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
implantAction: ActionActivateFreedomImplant
|
||||
|
|
@ -190,7 +198,7 @@
|
|||
id: UplinkImplant
|
||||
name: uplink implant
|
||||
description: This implant lets the user access a hidden Syndicate uplink at will.
|
||||
categories: [ HideSpawnMenu ]
|
||||
#categories: [ HideSpawnMenu ] Sunrsie-Edit
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
implantAction: ActionOpenUplinkImplant
|
||||
|
|
@ -208,6 +216,7 @@
|
|||
- type: Tag
|
||||
tags:
|
||||
- SyndieAgentUplink
|
||||
- SubdermalImplant
|
||||
#Sunrise-end
|
||||
|
||||
- type: entity
|
||||
|
|
@ -215,7 +224,7 @@
|
|||
id: EmpImplant
|
||||
name: EMP implant
|
||||
description: This implant creates an electromagnetic pulse when activated.
|
||||
categories: [ HideSpawnMenu ]
|
||||
#categories: [ HideSpawnMenu ] Sunrsie-Edit
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
implantAction: ActionActivateEmpImplant
|
||||
|
|
@ -230,7 +239,7 @@
|
|||
id: ScramImplant
|
||||
name: scram implant
|
||||
description: This implant randomly teleports the user within a large radius when activated.
|
||||
categories: [ HideSpawnMenu ]
|
||||
#categories: [ HideSpawnMenu ] Sunrsie-Edit
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
implantAction: ActionActivateScramImplant
|
||||
|
|
@ -242,7 +251,7 @@
|
|||
id: DnaScramblerImplant
|
||||
name: DNA scrambler implant
|
||||
description: This implant lets the user randomly change their appearance and name once.
|
||||
categories: [ HideSpawnMenu ]
|
||||
#categories: [ HideSpawnMenu ] Sunrsie-Edit
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
implantAction: ActionActivateDnaScramblerImplant
|
||||
|
|
@ -257,10 +266,10 @@
|
|||
id: MicroBombImplant
|
||||
name: micro-bomb implant
|
||||
description: This implant detonates the user upon activation or upon death.
|
||||
categories: [ HideSpawnMenu ]
|
||||
#categories: [ HideSpawnMenu ] Sunrsie-Edit
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
permanent: true
|
||||
#permanent: true Sunrsie-Edit
|
||||
implantAction: ActionActivateMicroBomb
|
||||
- type: TriggerOnMobstateChange
|
||||
mobState:
|
||||
|
|
@ -278,7 +287,6 @@
|
|||
- type: Tag
|
||||
tags:
|
||||
- SubdermalImplant
|
||||
- HideContextMenu
|
||||
- MicroBomb
|
||||
|
||||
|
||||
|
|
@ -287,10 +295,10 @@
|
|||
id: MacroBombImplant
|
||||
name: macro-bomb implant
|
||||
description: This implant creates a large explosion on death after a preprogrammed countdown.
|
||||
categories: [ HideSpawnMenu ]
|
||||
#categories: [ HideSpawnMenu ] Sunrsie-Edit
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
permanent: true
|
||||
#permanent: true Sunrsie-Edit
|
||||
- type: TriggerOnMobstateChange #Chains with OnUseTimerTrigger
|
||||
mobState:
|
||||
- Dead
|
||||
|
|
@ -314,7 +322,6 @@
|
|||
- type: Tag
|
||||
tags:
|
||||
- SubdermalImplant
|
||||
- HideContextMenu
|
||||
- MacroBomb
|
||||
|
||||
- type: entity
|
||||
|
|
@ -322,10 +329,10 @@
|
|||
id: DeathAcidifierImplant
|
||||
name: death-acidifier implant
|
||||
description: This implant melts the user and their equipment upon death.
|
||||
categories: [ HideSpawnMenu ]
|
||||
#categories: [ HideSpawnMenu ] Sunrsie-Edit
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
permanent: true
|
||||
#permanent: true Sunrsie-Edit
|
||||
implantAction: ActionActivateDeathAcidifier
|
||||
- type: TriggerOnMobstateChange
|
||||
mobState:
|
||||
|
|
@ -338,7 +345,6 @@
|
|||
- type: Tag
|
||||
tags:
|
||||
- SubdermalImplant
|
||||
- HideContextMenu
|
||||
- DeathAcidifier
|
||||
|
||||
- type: entity
|
||||
|
|
@ -346,10 +352,10 @@
|
|||
id: DeathRattleImplant
|
||||
name: death rattle implant
|
||||
description: This implant will inform the Syndicate radio channel should the user fall into critical condition or die.
|
||||
categories: [ HideSpawnMenu ]
|
||||
#categories: [ HideSpawnMenu ] Sunrsie-Edit
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
permanent: true
|
||||
#permanent: true Sunrsie-Edit
|
||||
whitelist:
|
||||
components:
|
||||
- MobState # admeme implanting a chair with rattle implant needs to give the chair mobstate so it can die first
|
||||
|
|
@ -366,10 +372,11 @@
|
|||
id: MindShieldImplant
|
||||
name: mindshield implant
|
||||
description: This implant will ensure loyalty to Nanotrasen and prevent mind control devices.
|
||||
categories: [ HideSpawnMenu ]
|
||||
#categories: [ HideSpawnMenu ] Sunrsie-Edit
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
permanent: true
|
||||
#permanent: true Sunrsie-Edit
|
||||
- type: Tag
|
||||
tags:
|
||||
- MindShield
|
||||
- SubdermalImplant
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@
|
|||
pocket1: BaseUplinkRadio0TC
|
||||
inhand:
|
||||
- NukeOpsDeclarationOfWar
|
||||
- RemoteNukeOpShuttleController # Sunrise-Edit
|
||||
|
||||
#Nuclear Operative Medic Gear
|
||||
- type: startingGear
|
||||
|
|
|
|||
25
Resources/Prototypes/_Sunrise/AssaultOps/antag.yml
Normal file
25
Resources/Prototypes/_Sunrise/AssaultOps/antag.yml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
- type: antag
|
||||
id: AssaultOperative
|
||||
name: roles-antag-assault-operative-name
|
||||
antagonist: true
|
||||
setPreference: true
|
||||
objective: roles-antag-assault-operative-objective
|
||||
requirements:
|
||||
- !type:OverallPlaytimeRequirement
|
||||
time: 7200 # 2h
|
||||
- !type:DepartmentTimeRequirement
|
||||
department: Security
|
||||
time: 3600 # 1h
|
||||
|
||||
- type: antag
|
||||
id: AssaultCommander
|
||||
name: roles-antag-assault-commander-name
|
||||
antagonist: true
|
||||
setPreference: true
|
||||
objective: roles-antag-assault-commander-objective
|
||||
requirements:
|
||||
- !type:OverallPlaytimeRequirement
|
||||
time: 7200 # 2h
|
||||
- !type:DepartmentTimeRequirement
|
||||
department: Security
|
||||
time: 7200 # 2h
|
||||
53
Resources/Prototypes/_Sunrise/AssaultOps/beam.yml
Normal file
53
Resources/Prototypes/_Sunrise/AssaultOps/beam.yml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
- type: entity
|
||||
id: IcarusBeam
|
||||
name: icarus
|
||||
categories: [ HideSpawnMenu ]
|
||||
description: A beam of light from the sun.
|
||||
components:
|
||||
- type: Clickable
|
||||
- type: MovementIgnoreGravity
|
||||
- type: Sprite
|
||||
sprite: _Sunrise/AssaultOperatives/sunray.rsi
|
||||
drawdepth: Effects
|
||||
noRot: false
|
||||
netsync: false
|
||||
scale: 6, 6
|
||||
layers:
|
||||
- state: sunray_splash
|
||||
- state: sunray
|
||||
offset: 0, 1
|
||||
- state: sunray_muzzle
|
||||
offset: 0, 2
|
||||
- type: IcarusBeam
|
||||
- type: AmbientSound
|
||||
range: 14
|
||||
sound:
|
||||
path: /Audio/_Sunrise/AssaultOperatives/sunbeam_loop.ogg
|
||||
- type: Physics
|
||||
bodyType: Dynamic
|
||||
bodyStatus: InAir
|
||||
linearDamping: 0
|
||||
angularDamping: 0
|
||||
- type: PointLight
|
||||
radius: 12
|
||||
color: yellow
|
||||
energy: 10.0
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
shape:
|
||||
!type:PhysShapeCircle
|
||||
radius: 2
|
||||
density: 1
|
||||
hard: false
|
||||
mask:
|
||||
- Impassable
|
||||
- BulletImpassable
|
||||
layer:
|
||||
- Impassable
|
||||
- MidImpassable
|
||||
- HighImpassable
|
||||
- LowImpassable
|
||||
- type: WarpPoint
|
||||
follow: true
|
||||
location: Icarus beam
|
||||
0
Resources/Prototypes/_Sunrise/AssaultOps/computer.yml
Normal file
0
Resources/Prototypes/_Sunrise/AssaultOps/computer.yml
Normal file
12
Resources/Prototypes/_Sunrise/AssaultOps/duffelbag.yml
Normal file
12
Resources/Prototypes/_Sunrise/AssaultOps/duffelbag.yml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
- type: entity
|
||||
parent: ClothingBackpackChameleon
|
||||
id: ClothingBackpackAssaultOpsFill
|
||||
suffix: Fill
|
||||
components:
|
||||
- type: StorageFill
|
||||
contents:
|
||||
- id: BoxSurvivalSyndicate
|
||||
- id: NocturineChemistryBottle
|
||||
- id: FreedomImplanter
|
||||
- id: PinpointerIcarus
|
||||
- id: EncryptionKeySyndie
|
||||
15
Resources/Prototypes/_Sunrise/AssaultOps/game_presets.yml
Normal file
15
Resources/Prototypes/_Sunrise/AssaultOps/game_presets.yml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
- type: gamePreset
|
||||
id: AssaultOps
|
||||
alias:
|
||||
- assaultops
|
||||
- assops # yeeeeah
|
||||
name: assaultops-title
|
||||
description: assaultops-description
|
||||
showInVote: true
|
||||
rules:
|
||||
- AssaultOps
|
||||
- LiteSubGamemodesRule
|
||||
- BasicStationEventScheduler
|
||||
- MeteorSwarmScheduler
|
||||
- SpaceTrafficControlEventScheduler
|
||||
- BasicRoundstartVariation
|
||||
35
Resources/Prototypes/_Sunrise/AssaultOps/ghost_roles.yml
Normal file
35
Resources/Prototypes/_Sunrise/AssaultOps/ghost_roles.yml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
- type: entity
|
||||
categories: [ HideSpawnMenu, Spawner ]
|
||||
parent: BaseAntagSpawner
|
||||
id: SpawnPointAssaultOpsCommander
|
||||
components:
|
||||
- type: GhostRole
|
||||
name: Командир диверсионного отряда
|
||||
description: Вы являетесь оперативником синдиката, назначенным на уничтожение станции. Вашей задачей как антагониста является выполнение всего необходимого для достижения этой цели.
|
||||
rules: ghost-role-information-rules-default-team-antagonist
|
||||
raffle:
|
||||
settings: default
|
||||
- type: Sprite
|
||||
sprite: Markers/jobs.rsi
|
||||
layers:
|
||||
- state: green
|
||||
- sprite: _Sunrise/Interface/Misc/antag_preview.rsi
|
||||
state: test
|
||||
|
||||
- type: entity
|
||||
categories: [ HideSpawnMenu, Spawner ]
|
||||
parent: BaseAntagSpawner
|
||||
id: SpawnPointAssaultOpsOperative
|
||||
components:
|
||||
- type: GhostRole
|
||||
name: Оперативник диверсионного отряда
|
||||
description: Вы являетесь оперативником синдиката, назначенным на уничтожение станции. Вашей задачей как антагониста является выполнение всего необходимого для достижения этой цели.
|
||||
rules: ghost-role-information-rules-default-team-antagonist
|
||||
raffle:
|
||||
settings: default
|
||||
- type: Sprite
|
||||
sprite: Markers/jobs.rsi
|
||||
layers:
|
||||
- state: green
|
||||
- sprite: _Sunrise/Interface/Misc/antag_preview.rsi
|
||||
state: test
|
||||
9
Resources/Prototypes/_Sunrise/AssaultOps/human.yml
Normal file
9
Resources/Prototypes/_Sunrise/AssaultOps/human.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Assault Operative
|
||||
- type: entity
|
||||
categories: [ HideSpawnMenu ]
|
||||
name: Assault Operative
|
||||
parent: MobHuman
|
||||
id: MobHumanAssaultOp
|
||||
components:
|
||||
- type: AssaultOperative
|
||||
- type: RandomHumanoidAppearance
|
||||
1
Resources/Prototypes/_Sunrise/AssaultOps/humanoid.yml
Normal file
1
Resources/Prototypes/_Sunrise/AssaultOps/humanoid.yml
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
7
Resources/Prototypes/_Sunrise/AssaultOps/implanters.yml
Normal file
7
Resources/Prototypes/_Sunrise/AssaultOps/implanters.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
- type: entity
|
||||
id: IcarusKeyImplanter
|
||||
name: имплантер ключа Икарус
|
||||
parent: BaseImplantOnlyImplanter
|
||||
components:
|
||||
- type: Implanter
|
||||
implant: IcarusKey
|
||||
10
Resources/Prototypes/_Sunrise/AssaultOps/jumpsuits.yml
Normal file
10
Resources/Prototypes/_Sunrise/AssaultOps/jumpsuits.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
- type: entity
|
||||
parent: ClothingUniformBase
|
||||
id: ClothingUniformJumpsuitTactical
|
||||
name: tactical turtleneck suit
|
||||
description: A double seamed tactical turtleneck disguised as a civilian grade silk suit. Intended for the most formal operator. The collar is really sharp.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Sunrise/AssaultOperatives/tactical_suit.rsi
|
||||
- type: Clothing
|
||||
sprite: _Sunrise/AssaultOperatives/tactical_suit.rsi
|
||||
20
Resources/Prototypes/_Sunrise/AssaultOps/key.yml
Normal file
20
Resources/Prototypes/_Sunrise/AssaultOps/key.yml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
- type: entity
|
||||
parent: BaseItem
|
||||
id: IcarusKey
|
||||
name: icarus authentication keycard
|
||||
description: A high profile authentication keycard to Nanotrasen's Icarus secured network.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Sunrise/AssaultOperatives/goldeneye_key.rsi
|
||||
layers:
|
||||
- state: key
|
||||
- type: IcarusKey
|
||||
- type: Item
|
||||
size: Tiny
|
||||
- type: WarpPoint
|
||||
follow: true
|
||||
location: Icarus key
|
||||
- type: SubdermalImplant
|
||||
- type: Tag
|
||||
tags:
|
||||
- SubdermalImplant
|
||||
13
Resources/Prototypes/_Sunrise/AssaultOps/masks.yml
Normal file
13
Resources/Prototypes/_Sunrise/AssaultOps/masks.yml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
- type: entity
|
||||
parent: ClothingMaskPullableBase
|
||||
id: ClothingMaskGaiter
|
||||
name: neck gaiter
|
||||
description: For the agent wanting to keep a low profile whilst concealing their identity. Has a small respirator to be used with internals.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Sunrise/AssaultOperatives/gaiter.rsi
|
||||
- type: Clothing
|
||||
sprite: _Sunrise/AssaultOperatives/gaiter.rsi
|
||||
- type: BreathMask
|
||||
- type: IngestionBlocker
|
||||
- type: IdentityBlocker
|
||||
17
Resources/Prototypes/_Sunrise/AssaultOps/mind_roles.yml
Normal file
17
Resources/Prototypes/_Sunrise/AssaultOps/mind_roles.yml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
- type: entity
|
||||
parent: BaseMindRoleAntag
|
||||
id: MindRoleAssaultOperative
|
||||
name: Assault Operative Role
|
||||
components:
|
||||
- type: MindRole
|
||||
exclusiveAntag: true
|
||||
antagPrototype: AssaultOperative
|
||||
- type: AssaultOpsRole
|
||||
|
||||
- type: entity
|
||||
parent: MindRoleNukeops
|
||||
id: MindRoleAssaultCommander
|
||||
name: Assault Commander Role
|
||||
components:
|
||||
- type: MindRole
|
||||
antagPrototype: AssaultCommander
|
||||
11
Resources/Prototypes/_Sunrise/AssaultOps/paper.yml
Normal file
11
Resources/Prototypes/_Sunrise/AssaultOps/paper.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
- type: entity
|
||||
name: цель
|
||||
parent: Paper
|
||||
id: DocumentAssaultOpsGoal
|
||||
components:
|
||||
- type: Paper
|
||||
content: assault-ops-document-goal
|
||||
stampState: paper_stamp-syndicate
|
||||
stampedBy:
|
||||
- stampedColor: '#850000FF'
|
||||
stampedName: stamp-component-stamped-name-syndicate
|
||||
10
Resources/Prototypes/_Sunrise/AssaultOps/pda.yml
Normal file
10
Resources/Prototypes/_Sunrise/AssaultOps/pda.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
- type: entity
|
||||
parent: PassengerPDA
|
||||
id: AssaultOpsPDA
|
||||
name: passenger PDA
|
||||
description: Why isn't it gray?
|
||||
components:
|
||||
- type: Pda
|
||||
id: AgentIDCard
|
||||
pen: Hypopen
|
||||
state: pda
|
||||
16
Resources/Prototypes/_Sunrise/AssaultOps/pinpointer.yml
Normal file
16
Resources/Prototypes/_Sunrise/AssaultOps/pinpointer.yml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
- type: entity
|
||||
name: pinpointer
|
||||
id: PinpointerIcarus
|
||||
parent: PinpointerBase
|
||||
components:
|
||||
- type: Pinpointer
|
||||
component: IcarusKey
|
||||
- type: Sprite
|
||||
sprite: _Sunrise/AssaultOperatives/pinpointer.rsi
|
||||
layers:
|
||||
- state: pinpointer_icarus
|
||||
map: ["enum.PinpointerLayers.Base"]
|
||||
- state: pinonnull
|
||||
map: ["enum.PinpointerLayers.Screen"]
|
||||
shader: unshaded
|
||||
visible: false
|
||||
65
Resources/Prototypes/_Sunrise/AssaultOps/roundstart.yml
Normal file
65
Resources/Prototypes/_Sunrise/AssaultOps/roundstart.yml
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
- type: entity
|
||||
id: AssaultOps
|
||||
parent: BaseGameRule
|
||||
components:
|
||||
- type: GameRule
|
||||
minPlayers: 20
|
||||
minCommandStaff: 3
|
||||
- type: AssaultOpsRule
|
||||
faction: Syndicate
|
||||
- type: LoadMapRule
|
||||
mapPath: /Maps/_Sunrise/Shuttles/assaultops.yml
|
||||
- type: RuleGrids
|
||||
- type: RandomMetadata
|
||||
nameSegments:
|
||||
- names_first
|
||||
- names_last
|
||||
- type: AntagLoadProfileRule
|
||||
speciesOverride: Human
|
||||
speciesOverrideBlacklist:
|
||||
- Vox
|
||||
- Felinid
|
||||
- Diona
|
||||
- HumanoidXeno
|
||||
- Predator
|
||||
- type: AntagSelection
|
||||
selectionTime: PrePlayerSpawn
|
||||
definitions:
|
||||
- prefRoles: [ AssaultCommander ]
|
||||
fallbackRoles: [ AssaultOperative ]
|
||||
spawnerPrototype: SpawnPointAssaultOpsCommander
|
||||
startingGear: AssaultCommanderGear
|
||||
roleLoadout:
|
||||
- RoleSurvivalAssaultOps
|
||||
components:
|
||||
- type: AssaultOperative
|
||||
# SUNRISE-TODO: Рандомное имя подходящее под под и расу
|
||||
- type: RandomMetadata
|
||||
nameSegments:
|
||||
- names_first
|
||||
- names_last
|
||||
- type: NpcFactionMember
|
||||
factions:
|
||||
- Syndicate
|
||||
mindRoles:
|
||||
- MindRoleAssaultCommander
|
||||
- prefRoles: [ AssaultOperative ]
|
||||
fallbackRoles: [ AssaultCommander ]
|
||||
spawnerPrototype: SpawnPointAssaultOpsOperative
|
||||
max: 5
|
||||
playerRatio: 15
|
||||
startingGear: AssaultOperativeGear
|
||||
roleLoadout:
|
||||
- RoleSurvivalAssaultOps
|
||||
components:
|
||||
- type: AssaultOperative
|
||||
# SUNRISE-TODO: Рандомное имя подходящее под под и расу
|
||||
- type: RandomMetadata
|
||||
nameSegments:
|
||||
- names_first
|
||||
- names_last
|
||||
- type: NpcFactionMember
|
||||
factions:
|
||||
- Syndicate
|
||||
mindRoles:
|
||||
- MindRoleAssaultOperative
|
||||
11
Resources/Prototypes/_Sunrise/AssaultOps/spawners.yml
Normal file
11
Resources/Prototypes/_Sunrise/AssaultOps/spawners.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
- type: entity
|
||||
id: SpawnPointAssaultOps
|
||||
parent: MarkerBase
|
||||
name: assaultops
|
||||
components:
|
||||
- type: SpawnPoint
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: green
|
||||
- sprite: Objects/Fun/toys.rsi
|
||||
state: base
|
||||
41
Resources/Prototypes/_Sunrise/AssaultOps/startinggear.yml
Normal file
41
Resources/Prototypes/_Sunrise/AssaultOps/startinggear.yml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
- type: startingGear
|
||||
id: AssaultOperativeGear
|
||||
equipment:
|
||||
jumpsuit: ClothingUniformJumpsuitTactical
|
||||
back: ClothingBackpackAssaultOpsFill
|
||||
mask: ClothingMaskGaiter
|
||||
eyes: ClothingEyesGlassesSunglasses
|
||||
shoes: ClothingShoesColorBlack
|
||||
id: AssaultOpsPDA
|
||||
# SUNRISE-TODO: Лоадаут для ДОшников
|
||||
#innerclothingskirt: ClothingUniformJumpsuitTactical
|
||||
|
||||
- type: startingGear
|
||||
id: LoneAssaultOperativeGear
|
||||
equipment:
|
||||
jumpsuit: ClothingUniformJumpsuitTactical
|
||||
back: ClothingBackpackAssaultOpsFill
|
||||
mask: ClothingMaskGaiter
|
||||
eyes: ClothingEyesGlassesSunglasses
|
||||
shoes: ClothingShoesColorBlack
|
||||
id: AssaultOpsPDA
|
||||
pocket2: UplinkRadioAssaultOperatives40TC
|
||||
# SUNRISE-TODO: Лоадаут для ДОшников
|
||||
#innerclothingskirt: ClothingUniformJumpsuitTactical
|
||||
|
||||
- type: startingGear
|
||||
id: AssaultCommanderGear
|
||||
equipment:
|
||||
jumpsuit: ClothingUniformJumpsuitTactical
|
||||
back: ClothingBackpackAssaultOpsFill
|
||||
mask: ClothingMaskGaiter
|
||||
eyes: ClothingEyesGlassesSunglasses
|
||||
shoes: ClothingShoesColorBlack
|
||||
id: AssaultOpsPDA
|
||||
pocket1: RemoteAssaultOpsShuttleController
|
||||
pocket2: UplinkRadioAssaultOperatives0TC
|
||||
# SUNRISE-TODO: Лоадаут для ДОшников
|
||||
#innerclothingskirt: ClothingUniformJumpsuitTactical
|
||||
inhand:
|
||||
- FlippoLighter
|
||||
- DocumentAssaultOpsGoal
|
||||
2
Resources/Prototypes/_Sunrise/AssaultOps/tags.yml
Normal file
2
Resources/Prototypes/_Sunrise/AssaultOps/tags.yml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
- type: Tag
|
||||
id: AssaultOpsUplink
|
||||
70
Resources/Prototypes/_Sunrise/AssaultOps/terminal.yml
Normal file
70
Resources/Prototypes/_Sunrise/AssaultOps/terminal.yml
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
- type: entity
|
||||
parent: BaseStructureComputer
|
||||
id: ComputerIcarus
|
||||
name: icarus terminal
|
||||
description: An ominous terminal with some ports and keypads, the screen is scrolling with illegible nonsense. It has a strange marking on the side, a red ring with a gold circle within.
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Sunrise/AssaultOperatives/goldeneye_terminal.rsi
|
||||
state: terminal
|
||||
- type: Computer
|
||||
- type: ApcPowerReceiver
|
||||
powerLoad: 200
|
||||
- type: LitOnPowered
|
||||
- type: ExtensionCableReceiver
|
||||
- type: ActivatableUIRequiresPower
|
||||
- type: LightningTarget
|
||||
priority: 1
|
||||
- type: RequireProjectileTarget
|
||||
- type: Electrified
|
||||
enabled: false
|
||||
usesApcPower: true
|
||||
- type: PointLight
|
||||
radius: 1.5
|
||||
energy: 1.6
|
||||
enabled: false
|
||||
mask: /Textures/Effects/LightMasks/cone.png
|
||||
autoRot: true
|
||||
color: "#b5a13c"
|
||||
offset: "0, 0.4" # shine from the top, not bottom of the computer
|
||||
castShadows: false
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
firstKeySlot:
|
||||
ejectSound: /Audio/Machines/id_swipe.ogg
|
||||
insertSound: /Audio/Machines/Nuke/general_beep.ogg
|
||||
ejectOnBreak: true
|
||||
swap: false
|
||||
whitelist:
|
||||
components:
|
||||
- IcarusKey
|
||||
secondKeySlot:
|
||||
ejectSound: /Audio/Machines/id_swipe.ogg
|
||||
insertSound: /Audio/Machines/Nuke/general_beep.ogg
|
||||
ejectOnBreak: true
|
||||
swap: false
|
||||
whitelist:
|
||||
components:
|
||||
- IcarusKey
|
||||
thirdKeySlot:
|
||||
ejectSound: /Audio/Machines/id_swipe.ogg
|
||||
insertSound: /Audio/Machines/Nuke/general_beep.ogg
|
||||
ejectOnBreak: true
|
||||
swap: false
|
||||
whitelist:
|
||||
components:
|
||||
- IcarusKey
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
firstKeySlot: !type:ContainerSlot
|
||||
secondKeySlot: !type:ContainerSlot
|
||||
thirdKeySlot: !type:ContainerSlot
|
||||
- type: IcarusTerminal
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
enum.IcarusTerminalUiKey.Key:
|
||||
type: IcarusTerminalBoundUserInterface
|
||||
- type: ActivatableUI
|
||||
key: enum.IcarusTerminalUiKey.Key
|
||||
24
Resources/Prototypes/_Sunrise/AssaultOps/uplink.yml
Normal file
24
Resources/Prototypes/_Sunrise/AssaultOps/uplink.yml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
- type: entity
|
||||
parent: BaseUplinkRadio
|
||||
id: UplinkRadioAssaultOperatives0TC
|
||||
suffix: AssaultOps
|
||||
components:
|
||||
- type: Store
|
||||
balance:
|
||||
Telecrystal: 0
|
||||
- type: Tag
|
||||
tags:
|
||||
- AssaultOpsUplink
|
||||
|
||||
|
||||
- type: entity
|
||||
parent: BaseUplinkRadio
|
||||
id: UplinkRadioAssaultOperatives40TC
|
||||
suffix: AssaultOps
|
||||
components:
|
||||
- type: Store
|
||||
balance:
|
||||
Telecrystal: 40
|
||||
- type: Tag
|
||||
tags:
|
||||
- AssaultOpsUplink
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
- type: entity
|
||||
id: RemoteShuttleControllerBase
|
||||
parent: BaseItem
|
||||
abstract: true
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Sunrise/Entities/Objects/Devices/remote_controller.rsi
|
||||
- type: Item
|
||||
size: Normal
|
||||
- type: ApcPowerReceiver
|
||||
needsPower: false
|
||||
powerLoad: 0
|
||||
- type: ShuttleConsole
|
||||
portable: true
|
||||
- type: RadarConsole
|
||||
maxRange: 256
|
||||
- type: ActivatableUI
|
||||
key: enum.ShuttleConsoleUiKey.Key
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
enum.ShuttleConsoleUiKey.Key:
|
||||
type: ShuttleConsoleBoundUserInterface
|
||||
|
||||
- type: entity
|
||||
id: RemoteAssaultOpsShuttleController
|
||||
parent: RemoteShuttleControllerBase
|
||||
name: Удаленный контроллер управления шаттлом (Диверсионный отряд)
|
||||
description: Теперь вам не нужен пилот, ведь пилот это вы!
|
||||
components:
|
||||
- type: Sprite
|
||||
state: syndicate
|
||||
- type: DroneConsole
|
||||
portable: true
|
||||
components:
|
||||
- type: AssaultOpsShuttle
|
||||
|
||||
- type: entity
|
||||
id: RemoteNukeOpShuttleController
|
||||
parent: RemoteShuttleControllerBase
|
||||
name: Удаленный контроллер управления шаттлом (Ядерные оперативки)
|
||||
description: Теперь вам не нужен пилот, ведь пилот это вы!
|
||||
components:
|
||||
- type: Sprite
|
||||
state: syndicate
|
||||
- type: DroneConsole
|
||||
portable: true
|
||||
components:
|
||||
- type: NukeOpsShuttle
|
||||
|
||||
- type: entity
|
||||
id: RemoteSecurityShuttleController
|
||||
parent: RemoteShuttleControllerBase
|
||||
name: Удаленный контроллер управления шаттлом (Шаттл службы безопастности)
|
||||
description: Теперь вам не нужен пилот, ведь пилот это вы!
|
||||
components:
|
||||
- type: Sprite
|
||||
state: nanotrasen
|
||||
- type: DroneConsole
|
||||
portable: true
|
||||
components:
|
||||
- type: SecurityShuttle
|
||||
|
||||
- type: entity
|
||||
id: RemoteCargoShuttleController
|
||||
parent: RemoteShuttleControllerBase
|
||||
name: Удаленный контроллер управления шаттлом (Шаттл карго)
|
||||
description: Теперь вам не нужен пилот, ведь пилот это вы!
|
||||
components:
|
||||
- type: Sprite
|
||||
state: nanotrasen
|
||||
- type: DroneConsole
|
||||
portable: true
|
||||
components:
|
||||
- type: CargoShuttle
|
||||
|
||||
- type: entity
|
||||
id: RemotePrisonShuttleController
|
||||
parent: RemoteShuttleControllerBase
|
||||
name: Удаленный контроллер управления шаттлом (Тюремный шаттл)
|
||||
description: Теперь вам не нужен пилот, ведь пилот это вы!
|
||||
components:
|
||||
- type: Sprite
|
||||
state: nanotrasen
|
||||
- type: DroneConsole
|
||||
portable: true
|
||||
components:
|
||||
- type: PrisonShuttle
|
||||
|
|
@ -80,6 +80,14 @@
|
|||
- type: AccessReader
|
||||
access: [["SyndicateAgent"]]
|
||||
|
||||
- type: entity
|
||||
parent: AirlockMaint
|
||||
id: AirlockMaintSyndicateLocked
|
||||
suffix: Syndicate, Locked
|
||||
components:
|
||||
- type: AccessReader
|
||||
access: [["SyndicateAgent"]]
|
||||
|
||||
#DoubleAirlocks
|
||||
|
||||
- type: entity
|
||||
|
|
|
|||
|
|
@ -336,3 +336,10 @@
|
|||
- Bra
|
||||
- Pants
|
||||
- Socks
|
||||
|
||||
- type: roleLoadout
|
||||
id: RoleSurvivalAssaultOps
|
||||
groups:
|
||||
- SurvivalSyndicate
|
||||
- GroupSpeciesBreathTool
|
||||
- GroupPocketTankDouble
|
||||
|
|
|
|||
77
Resources/Prototypes/_Sunrise/Structures/interrogator.yml
Normal file
77
Resources/Prototypes/_Sunrise/Structures/interrogator.yml
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
- type: entity
|
||||
id: Interrogator
|
||||
parent: BaseMachinePowered
|
||||
name: interrogator
|
||||
description: apchy
|
||||
components:
|
||||
- type: Interrogator
|
||||
- type: Sprite
|
||||
sprite: _Sunrise/AssaultOperatives/interrogator.rsi
|
||||
snapCardinals: true
|
||||
layers:
|
||||
- state: base
|
||||
map: [ "enum.InterrogatorVisualLayers.Base" ]
|
||||
- state: extraction-on
|
||||
map: [ "enum.InterrogatorVisualLayers.Extract" ]
|
||||
visible: false
|
||||
- type: PointLight
|
||||
color: "#3a807f"
|
||||
radius: 2
|
||||
energy: 10
|
||||
enabled: false
|
||||
- type: Physics
|
||||
bodyType: Static
|
||||
- type: Construction
|
||||
graph: Machine
|
||||
node: machine
|
||||
containers:
|
||||
- machine_board
|
||||
- machine_parts
|
||||
- body_container
|
||||
- type: EmptyOnMachineDeconstruct
|
||||
containers:
|
||||
- body_container
|
||||
- type: Damageable
|
||||
damageContainer: StructuralInorganic
|
||||
damageModifierSet: StrongMetallic
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 100
|
||||
behaviors:
|
||||
- !type:PlaySoundBehavior
|
||||
sound:
|
||||
collection: MetalGlassBreak
|
||||
- !type:ChangeConstructionNodeBehavior
|
||||
node: machineFrame
|
||||
- !type:DoActsBehavior
|
||||
acts: ["Destruction"]
|
||||
- type: Machine
|
||||
board: InterrogatorMachineCircuitboard
|
||||
- type: WiresPanel
|
||||
- type: ApcPowerReceiver
|
||||
powerLoad: 200
|
||||
- type: Appearance
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
body_container: !type:ContainerSlot
|
||||
machine_board: !type:Container
|
||||
machine_parts: !type:Container
|
||||
|
||||
|
||||
- type: entity
|
||||
id: InterrogatorMachineCircuitboard
|
||||
parent: BaseMachineCircuitboard
|
||||
name: interrogator machine board
|
||||
description: A machine printed circuit board for a interrogator.
|
||||
components:
|
||||
- type: Sprite
|
||||
state: medical
|
||||
- type: MachineBoard
|
||||
prototype: Interrogator
|
||||
stackRequirements:
|
||||
MatterBin: 2
|
||||
Manipulator: 2
|
||||
Glass: 1
|
||||
Cable: 1
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
- type: entity
|
||||
id: ThrusterSyndicate
|
||||
parent: [ BaseThruster, ConstructibleMachine ]
|
||||
components:
|
||||
- type: Thruster
|
||||
baseThrust: 150
|
||||
- type: Machine
|
||||
board: ThrusterSyndicateMachineCircuitboard
|
||||
- type: Sprite
|
||||
sprite: _Sunrise/Structures/Shuttles/syndicate_thruster.rsi
|
||||
layers:
|
||||
- state: base
|
||||
map: ["enum.ThrusterVisualLayers.Base"]
|
||||
- state: red_thrust
|
||||
map: ["enum.ThrusterVisualLayers.ThrustOn"]
|
||||
shader: unshaded
|
||||
visible: false
|
||||
- state: red_thrust_burn_unshaded
|
||||
map: ["enum.ThrusterVisualLayers.ThrustingUnshaded"]
|
||||
shader: unshaded
|
||||
visible: false
|
||||
offset: 0, 1
|
||||
|
||||
- type: entity
|
||||
id: ThrusterSyndicateMachineCircuitboard
|
||||
parent: BaseMachineCircuitboard
|
||||
name: thruster syndie machine board
|
||||
components:
|
||||
- type: MachineBoard
|
||||
prototype: ThrusterSyndicate
|
||||
stackRequirements:
|
||||
Capacitor: 4
|
||||
Steel: 5
|
||||
|
|
@ -31,9 +31,6 @@
|
|||
- type: Tag
|
||||
id: BorgModuleERT
|
||||
|
||||
- type: Tag
|
||||
id: AssaultOpsUplink
|
||||
|
||||
- type: Tag
|
||||
id: SyndieAgentUplink
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 405 B |
Binary file not shown.
|
After Width: | Height: | Size: 332 B |
Binary file not shown.
|
After Width: | Height: | Size: 348 B |
Binary file not shown.
|
After Width: | Height: | Size: 353 B |
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from Skyrat-tg at commit https://github.com/Skyrat-SS13/Skyrat-tg/commit/fe1f0bd9bb30996d09f17dacbd8b72dee7a4dfd7",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "equipped-MASK",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "up-equipped-MASK",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 395 B |
Binary file not shown.
|
After Width: | Height: | Size: 795 B |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue