Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return S.P.A.F. #50

Merged
merged 2 commits into from
Aug 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions Content.Server/Stories/Spaf/SpafSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Robust.Shared.Prototypes;
using Content.Server.Polymorph.Systems;
using Content.Shared.Stories.Spaf;

namespace Content.Server.Stories.Spaf;

public sealed partial class SpafSystem : SharedSpafSystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly PolymorphSystem _polymorph = default!;
public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<SpafComponent, SpafPolymorphEvent>(OnPolymorph);
}

private void OnPolymorph(EntityUid uid, SpafComponent component, SpafPolymorphEvent args)
{
if (args.Handled || !TryModifyHunger(args.Performer, args.HungerCost))
return;

if (!_prototype.TryIndex(args.ProtoId, out var prototype))
return;

_polymorph.PolymorphEntity(args.Performer, prototype.Configuration);

args.Handled = true;
}
}
133 changes: 133 additions & 0 deletions Content.Shared/Stories/Spaf/SharedSpafSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
using Content.Shared.Actions;
using Content.Shared.DoAfter;
using Content.Shared.Popups;
using Robust.Shared.Containers;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Fluids;
using Robust.Shared.Prototypes;
using Content.Shared.Stealth;
using Content.Shared.Chemistry.Components;
using Content.Shared.Devour;
using Content.Shared.Devour.Components;

namespace Content.Shared.Stories.Spaf;

public abstract partial class SharedSpafSystem : EntitySystem
{
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedActionsSystem _action = default!;
[Dependency] private readonly HungerSystem _hunger = default!;
[Dependency] private readonly SharedPuddleSystem _puddle = default!;
[Dependency] private readonly SharedStealthSystem _stealth = default!;
public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<SpafComponent, ComponentInit>(OnInit);

SubscribeLocalEvent<SpafComponent, SpafCreateEntityEvent>(OnCreateEntity);
SubscribeLocalEvent<SpafComponent, SpafSpillSolutionEvent>(OnSpill);
SubscribeLocalEvent<SpafComponent, SpafStealthEvent>(OnStealth);
SubscribeLocalEvent<SpafComponent, SpafStealthDoAfterEvent>(OnStealthDoAfter);

SubscribeLocalEvent<SpafComponent, DevourDoAfterEvent>(OnDevourDoAfter);

SubscribeLocalEvent<HungerComponent, FoodPopupEvent>(OnFood);
}

public bool TryModifyHunger(EntityUid uid, float amount, HungerComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;

if (component.CurrentHunger - amount < 0)
{
_popup.PopupEntity(Loc.GetString("need-more-food"), uid, uid);
return false;
}

_hunger.ModifyHunger(uid, -amount, component);

return true;
}

private void OnDevourDoAfter(EntityUid uid, SpafComponent component, DevourDoAfterEvent args)
{
if (!args.Cancelled && TryComp<DevourerComponent>(uid, out var devourer))
_hunger.ModifyHunger(uid, devourer.HealRate);
}

private void OnInit(EntityUid uid, SpafComponent component, ComponentInit args)
{
foreach (var action in component.Actions)
{
var actionId = _action.AddAction(uid, action);
if (actionId.HasValue)
component.GrantedActions.Add(actionId.Value);
}
}

private void OnCreateEntity(EntityUid uid, SpafComponent component, SpafCreateEntityEvent args)
{
if (args.Handled || !TryModifyHunger(args.Performer, args.HungerCost))
return;

SpawnAtPosition(args.Prototype, Transform(args.Performer).Coordinates);

args.Handled = true;
}

private void OnSpill(EntityUid uid, SpafComponent component, SpafSpillSolutionEvent args)
{
if (args.Handled || !TryModifyHunger(args.Performer, args.HungerCost))
return;

var solution = new Solution(args.Solution);

_puddle.TrySpillAt(Transform(args.Performer).Coordinates, solution, out _);

args.Handled = true;
}

