diff --git a/Content.Client/_White/Guardian/GuardianSelectorBUI.cs b/Content.Client/_White/Guardian/GuardianSelectorBUI.cs new file mode 100644 index 0000000000..6b4799e56d --- /dev/null +++ b/Content.Client/_White/Guardian/GuardianSelectorBUI.cs @@ -0,0 +1,114 @@ +using Content.Client._White.UserInterface.Radial; +using Content.Shared._White.Guardian; + +namespace Content.Client._White.Guardian; + +public sealed class GuardianSelectorBUI : BoundUserInterface +{ + private readonly Dictionary _names = new() + { + { GuardianSelector.Assasin, Loc.GetString("guardian-assasin-name")}, + { GuardianSelector.Charger, Loc.GetString("guardian-charger-name")}, + { GuardianSelector.Lighting, Loc.GetString("guardian-lighting-name")}, + { GuardianSelector.Standart, Loc.GetString("guardian-standart-name")}, + }; + + private readonly Dictionary _icons = new() + { + { GuardianSelector.Assasin, "/Textures/White/Interface/guardianselector.rsi/assasin.png" }, + { GuardianSelector.Charger, "/Textures/White/Interface/guardianselector.rsi/charger.png" }, + { GuardianSelector.Lighting, "/Textures/White/Interface/guardianselector.rsi/lighting.png" }, + { GuardianSelector.Standart, "/Textures/White/Interface/guardianselector.rsi/standart.png" }, + }; + + private readonly Dictionary _guardianSelectors = new() + { + { "Assasin", GuardianSelector.Assasin }, + { "Charger", GuardianSelector.Charger }, + { "Lighting", GuardianSelector.Lighting }, + { "Standart", GuardianSelector.Standart }, + }; + + private RadialContainer? _radialContainer; + private bool _updated; + + public GuardianSelectorBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + if (_radialContainer != null) + UIReset(); + + _radialContainer = new RadialContainer(); + + _radialContainer.Closed += Close; + + if (State != null) + UpdateState(State); + } + + private void UIReset() + { + _radialContainer?.Close(); + _radialContainer = null; + _updated = false; + } + + private void PopulateRadial(IReadOnlyCollection ids, NetEntity target) + { + foreach (var id in ids) + { + if (_radialContainer == null) + continue; + + if(!_guardianSelectors.TryGetValue(id, out var guardianSelector)) + return; + + if(!_names.TryGetValue(guardianSelector, out var name) || !_icons.TryGetValue(guardianSelector, out var icon)) + return; + + var button = _radialContainer.AddButton(name, icon); + button.Controller.OnPressed += _ => + { + Select(guardianSelector, target); + }; + } + } + + private void Select(GuardianSelector type, NetEntity target) + { + SendMessage(new GuardianSelectorSelectedBuiMessage(type, target)); + UIReset(); + Close(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + _radialContainer?.Close(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (_updated) + return; + + if (state is GuardianSelectorBUIState newState) + { + PopulateRadial(newState.Ids, newState.Target); + } + + if (_radialContainer == null) + return; + + _radialContainer?.OpenAttachedLocalPlayer(); + _updated = true; + } +} + diff --git a/Content.Server/Guardian/GuardianComponent.cs b/Content.Server/Guardian/GuardianComponent.cs index a54d033756..f9958f08f7 100644 --- a/Content.Server/Guardian/GuardianComponent.cs +++ b/Content.Server/Guardian/GuardianComponent.cs @@ -1,3 +1,8 @@ +using Content.Shared._White.Guardian; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + namespace Content.Server.Guardian { /// @@ -15,20 +20,63 @@ namespace Content.Server.Guardian /// /// Percentage of damage reflected from the guardian to the host /// - [DataField] + [DataField("damageShare")] public float DamageShare { get; set; } = 0.65f; /// /// Maximum distance the guardian can travel before it's forced to recall, use YAML to set /// - [DataField] + [DataField("distance")] public float DistanceAllowed { get; set; } = 5f; + /// + /// Maximum default distance the guardian can travel before it's forced to recall, use YAML to set + /// + [DataField("distanceDefault")] + public float DistanceAllowedDefault { get; set; } = 10f; + + [DataField] + public float DistancePowerAssasin { get; set; } = 25f; + /// /// If the guardian is currently manifested /// [DataField] public bool GuardianLoose; + [DataField] + public GuardianSelector GuardianType = GuardianSelector.Standart; + + [DataField("powerToggleAction", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string PowerToggleAction = "ActionGuardianPowerToggle"; + + [DataField] + public EntityUid? PowerToggleActionEntity; + + [ViewVariables(VVAccess.ReadWrite), DataField] + public bool IsInPowerMode; + + [DataField("chargerPowerAction", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string ChargerPowerAction = "ActionChargerPower"; + + [DataField] + public EntityUid? ChargerPowerActionEntity; + + [ViewVariables(VVAccess.ReadWrite), DataField] + public bool IsCharged; + + [ViewVariables(VVAccess.ReadWrite), DataField("assasinDamageModifier")] + public float AssasinDamageModifier = 3F; + + [ViewVariables(VVAccess.ReadWrite), DataField] + public int LightingCount = 1; + + [ViewVariables(VVAccess.ReadWrite), DataField] + public SoundSpecifier? ChargerSound = new SoundPathSpecifier("/Audio/White/Guardian/charger.ogg"); + + [DataField] + public EntProtoId Action = "ActionToggleGuardian"; + + [DataField] public EntityUid? ActionEntity; } } diff --git a/Content.Server/Guardian/GuardianCreatorComponent.cs b/Content.Server/Guardian/GuardianCreatorComponent.cs deleted file mode 100644 index dfe801f290..0000000000 --- a/Content.Server/Guardian/GuardianCreatorComponent.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Server.Guardian -{ - /// - /// Creates a GuardianComponent attached to the user's GuardianHost. - /// - [RegisterComponent] - public sealed partial class GuardianCreatorComponent : Component - { - /// - /// Counts as spent upon exhausting the injection - /// - /// - /// We don't mark as deleted as examine depends on this. - /// - public bool Used = false; - - /// - /// The prototype of the guardian entity which will be created - /// - [DataField("guardianProto", customTypeSerializer:typeof(PrototypeIdSerializer), required: true)] - public string GuardianProto { get; set; } = default!; - - /// - /// How long it takes to inject someone. - /// - [DataField("delay")] - public float InjectionDelay = 5f; - } -} diff --git a/Content.Server/Guardian/GuardianSystem.cs b/Content.Server/Guardian/GuardianSystem.cs index 203882ed9e..1622a45576 100644 --- a/Content.Server/Guardian/GuardianSystem.cs +++ b/Content.Server/Guardian/GuardianSystem.cs @@ -1,7 +1,9 @@ +using Content.Server._White.IncorporealSystem; using Content.Server.Body.Systems; +using Content.Server.Lightning; using Content.Server.Popups; +using Content.Shared._White.Guardian; using Content.Shared.Actions; -using Content.Shared.Audio; using Content.Shared.Damage; using Content.Shared.DoAfter; using Content.Shared.Examine; @@ -12,7 +14,7 @@ using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Mobs; using Content.Shared.Popups; -using Robust.Server.GameObjects; +using Content.Shared.Weapons.Melee; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; @@ -35,6 +37,9 @@ namespace Content.Server.Guardian [Dependency] private readonly BodySystem _bodySystem = default!; [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; + [Dependency] private readonly LightningSystem _lightningSystem = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; public override void Initialize() { @@ -58,8 +63,93 @@ namespace Content.Server.Guardian SubscribeLocalEvent(OnPerformAction); SubscribeLocalEvent(OnGuardianAttackAttempt); + + // PARSEC EDIT START + SubscribeLocalEvent(OnGuardianSelected); + SubscribeLocalEvent(OnPerformGuardianPowerAction); + SubscribeLocalEvent(OnPerformChargerPowerAction); + SubscribeLocalEvent(OnPerformGuardianAction); } + // PARSEC EDIT START + + private void OnGuardianSelected(EntityUid uid, + GuardianCreatorComponent component, + GuardianSelectorSelectedBuiMessage args) + { + var target = GetEntity(args.Target); + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.Actor, component.InjectionDelay, new GuardianCreatorDoAfterEvent{SelectedType = args.GuardianType}, uid, target: target, used: uid){BreakOnMove = true}); + } + + private void OnPerformGuardianPowerAction(EntityUid uid, + GuardianComponent component, + ToggleGuardianPowerActionEvent args) + { + if(args.Handled) + return; + + args.Handled = true; + ToggleGuardianPower(uid, component, !component.IsInPowerMode); + } + + private void ToggleGuardianPower(EntityUid uid, GuardianComponent component, bool toggleValue) + { + if (component.IsInPowerMode == toggleValue) + return; + + if(component.PowerToggleActionEntity == null) + return; + + if (component is { IsInPowerMode: false, GuardianLoose: true }) + { + _popupSystem.PopupEntity("Вы должны находится в теле, чтобы активировать способность!", uid, uid, PopupType.MediumCaution); + return; + } + + component.IsInPowerMode = toggleValue; + + _actionSystem.SetToggled(component.PowerToggleActionEntity, component.IsInPowerMode); + SetupPower(uid, component, component.GuardianType); + } + + private void SetupPower(EntityUid uid, GuardianComponent component, GuardianSelector type) + { + if (type == GuardianSelector.Assasin) + SetupAssasin(uid, component); + } + + private void SetupAssasin(EntityUid uid, GuardianComponent component) + { + if (HasComp(uid)) + { + RemComp(uid); + _actionSystem.SetToggled(component.PowerToggleActionEntity, !component.IsInPowerMode); + component.IsInPowerMode = false; + component.DistanceAllowed = component.DistanceAllowedDefault; + return; + } + + var incorporealComp = EnsureComp(uid); + incorporealComp.Effect = false; + component.DistanceAllowed = component.DistancePowerAssasin; + } + + private void OnPerformChargerPowerAction(EntityUid uid, GuardianComponent component, ChargerPowerActionEvent args) + { + if(args.Handled) + return; + + args.Handled = true; + + if(component.IsCharged) + return; + + component.IsCharged = true; + _audio.PlayPvs(component.ChargerSound, uid); + } + + //Parsec edit end + private void OnGuardianShutdown(EntityUid uid, GuardianComponent component, ComponentShutdown args) { var host = component.Host; @@ -70,6 +160,10 @@ namespace Content.Server.Guardian _container.Remove(uid, hostComponent.GuardianContainer); hostComponent.HostedGuardian = null; + if(component.PowerToggleActionEntity != null) + _actionSystem.RemoveAction(uid, component.PowerToggleActionEntity); // Parsec + if(component.ChargerPowerActionEntity != null) + _actionSystem.RemoveAction(uid, component.ChargerPowerActionEntity); // parsec Dirty(host.Value, hostComponent); QueueDel(hostComponent.ActionEntity); hostComponent.ActionEntity = null; @@ -86,6 +180,24 @@ namespace Content.Server.Guardian args.Handled = true; } + private void OnPerformGuardianAction(EntityUid uid, GuardianComponent component, GuardianToggleActionEvent args) + { + if (args.Handled) + return; + + if(component.Host == null) + return; + + var host = (EntityUid) component.Host; + + if(!TryComp(host, out var guardianHostComponent)) + return; + + ToggleGuardian(host, guardianHostComponent); + + args.Handled = true; + } + private void OnGuardianPlayerDetached(EntityUid uid, GuardianComponent component, PlayerDetachedEvent args) { var host = component.Host; @@ -109,6 +221,19 @@ namespace Content.Server.Guardian } _popupSystem.PopupEntity(Loc.GetString("guardian-available"), host.Value, host.Value); + + _actionSystem.AddAction(uid, ref component.ActionEntity, component.Action); + + if (component.GuardianType is GuardianSelector.Standart or GuardianSelector.Lighting) + return; + + if (component.GuardianType == GuardianSelector.Charger) + { + _actionSystem.AddAction(uid, ref component.ChargerPowerActionEntity, component.ChargerPowerAction); + return; + } + + _actionSystem.AddAction(uid, ref component.PowerToggleActionEntity, component.PowerToggleAction); } private void OnHostInit(EntityUid uid, GuardianHostComponent component, ComponentInit args) @@ -133,12 +258,55 @@ namespace Content.Server.Guardian private void OnGuardianAttackAttempt(EntityUid uid, GuardianComponent component, AttackAttemptEvent args) { - if (args.Cancelled || args.Target != component.Host) + if (args.Cancelled) return; - // why is this server side code? This should be in shared - _popupSystem.PopupCursor(Loc.GetString("guardian-attack-host"), uid, PopupType.LargeCaution); - args.Cancel(); + if (args.Target == null) + return; + + if (args.Target == component.Host) + { + _popupSystem.PopupCursor(Loc.GetString("guardian-attack-host"), uid, PopupType.LargeCaution); + args.Cancel(); + return; + } + + var target = (EntityUid) args.Target; + + if (component.GuardianType == GuardianSelector.Lighting) + { + for (var i = 0; i < component.LightingCount; i++) + { + _lightningSystem.ShootLightning(uid, target); + } + } + + if (component.GuardianType == GuardianSelector.Assasin) + { + if (!component.IsInPowerMode) + return; + + if (!TryComp(args.Weapon, out var meleeWeaponComponent)) + return; + + _damageableSystem.TryChangeDamage(args.Target, + meleeWeaponComponent.Damage * component.AssasinDamageModifier, + true); + SetupAssasin(uid, component); + } + + if (component.GuardianType == GuardianSelector.Charger) + { + if(!component.IsCharged) + return; + + foreach (var hand in _handsSystem.EnumerateHands(target)) + { + _handsSystem.TryDrop(target, hand); + } + + component.IsCharged = false; + } } public void ToggleGuardian(EntityUid user, GuardianHostComponent hostComponent) @@ -172,6 +340,7 @@ namespace Content.Server.Guardian args.Handled = true; UseCreator(args.User, args.Target.Value, uid, component); } + private void UseCreator(EntityUid user, EntityUid target, EntityUid injector, GuardianCreatorComponent component) { if (component.Used) @@ -194,10 +363,11 @@ namespace Content.Server.Guardian return; } - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, component.InjectionDelay, new GuardianCreatorDoAfterEvent(), injector, target: target, used: injector){BreakOnMove = true}); + _ui.SetUiState(injector, GuardianSelectorUiKey.Key, new GuardianSelectorBUIState(component.GuardiansAvaliable, GetNetEntity(target))); + _ui.OpenUi(injector, GuardianSelectorUiKey.Key, user); } - private void OnDoAfter(EntityUid uid, GuardianCreatorComponent component, DoAfterEvent args) + private void OnDoAfter(EntityUid uid, GuardianCreatorComponent component, GuardianCreatorDoAfterEvent args) { if (args.Handled || args.Args.Target == null) return; @@ -207,14 +377,16 @@ namespace Content.Server.Guardian var hostXform = Transform(args.Args.Target.Value); var host = EnsureComp(args.Args.Target.Value); + var guardianProto = component.GuardianSelectorToProto[args.SelectedType]; // Parsec // Use map position so it's not inadvertantly parented to the host + if it's in a container it spawns outside I guess. - var guardian = Spawn(component.GuardianProto, _transform.GetMapCoordinates(args.Args.Target.Value, xform: hostXform)); + var guardian = Spawn(guardianProto, _transform.GetMapCoordinates(args.Args.Target.Value, xform: hostXform)); // Parsec edit _container.Insert(guardian, host.GuardianContainer); host.HostedGuardian = guardian; if (TryComp(guardian, out var guardianComp)) { + guardianComp.GuardianType = args.SelectedType; guardianComp.Host = args.Args.Target.Value; _audio.PlayPvs("/Audio/Effects/guardian_inject.ogg", args.Args.Target.Value); _popupSystem.PopupEntity(Loc.GetString("guardian-created"), args.Args.Target.Value, args.Args.Target.Value); @@ -273,8 +445,8 @@ namespace Content.Server.Guardian /// private void OnCreatorExamine(EntityUid uid, GuardianCreatorComponent component, ExaminedEvent args) { - if (component.Used) - args.PushMarkup(Loc.GetString("guardian-activator-empty-examine")); + if (component.Used) + args.PushMarkup(Loc.GetString("guardian-activator-empty-examine")); } /// @@ -344,7 +516,7 @@ namespace Content.Server.Guardian guardianComponent.GuardianLoose = true; } - private void RetractGuardian(EntityUid host,GuardianHostComponent hostComponent, EntityUid guardian, GuardianComponent guardianComponent) + private void RetractGuardian(EntityUid host, GuardianHostComponent hostComponent, EntityUid guardian, GuardianComponent guardianComponent) { if (!guardianComponent.GuardianLoose) { @@ -355,6 +527,20 @@ namespace Content.Server.Guardian _container.Insert(guardian, hostComponent.GuardianContainer); DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardian)); _popupSystem.PopupEntity(Loc.GetString("guardian-entity-recall"), host); + if (guardianComponent.IsInPowerMode) + { + if (guardianComponent.GuardianType == GuardianSelector.Assasin) + { + SetupPower(guardian, guardianComponent, guardianComponent.GuardianType); + return; + } + + guardianComponent.IsInPowerMode = false; + if (guardianComponent.PowerToggleActionEntity != null) + { + _actionSystem.SetToggled(guardianComponent.PowerToggleActionEntity, guardianComponent.IsInPowerMode); + } + } guardianComponent.GuardianLoose = false; } } diff --git a/Content.Server/_White/Commands/PoshelnahuiCommand.cs b/Content.Server/_White/Commands/PoshelnahuiCommand.cs index 722f3123b1..00152e2387 100644 --- a/Content.Server/_White/Commands/PoshelnahuiCommand.cs +++ b/Content.Server/_White/Commands/PoshelnahuiCommand.cs @@ -13,7 +13,7 @@ public sealed class PoshelnahuiCommand : IConsoleCommand public string Command => "poshelnahui"; public string Description => "Close client game lol"; public string Help => "poshelnahui "; - private readonly List _vip = ["Valtos", "SamsungS", "Dosharus", "BIG_Zi_348"]; + private readonly List _vip = ["Valtos", "SamsungS", "Dosharus", "CaypenNow"]; public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 1 || string.IsNullOrEmpty(args[0])) diff --git a/Content.Server/_White/Cult/Runes/Systems/CultSystem.ConstructsAbilities.cs b/Content.Server/_White/Cult/Runes/Systems/CultSystem.ConstructsAbilities.cs index 93eff0bebc..ce78d38770 100644 --- a/Content.Server/_White/Cult/Runes/Systems/CultSystem.ConstructsAbilities.cs +++ b/Content.Server/_White/Cult/Runes/Systems/CultSystem.ConstructsAbilities.cs @@ -157,6 +157,8 @@ public partial class CultSystem _statusEffectsSystem.TryAddStatusEffect(ev.Performer, ev.StatusEffectId, TimeSpan.FromSeconds(ev.Duration), false); + Spawn("EffectEmpPulse", Transform(ev.Performer).Coordinates); + ev.Handled = true; } diff --git a/Content.Server/_White/IncorporealSystem/IncorporealComponent.cs b/Content.Server/_White/IncorporealSystem/IncorporealComponent.cs index aa2eba7a8e..98e52aad91 100644 --- a/Content.Server/_White/IncorporealSystem/IncorporealComponent.cs +++ b/Content.Server/_White/IncorporealSystem/IncorporealComponent.cs @@ -6,10 +6,11 @@ namespace Content.Server._White.IncorporealSystem; public sealed partial class IncorporealComponent : Component { [DataField] public float MovementSpeedBuff = 1.5f; + [DataField] public bool Effect = true; [DataField] public int CollisionMask = (int) CollisionGroup.GhostImpassable; [DataField] public int CollisionLayer = 0; - + public int StoredMask; public int StoredLayer; -} \ No newline at end of file +} diff --git a/Content.Server/_White/IncorporealSystem/IncorporealSystem.cs b/Content.Server/_White/IncorporealSystem/IncorporealSystem.cs index a3b658e2ca..1ca96ab675 100644 --- a/Content.Server/_White/IncorporealSystem/IncorporealSystem.cs +++ b/Content.Server/_White/IncorporealSystem/IncorporealSystem.cs @@ -53,7 +53,6 @@ public sealed class IncorporealSystem : EntitySystem _visibilitySystem.RefreshVisibility(uid); } - Spawn("EffectEmpPulse", Transform(uid).Coordinates); EnsureComp(uid); _stealth.SetVisibility(uid, -1); if (TryComp(uid, out PullableComponent? pullable)) @@ -79,7 +78,12 @@ public sealed class IncorporealSystem : EntitySystem } component.MovementSpeedBuff = 1; - Spawn("EffectEmpPulse", _transform.GetMapCoordinates(uid)); + + if (component.Effect) + { + Spawn("EffectEmpPulse", _transform.GetMapCoordinates(uid)); + } + _stealth.SetVisibility(uid, 1); RemComp(uid); _movement.RefreshMovementSpeedModifiers(uid); diff --git a/Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs b/Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs index 9119c482a1..5e95d43c0e 100644 --- a/Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs +++ b/Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs @@ -404,6 +404,8 @@ public sealed class WizardSpellsSystem : EntitySystem _statusEffectsSystem.TryAddStatusEffect(msg.Performer, "Incorporeal", TimeSpan.FromSeconds(10), false); + Spawn("EffectEmpPulse", Transform(msg.Performer).Coordinates); + Cast(msg); } diff --git a/Content.Shared/Guardian/GuardianCreatorComponent.cs b/Content.Shared/Guardian/GuardianCreatorComponent.cs new file mode 100644 index 0000000000..3cb04ba1c8 --- /dev/null +++ b/Content.Shared/Guardian/GuardianCreatorComponent.cs @@ -0,0 +1,49 @@ +using Content.Shared._White.Guardian; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Guardian +{ + /// + /// Creates a GuardianComponent attached to the user's GuardianHost. + /// + [RegisterComponent, NetworkedComponent] + public sealed partial class GuardianCreatorComponent : Component + { + /// + /// Counts as spent upon exhausting the injection + /// + /// + /// We don't mark as deleted as examine depends on this. + /// + [DataField] + public bool Used = false; + + /// + /// The prototype of the guardian entity which will be created + /// + [DataField("guardianProto", + customTypeSerializer: typeof(PrototypeIdSerializer), + required: true)] + public string GuardianProto; + + /// + /// How long it takes to inject someone. + /// + [DataField("delay")] + public float InjectionDelay = 5f; + + [DataField("guardiansAvaliable")] + public IReadOnlyCollection GuardiansAvaliable = ArraySegment.Empty; + + [DataField] + public Dictionary GuardianSelectorToProto = new() + { + { GuardianSelector.Assasin, "MobHoloparasiteGuardianAssasin" }, + { GuardianSelector.Standart, "MobHoloparasiteGuardianStandart" }, + { GuardianSelector.Charger, "MobHoloparasiteGuardianCharger" }, + { GuardianSelector.Lighting, "MobHoloparasiteGuardianLighting" } + }; + } +} diff --git a/Content.Shared/Guardian/GuardianCreatorDoAfterEvent.cs b/Content.Shared/Guardian/GuardianCreatorDoAfterEvent.cs index 8addfa2ed1..e9d85faddd 100644 --- a/Content.Shared/Guardian/GuardianCreatorDoAfterEvent.cs +++ b/Content.Shared/Guardian/GuardianCreatorDoAfterEvent.cs @@ -1,4 +1,5 @@ -using Content.Shared.DoAfter; +using Content.Shared._White.Guardian; +using Content.Shared.DoAfter; using Robust.Shared.Serialization; namespace Content.Shared.Guardian; @@ -6,4 +7,5 @@ namespace Content.Shared.Guardian; [Serializable, NetSerializable] public sealed partial class GuardianCreatorDoAfterEvent : SimpleDoAfterEvent { + public GuardianSelector SelectedType; // Parsec Edit } diff --git a/Content.Shared/_White/Guardian/GuardianEvents.cs b/Content.Shared/_White/Guardian/GuardianEvents.cs new file mode 100644 index 0000000000..a4a32809a6 --- /dev/null +++ b/Content.Shared/_White/Guardian/GuardianEvents.cs @@ -0,0 +1,53 @@ +using Content.Shared.Actions; +using Robust.Shared.Serialization; + +namespace Content.Shared._White.Guardian; + +public enum GuardianSelector : byte +{ + Assasin, + Standart, + Charger, + Lighting +} + +[Serializable, NetSerializable] +public enum GuardianSelectorUiKey : byte +{ + Key +} + +[Serializable, NetSerializable] +public sealed class GuardianSelectorBUIState : BoundUserInterfaceState +{ + public IReadOnlyCollection Ids { get; set; } + + public NetEntity Target { get; set; } + + public GuardianSelectorBUIState(IReadOnlyCollection ids, NetEntity target) + { + Ids = ids; + Target = target; + } +} + +[Serializable, NetSerializable] +public sealed class GuardianSelectorSelectedBuiMessage : BoundUserInterfaceMessage +{ + public GuardianSelector GuardianType; + public NetEntity Target; + + public GuardianSelectorSelectedBuiMessage(GuardianSelector guardianType, NetEntity target) + { + GuardianType = guardianType; + Target = target; + } +} + +public sealed partial class ToggleGuardianPowerActionEvent : InstantActionEvent +{ +} + +public sealed partial class ChargerPowerActionEvent : InstantActionEvent +{ +} diff --git a/Resources/Audio/White/Guardian/charger.ogg b/Resources/Audio/White/Guardian/charger.ogg new file mode 100644 index 0000000000..80211eeeaf Binary files /dev/null and b/Resources/Audio/White/Guardian/charger.ogg differ diff --git a/Resources/Changelog/ChangelogWhite.yml b/Resources/Changelog/ChangelogWhite.yml index 60539d3ea5..3f327a32f5 100644 --- a/Resources/Changelog/ChangelogWhite.yml +++ b/Resources/Changelog/ChangelogWhite.yml @@ -1,16 +1,4 @@ Entries: -- author: Aviu - changes: - - message: "\u0411\u0417 \u0442\u0435\u043F\u0435\u0440\u044C \u0443\u0441\u044B\ - \u043F\u043B\u044F\u0435\u0442 \u0433\u0435\u043D\u043E\u043A\u0440\u0430\u0434\ - \u043E\u0432." - type: Add - - message: "\u041D\u0438\u0442\u0440\u0438\u0443\u043C \u0441\u043D\u043E\u0432\u0430\ - \ \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442." - type: Fix - id: 178 - time: '2024-03-03T08:09:04.0000000+00:00' - url: https://api.github.com/repos/frosty-dev/ss14-core/pulls/167 - author: ThereDrD changes: - message: "\u0424\u0438\u043A\u0441 \u043F\u0443\u0441\u0442\u044B\u0445 \u0441\ @@ -9078,3 +9066,45 @@ id: 677 time: '2025-04-13T16:14:06.0000000+00:00' url: https://api.github.com/repos/frosty-dev/ss14-core/pulls/947 +- author: CaypenNow + changes: + - message: "\u0413\u043E\u043B\u043E\u043F\u0430\u0440\u0430\u0437\u0438\u0442\u044B\ + \ \u043F\u043E\u043B\u0443\u0447\u0438\u043B\u0438 \u043F\u043E\u043B\u043D\u043E\ + \u0435 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435. \u0422\u0435\ + \u043F\u0435\u0440\u044C, \u043F\u0440\u0438 \u0438\u043C\u043F\u043B\u0430\u043D\ + \u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0438 \u0433\u043E\u043B\u043E\ + \u043F\u0430\u0440\u0430\u0437\u0438\u0442\u0430, \u0435\u0441\u0442\u044C \u0432\ + \u043E\u0437\u043C\u043E\u0436\u043D\u043E\u0441\u0442\u044C \u0432\u044B\u0431\ + \u0440\u0430\u0442\u044C \u043E\u0434\u0438\u043D \u0438\u0437 4 \u043A\u043B\ + \u0430\u0441\u0441\u043E\u0432. \u041A\u0430\u0436\u0434\u044B\u0439 \u043A\u043B\ + \u0430\u0441\u0441 \u0438\u043C\u0435\u0435\u0442 \u0440\u0430\u0437\u043B\u0438\ + \u0447\u0438\u044F, \u0430 \u043D\u0435\u043A\u043E\u0442\u043E\u0440\u044B\u0435\ + \ \u0438 \u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u0435\ + \ \u0441\u043F\u043E\u0441\u043E\u0431\u043D\u043E\u0441\u0442\u0438." + type: Add + - message: "\u0413\u043E\u043B\u043E\u043F\u0430\u0440\u0430\u0437\u0438\u0442 \u043F\ + \u043E\u043B\u0443\u0447\u0438\u043B \u043D\u043E\u0432\u044B\u0439 \u0441\u043F\ + \u0440\u0430\u0439\u0442" + type: Add + - message: "\u0422\u0435\u043F\u0435\u0440\u044C \u0433\u043E\u043B\u043E\u043F\u0430\ + \u0440\u0430\u0437\u0438\u0442 \u043C\u043E\u0436\u0435\u0442 \u0432\u044B\u0445\ + \u043E\u0434\u0438\u0442\u044C \u0438\u0437 \u0442\u0435\u043B\u0430 \u0441\u0430\ + \u043C" + type: Tweak + - message: "\u0422\u0435\u043F\u0435\u0440\u044C \u0433\u043E\u043B\u043E\u043F\u0430\ + \u0440\u0430\u0437\u0438\u0442 \u043C\u043E\u0436\u0435\u0442 \u0442\u0430\u0441\ + \u043A\u0430\u0442\u044C \u0432\u0435\u0449\u0438\u0442\u044C" + type: Tweak + - message: "\u0422\u0435\u043F\u0435\u0440\u044C \u0446\u0432\u0435\u0442 \u0433\ + \u043E\u043B\u043E\u043F\u0430\u0440\u0430\u0437\u0438\u0442\u0430 - \u0443\u043D\ + \u0438\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u0434\u043B\u044F \u043A\u043B\ + \u0430\u0441\u0441\u0430, \u0430 \u043D\u0435 \u043F\u0440\u043E\u0441\u0442\ + \u043E \u0440\u0430\u043D\u0434\u043E\u043C\u043D\u044B\u0439." + type: Tweak + - message: "\u0426\u0435\u043D\u0430 \u0433\u043E\u043B\u043E\u043F\u0430\u0440\u0430\ + \u0437\u0438\u0442\u0430 \u043F\u043E\u0432\u044B\u0448\u0435\u043D\u0430. 12\ + \ \u0422\u041A --> 13 \u0422\u041A" + type: Tweak + id: 678 + time: '2025-04-18T03:18:09.0000000+00:00' + url: https://api.github.com/repos/frosty-dev/ss14-core/pulls/948 diff --git a/Resources/Locale/ru-RU/_white/guardian/guardian.ftl b/Resources/Locale/ru-RU/_white/guardian/guardian.ftl new file mode 100644 index 0000000000..2d654b9c2a --- /dev/null +++ b/Resources/Locale/ru-RU/_white/guardian/guardian.ftl @@ -0,0 +1,4 @@ +guardian-assasin-name = Ассасин +guardian-lighting-name = Молниеносный +guardian-charger-name = Быстрый +guardian-standart-name = Классический \ No newline at end of file diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml index d5020e2829..8204558502 100644 --- a/Resources/Prototypes/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/Catalog/uplink_catalog.yml @@ -922,7 +922,7 @@ icon: { sprite: /Textures/Objects/Misc/guardian_info.rsi, state: icon } productEntity: BoxHoloparasite cost: - Telecrystal: 12 + Telecrystal: 13 categories: - UplinkAllies conditions: diff --git a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml index c044105a7a..68192a5d50 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml @@ -20,6 +20,7 @@ context: "human" - type: MobMover - type: InputMover + - type: Puller - type: MovementSpeedModifier baseWalkSpeed: 4 baseSprintSpeed: 5.5 @@ -29,20 +30,6 @@ Blunt: 5 soundHit: collection: MetalThud - - type: RandomSprite - available: - - enum.DamageStateVisualLayers.Base: - magic_base: "" - enum.DamageStateVisualLayers.BaseUnshaded: - magic_flare: Sixteen - - enum.DamageStateVisualLayers.Base: - miner_base: "" - enum.DamageStateVisualLayers.BaseUnshaded: - miner_flare: Sixteen - - enum.DamageStateVisualLayers.Base: - tech_base: "" - enum.DamageStateVisualLayers.BaseUnshaded: - tech_flare: Sixteen - type: Sprite drawdepth: Mobs sprite: White/Mobs/Aliens/Guardians/guardians.rsi @@ -112,8 +99,8 @@ # From the uplink injector - type: entity - name: Holoparasite - id: MobHoloparasiteGuardian + name: HoloparasiteStandart + id: MobHoloparasiteGuardianStandart parent: MobGuardianBase description: A mesmerising whirl of hard-light patterns weaves a marvelous, yet oddly familiar visage. It stands proud, tuning into its owner's life to sustain itself. components: @@ -126,22 +113,208 @@ raffle: settings: default - type: GhostTakeoverAvailable + - type: Sprite + drawdepth: Mobs + sprite: White/Mobs/Aliens/Guardians/guardians.rsi + layers: + - state: tech_base + map: [ "enum.DamageStateVisualLayers.Base" ] + - state: tech_flare + map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ] + color: "#707070" + shader: unshaded + noRot: true + - type: Guardian + damageShare: 0.5 + distance: 6 + - type: MovementSpeedModifier + baseWalkSpeed: 4.5 + baseSprintSpeed: 6 + - type: DamageOnHighSpeedImpact + damage: + types: + Blunt: 5 + soundHit: + collection: MetalThud + - type: MeleeWeapon + hidden: false + altDisarm: false + animation: WeaponArcFist + attackRate: 2.2 + autoAttack: true + soundHit: + collection: Punch + damage: + types: + Blunt: 24 + Structural: 24 - type: NameIdentifier group: Holoparasite - type: TypingIndicator proto: holo - - type: Sprite - layers: - - state: tech_base - map: [ "enum.DamageStateVisualLayers.Base" ] - - state: tech_flare - map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ] - color: "#40a7d7" - shader: unshaded - type: HTN rootTask: task: SimpleHumanoidHostileCompound +- type: entity + name: HoloparasiteAssasin + id: MobHoloparasiteGuardianAssasin + parent: MobGuardianBase + description: A mesmerising whirl of hard-light patterns weaves a marvelous, yet oddly familiar visage. It stands proud, tuning into its owner's life to sustain itself. + components: + - type: GhostRole + allowMovement: true + allowSpeech: true + makeSentient: true + name: ghost-role-information-holoparasite-name + description: ghost-role-information-holoparasite-description + raffle: + settings: default + - type: GhostTakeoverAvailable + - type: Sprite + drawdepth: Mobs + sprite: White/Mobs/Aliens/Guardians/guardians.rsi + layers: + - state: tech_base + map: [ "enum.DamageStateVisualLayers.Base" ] + - state: tech_flare + map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ] + color: "#9d0016" + shader: unshaded + noRot: true + - type: Guardian + damageShare: 1 + distance: 10 + - type: MovementSpeedModifier + baseWalkSpeed: 4 + baseSprintSpeed: 5.5 + - type: DamageOnHighSpeedImpact + damage: + types: + Blunt: 5 + soundHit: + collection: MetalThud + - type: MeleeWeapon + hidden: false + altDisarm: false + animation: WeaponArcFist + attackRate: 2 + autoAttack: true + soundHit: + collection: Punch + damage: + types: + Blunt: 14 + Structural: 14 + - type: NameIdentifier + group: Holoparasite + - type: TypingIndicator + proto: holo + - type: HTN + rootTask: + task: SimpleHumanoidHostileCompound + +- type: entity + name: HoloparasiteCharger + id: MobHoloparasiteGuardianCharger + parent: MobGuardianBase + description: A mesmerising whirl of hard-light patterns weaves a marvelous, yet oddly familiar visage. It stands proud, tuning into its owner's life to sustain itself. + components: + - type: GhostRole + allowMovement: true + allowSpeech: true + makeSentient: true + name: ghost-role-information-holoparasite-name + description: ghost-role-information-holoparasite-description + raffle: + settings: default + - type: GhostTakeoverAvailable + - type: Sprite + drawdepth: Mobs + sprite: White/Mobs/Aliens/Guardians/guardians.rsi + layers: + - state: tech_base + map: [ "enum.DamageStateVisualLayers.Base" ] + - state: tech_flare + map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ] + color: "#54f3ff" + shader: unshaded + noRot: true + - type: MovementSpeedModifier + baseWalkSpeed: 6 + baseSprintSpeed: 7.5 + - type: DamageOnHighSpeedImpact + damage: + types: + Blunt: 5 + soundHit: + collection: MetalThud + - type: MeleeWeapon + hidden: false + altDisarm: false + animation: WeaponArcFist + attackRate: 2.5 + autoAttack: true + soundHit: + collection: Punch + damage: + types: + Blunt: 15 + Structural: 15 + - type: NameIdentifier + group: Holoparasite + - type: TypingIndicator + proto: holo + - type: HTN + rootTask: + task: SimpleHumanoidHostileCompound + +- type: entity + name: HoloparasiteLighting + id: MobHoloparasiteGuardianLighting + parent: MobGuardianBase + description: A mesmerising whirl of hard-light patterns weaves a marvelous, yet oddly familiar visage. It stands proud, tuning into its owner's life to sustain itself. + components: + - type: GhostRole + allowMovement: true + allowSpeech: true + makeSentient: true + name: ghost-role-information-holoparasite-name + description: ghost-role-information-holoparasite-description + raffle: + settings: default + - type: GhostTakeoverAvailable + - type: Sprite + drawdepth: Mobs + sprite: White/Mobs/Aliens/Guardians/guardians.rsi + layers: + - state: tech_base + map: [ "enum.DamageStateVisualLayers.Base" ] + - state: tech_flare + map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ] + color: "#2000c8" + shader: unshaded + noRot: true + - type: MeleeWeapon + hidden: false + altDisarm: false + animation: WeaponArcFist + attackRate: 1.8 + autoAttack: true + soundHit: + collection: Punch + damage: + types: + Blunt: 5 + Structural: 5 + - type: NameIdentifier + group: Holoparasite + - type: TypingIndicator + proto: holo + - type: HTN + rootTask: + task: SimpleHumanoidHostileCompound + # From Wizard deck of cards - type: entity name: Ifrit diff --git a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/guardian_activators.yml b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/guardian_activators.yml index 0b1769a3da..cdd3faee0e 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/guardian_activators.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/guardian_activators.yml @@ -9,6 +9,15 @@ state: combat_hypo - type: GuardianCreator guardianProto: MobHoloparasiteGuardian + guardiansAvaliable: + - Assasin + - Charger + - Standart + - Lighting + - type: UserInterface # PARSEC EDIT + interfaces: + enum.GuardianSelectorUiKey.Key: + type: GuardianSelectorBUI - type: entity name: holoclown injector diff --git a/Resources/Prototypes/_White/Actions/guardian.yml b/Resources/Prototypes/_White/Actions/guardian.yml new file mode 100644 index 0000000000..ed192777bd --- /dev/null +++ b/Resources/Prototypes/_White/Actions/guardian.yml @@ -0,0 +1,36 @@ +- type: entity + id: ActionGuardianPowerToggle + name: "[color=green]Сила голопаразита[/color]" + description: Уникальная способность голопаразита. + noSpawn: true + components: + - type: InstantAction + checkCanInteract: false + checkConsciousness: false + icon: White/Interface/Guardian/powerOff.png + iconOn: White/Interface/Guardian/power.png + event: !type:ToggleGuardianPowerActionEvent + +- type: entity + id: ActionGuardianPowerOff + parent: ActionCombatModeToggle + name: "[color=red]Сила голопаразита[/color]" + description: Уникальная способность голопаразита. + noSpawn: true + components: + - type: InstantAction + enabled: false + autoPopulate: false + +- type: entity + id: ActionChargerPower + name: "[color=green]Сила голопаразита[/color]" + description: Уникальная способность голопаразита, позволяет зарядить ваш кулак и одним ударом поразить цель. + noSpawn: true + components: + - type: InstantAction + useDelay: 4 + checkCanInteract: false + checkConsciousness: false + icon: White/Interface/Guardian/powerOff.png + event: !type:ChargerPowerActionEvent \ No newline at end of file diff --git a/Resources/Textures/White/Interface/Guardian/power.png b/Resources/Textures/White/Interface/Guardian/power.png new file mode 100644 index 0000000000..8fef8c374f Binary files /dev/null and b/Resources/Textures/White/Interface/Guardian/power.png differ diff --git a/Resources/Textures/White/Interface/Guardian/powerOff.png b/Resources/Textures/White/Interface/Guardian/powerOff.png new file mode 100644 index 0000000000..4d5b3f8400 Binary files /dev/null and b/Resources/Textures/White/Interface/Guardian/powerOff.png differ diff --git a/Resources/Textures/White/Interface/guardianselector.rsi/assasin.png b/Resources/Textures/White/Interface/guardianselector.rsi/assasin.png new file mode 100644 index 0000000000..b83f25d08a Binary files /dev/null and b/Resources/Textures/White/Interface/guardianselector.rsi/assasin.png differ diff --git a/Resources/Textures/White/Interface/guardianselector.rsi/charger.png b/Resources/Textures/White/Interface/guardianselector.rsi/charger.png new file mode 100644 index 0000000000..471c56ce15 Binary files /dev/null and b/Resources/Textures/White/Interface/guardianselector.rsi/charger.png differ diff --git a/Resources/Textures/White/Interface/guardianselector.rsi/lighting.png b/Resources/Textures/White/Interface/guardianselector.rsi/lighting.png new file mode 100644 index 0000000000..71802ecd4e Binary files /dev/null and b/Resources/Textures/White/Interface/guardianselector.rsi/lighting.png differ diff --git a/Resources/Textures/White/Interface/guardianselector.rsi/meta.json b/Resources/Textures/White/Interface/guardianselector.rsi/meta.json new file mode 100644 index 0000000000..c686859c1a --- /dev/null +++ b/Resources/Textures/White/Interface/guardianselector.rsi/meta.json @@ -0,0 +1,20 @@ +{ + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/tgstation/tgstation/blob/master/icons/hud/guardian.dmi", + "version": 1, + "size": { "y": 32, "x": 32 }, + "states": [ + { + "name": "charger" + }, + { + "name": "lighting" + }, + { + "name": "assasin" + }, + { + "name": "standart" + } + ] +} diff --git a/Resources/Textures/White/Interface/guardianselector.rsi/standart.png b/Resources/Textures/White/Interface/guardianselector.rsi/standart.png new file mode 100644 index 0000000000..f9ff592f1c Binary files /dev/null and b/Resources/Textures/White/Interface/guardianselector.rsi/standart.png differ