private void OnStealth(EntityUid uid, SpafComponent component, SpafStealthEvent args)
{
if (args.Handled || !TryModifyHunger(args.Performer, args.HungerCost))
return;

// DoAfter с Hidden = true используется, чтобы спаф мог видеть сколько секунд
// у него осталось. Достаточно удобно, не требует писать много кода для этого.

_stealth.SetEnabled(uid, true);

args.Handled = _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.Performer, TimeSpan.FromSeconds(args.Seconds), new SpafStealthDoAfterEvent(), args.Performer, args.Performer)
{
Hidden = true,
BreakOnHandChange = false,
BreakOnDropItem = false,
BreakOnWeightlessMove = false,
RequireCanInteract = false,
});
}

private void OnStealthDoAfter(EntityUid uid, SpafComponent component, SpafStealthDoAfterEvent args)
{
if (args.Handled || args.Cancelled)
return;

_stealth.SetEnabled(uid, false);

args.Handled = true;
}

private void OnFood(EntityUid uid, HungerComponent component, FoodPopupEvent args)
{
if (args.Handled)
return;

_popup.PopupEntity("" + component.CurrentHunger, uid, uid);

args.Handled = true;
}
}
56 changes: 56 additions & 0 deletions Content.Shared/Stories/Spaf/SpafActions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using Content.Shared.Actions;
using Content.Shared.Chemistry.Components;
using Content.Shared.DoAfter;
using Content.Shared.Polymorph;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;

namespace Content.Shared.Stories.Spaf;

public interface ISpafAction
{
[DataField("cost")]
public float HungerCost { get; set; }
}

public sealed partial class SpafCreateEntityEvent : InstantActionEvent, ISpafAction
{
[DataField("cost")]
public float HungerCost { get; set; } = 20f;

[DataField("proto", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string Prototype;
}

public sealed partial class SpafSpillSolutionEvent : InstantActionEvent, ISpafAction
{
[DataField("cost")]
public float HungerCost { get; set; } = 10f;

[DataField("solution")]
public Solution Solution { get; set; } = new();
}

public sealed partial class SpafPolymorphEvent : InstantActionEvent, ISpafAction
{
[DataField("cost")]
public float HungerCost { get; set; } = 70f;

[DataField]
public ProtoId<PolymorphPrototype> ProtoId;
}

public sealed partial class SpafStealthEvent : InstantActionEvent, ISpafAction
{
[DataField("cost")]
public float HungerCost { get; set; } = 15f;

[DataField]
public float Seconds { get; set; } = 5f;
}

[Serializable, NetSerializable]
public sealed partial class SpafStealthDoAfterEvent : SimpleDoAfterEvent { }

public sealed partial class FoodPopupEvent : InstantActionEvent { }
19 changes: 19 additions & 0 deletions Content.Shared/Stories/Spaf/SpafComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Content.Shared.StatusIcon;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;

namespace Content.Shared.Stories.Spaf;

[RegisterComponent, NetworkedComponent, Access(typeof(SharedSpafSystem))]
public sealed partial class SpafComponent : Component
{
// TODO: Add spaf status icon
// [DataField]
// public ProtoId<FactionIconPrototype> StatusIcon = "SpafFaction";

[DataField]
public HashSet<string> Actions = new();

[DataField]
public HashSet<EntityUid> GrantedActions = new();
}
9 changes: 9 additions & 0 deletions Resources/Locale/ru-RU/_stories/ghost-roles.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ghost-role-information-spaf-name = S.P.A.F.
ghost-role-information-spaf-description = Вы - космический паразит, который жаждит двух вещей: еды и продолжения жизни. Играйте как настоящий охотник техов при помощи способностей, которые тратят еду во время своего использования
ghost-role-information-spaf-rules = Вы - антагонист, убивайте, ешьте, размножайтесь и в конце концов захватите эту станцию, но старайтесь не попадаться на глаза!
ghost-role-information-spafegg-name = личинка принадлежащая S.P.A.F.
ghost-role-information-spafegg-description = Вы еще не родившийся S.P.A.F. Эволюционируйте до маленького монстра, который выглядит как маленький (и миленький) слизнячок. Это ваше оружие ;)
ghost-role-information-spafegg-rules = Вы - скрытный антагонист, убивайте, ешьте, размножайтесь и в конце концов захватите эту станцию, но старайтесь не попадаться на глаза!
ghost-role-information-spafmouse-name = Мышь, зараженная паразитом S.P.A.F.
ghost-role-information-spafmouse-description = Ваша задача: найти уромное место для последующего превращения в S.P.A.F.
ghost-role-information-spafmouse-rules = Вы - скрытный антагонист, найдите место для эволюции и старайтесь не показывать окружающим, что вы не простая мышь!
1 change: 1 addition & 0 deletions Resources/Locale/ru-RU/_stories/spaf.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
need-more-food = Вам нужно больше еды!
118 changes: 118 additions & 0 deletions Resources/Prototypes/Stories/Actions/spaf.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
- type: entity
id: ActionSpafStealth
name: "[color=limegreen]Режим невидимости[/color]"
description: Потратьте 15 единиц еды, чтобы замаскироваться на 5 секунд.
noSpawn: true
components:
- type: InstantAction
useDelay: 6
icon: Stories/Actions/spaf.rsi/spaf_stealth.png
event: !type:SpafStealthEvent
cost: 15
seconds: 5

- type: entity
id: ActionSpafLight
name: "[color=orange]Светящаяся эссенция[/color]"
description: Выработайте светящуюся эссенцию, чтобы осветить себе путь.
noSpawn: true
components:
- type: InstantAction
useDelay: 1
icon: Stories/Actions/spaf.rsi/spaf_light.png
event: !type:ToggleActionEvent

- type: entity
id: ActionSpafLube
name: "[color=purple]Слизь[/color]"
description: Потратьте 10 единиц еды, чтобы выделить очень скользкую слизь.
noSpawn: true
components:
- type: InstantAction
useDelay: 10
icon: Stories/Actions/spaf.rsi/spaf_lube.png
event: !type:SpafSpillSolutionEvent
cost: 10
solution:
maxVol: 12
reagents:
- ReagentId: SpaceLube
Quantity: 12

- type: entity
id: ActionSpafMine
name: "[color=rosybrown]Органическая ловушка[/color]"
description: Потратьте 20 единиц еды, чтобы создать органическую ловушку, которая оглушает врагов на время.
noSpawn: true
components:
- type: InstantAction
useDelay: 5
icon: Stories/Actions/spaf.rsi/spaf_mine.png
event: !type:SpafCreateEntityEvent
cost: 20
proto: LandMineSpaf

- type: entity
id: ActionSpafFood
name: "[color=lightseagreen]Таинственные знания[/color]"
description: Неизвестный науке метод, который позволяет вам с точностью до сотых определить наполненность своего желудка.
noSpawn: true
components:
- type: InstantAction
useDelay: 1
icon: Stories/Actions/spaf.rsi/spaf_food.png
event: !type:FoodPopupEvent

- type: entity
id: ActionSpafGlue
name: "[color=darkslateblue]Липкая слизь[/color]"
description: Потратьте 10 единиц еды, чтобы выделить липкую слизь.
noSpawn: true
components:
- type: InstantAction
useDelay: 10
icon: Stories/Actions/spaf.rsi/spaf_glue.png
event: !type:SpafSpillSolutionEvent
cost: 10
solution:
maxVol: 12
reagents:
- ReagentId: Slime
Quantity: 12

- type: entity
id: ActionSpafEgg
name: "[color=orange]Новая жизнь[/color]"
description: Потратьте 50 единиц еды, чтобы создать яйцо с маленькой копией себя.
noSpawn: true
components:
- type: InstantAction
useDelay: 60
icon: Stories/Actions/spaf.rsi/spaf_egg.png
event: !type:SpafCreateEntityEvent
cost: 50
proto: MobSpafEgg

- type: entity
id: ActionSpafEvol
name: "[color=blue]Эволюция[/color]"
description: Потратьте 70 единиц еды, чтобы эволюционировать во взрослую особь.
noSpawn: true
components:
- type: InstantAction
useDelay: 60
icon: Stories/Actions/spaf.rsi/spaf_evol.png
event: !type:SpafPolymorphEvent
cost: 70
protoId: MobSpaf

- type: entity
id: ActionSpafDevour
name: "[color=lime]Поглощение[/color]"
description: Съешьте свою жертву, чтобы насытиться.
noSpawn: true
components:
- type: EntityTargetAction
icon: Stories/Actions/spaf.rsi/spaf_devour.png
iconOn: Stories/Actions/spaf.rsi/spaf_devour_on.png
event: !type:DevourActionEvent
Loading
Loading