6
Content.Client/Devour/DevourSystem.cs
Normal file
6
Content.Client/Devour/DevourSystem.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
using Content.Shared.Devour;
|
||||
|
||||
namespace Content.Client.Devour;
|
||||
public sealed class DevourSystem : SharedDevourSystem
|
||||
{
|
||||
}
|
||||
51
Content.Server/Devour/DevourSystem.cs
Normal file
51
Content.Server/Devour/DevourSystem.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Content.Shared.Devour;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Server.Devour.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.Devour;
|
||||
|
||||
public sealed class DevourSystem : SharedDevourSystem
|
||||
{
|
||||
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DevourerComponent, DevourDoAfterEvent>(OnDoAfter);
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, DevourerComponent component, DevourDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
var ichorInjection = new Solution(component.Chemical, component.HealRate);
|
||||
|
||||
if (component.FoodPreference == FoodPreference.All ||
|
||||
(component.FoodPreference == FoodPreference.Humanoid && HasComp<HumanoidAppearanceComponent>(args.Args.Target)))
|
||||
{
|
||||
ichorInjection.ScaleSolution(0.5f);
|
||||
|
||||
if (component.ShouldStoreDevoured && args.Args.Target is not null)
|
||||
{
|
||||
component.Stomach.Insert(args.Args.Target.Value);
|
||||
}
|
||||
_bloodstreamSystem.TryAddToChemicals(uid, ichorInjection);
|
||||
}
|
||||
|
||||
//TODO: Figure out a better way of removing structures via devour that still entails standing still and waiting for a DoAfter. Somehow.
|
||||
//If it's not human, it must be a structure
|
||||
else if (args.Args.Target != null)
|
||||
{
|
||||
QueueDel(args.Args.Target.Value);
|
||||
}
|
||||
|
||||
_audioSystem.PlayPvs(component.SoundDevour, uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,23 +13,6 @@ namespace Content.Server.Dragon
|
||||
[RegisterComponent]
|
||||
public sealed class DragonComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The chemical ID injected upon devouring
|
||||
/// </summary>
|
||||
[DataField("devourChemical", customTypeSerializer: typeof(PrototypeIdSerializer<ReagentPrototype>))]
|
||||
public string DevourChem = "Ichor";
|
||||
|
||||
/// <summary>
|
||||
/// The amount of ichor injected per devour
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("devourHealRate")]
|
||||
public float DevourHealRate = 15f;
|
||||
|
||||
[DataField("devourActionId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityTargetActionPrototype>))]
|
||||
public string DevourActionId = "DragonDevour";
|
||||
|
||||
[DataField("devourAction")]
|
||||
public EntityTargetAction? DevourAction;
|
||||
|
||||
/// <summary>
|
||||
/// If we have active rifts.
|
||||
@@ -68,58 +51,15 @@ namespace Content.Server.Dragon
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("riftPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string RiftPrototype = "CarpRift";
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time it takes to devour something
|
||||
/// <remarks>
|
||||
/// NOTE: original intended design was to increase this proportionally with damage thresholds, but those proved quite difficult to get consistently. right now it devours the structure at a fixed timer.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
[DataField("structureDevourTime")]
|
||||
public float StructureDevourTime = 10f;
|
||||
|
||||
[DataField("devourTime")]
|
||||
public float DevourTime = 3f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("soundDeath")]
|
||||
public SoundSpecifier? SoundDeath = new SoundPathSpecifier("/Audio/Animals/space_dragon_roar.ogg");
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("soundDevour")]
|
||||
public SoundSpecifier? SoundDevour = new SoundPathSpecifier("/Audio/Effects/demon_consume.ogg")
|
||||
{
|
||||
Params = AudioParams.Default.WithVolume(-3f),
|
||||
};
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("soundStructureDevour")]
|
||||
public SoundSpecifier? SoundStructureDevour = new SoundPathSpecifier("/Audio/Machines/airlock_creaking.ogg")
|
||||
{
|
||||
Params = AudioParams.Default.WithVolume(-3f),
|
||||
};
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("soundRoar")]
|
||||
public SoundSpecifier? SoundRoar =
|
||||
new SoundPathSpecifier("/Audio/Animals/space_dragon_roar.ogg")
|
||||
{
|
||||
Params = AudioParams.Default.WithVolume(3f),
|
||||
};
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("devourWhitelist")]
|
||||
public EntityWhitelist? DevourWhitelist = new()
|
||||
{
|
||||
Components = new[]
|
||||
{
|
||||
"Door",
|
||||
"MobState",
|
||||
},
|
||||
Tags = new List<string>
|
||||
{
|
||||
"Wall",
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Where the entities go when dragon devours them, empties when the dragon is butchered.
|
||||
/// </summary>
|
||||
public Container DragonStomach = default!;
|
||||
}
|
||||
|
||||
public sealed class DragonDevourActionEvent : EntityTargetActionEvent {}
|
||||
|
||||
@@ -59,12 +59,9 @@ namespace Content.Server.Dragon
|
||||
|
||||
SubscribeLocalEvent<DragonComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<DragonComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<DragonComponent, DragonDevourActionEvent>(OnDevourAction);
|
||||
SubscribeLocalEvent<DragonComponent, DragonSpawnRiftActionEvent>(OnDragonRift);
|
||||
SubscribeLocalEvent<DragonComponent, RefreshMovementSpeedModifiersEvent>(OnDragonMove);
|
||||
|
||||
SubscribeLocalEvent<DragonComponent, DragonDevourDoAfterEvent>(OnDoAfter);
|
||||
|
||||
SubscribeLocalEvent<DragonComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
|
||||
SubscribeLocalEvent<DragonRiftComponent, ComponentShutdown>(OnRiftShutdown);
|
||||
@@ -75,29 +72,6 @@ namespace Content.Server.Dragon
|
||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRiftRoundEnd);
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, DragonComponent component, DragonDevourDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
var ichorInjection = new Solution(component.DevourChem, component.DevourHealRate);
|
||||
|
||||
//Humanoid devours allow dragon to get eggs, corpses included
|
||||
if (HasComp<HumanoidAppearanceComponent>(args.Args.Target))
|
||||
{
|
||||
ichorInjection.ScaleSolution(0.5f);
|
||||
component.DragonStomach.Insert(args.Args.Target.Value);
|
||||
_bloodstreamSystem.TryAddToChemicals(uid, ichorInjection);
|
||||
}
|
||||
|
||||
//TODO: Figure out a better way of removing structures via devour that still entails standing still and waiting for a DoAfter. Somehow.
|
||||
//If it's not human, it must be a structure
|
||||
else if (args.Args.Target != null)
|
||||
EntityManager.QueueDeleteEntity(args.Args.Target.Value);
|
||||
|
||||
_audioSystem.PlayPvs(component.SoundDevour, uid);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
@@ -303,8 +277,6 @@ namespace Content.Server.Dragon
|
||||
if (component.SoundDeath != null)
|
||||
_audioSystem.PlayPvs(component.SoundDeath, uid, component.SoundDeath.Params);
|
||||
|
||||
component.DragonStomach.EmptyContainer();
|
||||
|
||||
foreach (var rift in component.Rifts)
|
||||
{
|
||||
QueueDel(rift);
|
||||
@@ -322,62 +294,10 @@ namespace Content.Server.Dragon
|
||||
|
||||
private void OnStartup(EntityUid uid, DragonComponent component, ComponentStartup args)
|
||||
{
|
||||
//Dragon doesn't actually chew, since he sends targets right into his stomach.
|
||||
//I did it mom, I added ERP content into upstream. Legally!
|
||||
component.DragonStomach = _containerSystem.EnsureContainer<Container>(uid, "dragon_stomach");
|
||||
|
||||
if (component.DevourAction != null)
|
||||
_actionsSystem.AddAction(uid, component.DevourAction, null);
|
||||
|
||||
if (component.SpawnRiftAction != null)
|
||||
_actionsSystem.AddAction(uid, component.SpawnRiftAction, null);
|
||||
|
||||
Roar(component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The devour action
|
||||
/// </summary>
|
||||
private void OnDevourAction(EntityUid uid, DragonComponent component, DragonDevourActionEvent args)
|
||||
{
|
||||
if (args.Handled || component.DevourWhitelist?.IsValid(args.Target, EntityManager) != true)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
var target = args.Target;
|
||||
|
||||
// Structure and mob devours handled differently.
|
||||
if (EntityManager.TryGetComponent(target, out MobStateComponent? targetState))
|
||||
{
|
||||
switch (targetState.CurrentState)
|
||||
{
|
||||
case MobState.Critical:
|
||||
case MobState.Dead:
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(uid, component.DevourTime, new DragonDevourDoAfterEvent(), uid, target: target, used: uid)
|
||||
{
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
_popupSystem.PopupEntity(Loc.GetString("devour-action-popup-message-fail-target-alive"), uid, uid);
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("devour-action-popup-message-structure"), uid, uid);
|
||||
|
||||
if (component.SoundStructureDevour != null)
|
||||
_audioSystem.PlayPvs(component.SoundStructureDevour, uid, component.SoundStructureDevour.Params);
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(uid, component.StructureDevourTime, new DragonDevourDoAfterEvent(), uid, target: target, used: uid)
|
||||
{
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
82
Content.Shared/Devour/Components/DevourerComponent.cs
Normal file
82
Content.Shared/Devour/Components/DevourerComponent.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Devour;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Devour.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedDevourSystem))]
|
||||
public sealed class DevourerComponent : Component
|
||||
{
|
||||
[DataField("devourActionId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityTargetActionPrototype>))]
|
||||
public string DevourActionId = "Devour";
|
||||
|
||||
[DataField("devourAction")]
|
||||
public EntityTargetAction? DevourAction;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("soundDevour")]
|
||||
public SoundSpecifier? SoundDevour = new SoundPathSpecifier("/Audio/Effects/demon_consume.ogg")
|
||||
{
|
||||
Params = AudioParams.Default.WithVolume(-3f),
|
||||
};
|
||||
|
||||
[DataField("devourTime")]
|
||||
public float DevourTime = 3f;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time it takes to devour something
|
||||
/// <remarks>
|
||||
/// NOTE: original intended design was to increase this proportionally with damage thresholds, but those proved quite difficult to get consistently. right now it devours the structure at a fixed timer.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
[DataField("structureDevourTime")]
|
||||
public float StructureDevourTime = 10f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("soundStructureDevour")]
|
||||
public SoundSpecifier? SoundStructureDevour = new SoundPathSpecifier("/Audio/Machines/airlock_creaking.ogg")
|
||||
{
|
||||
Params = AudioParams.Default.WithVolume(-3f),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Where the entities go when it devours them, empties when it is butchered.
|
||||
/// </summary>
|
||||
public Container Stomach = default!;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("shouldStoreDevoured")]
|
||||
public bool ShouldStoreDevoured = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("whitelist")]
|
||||
public EntityWhitelist? Whitelist = new()
|
||||
{
|
||||
Components = new[]
|
||||
{
|
||||
"MobState",
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The chemical ID injected upon devouring
|
||||
/// </summary>
|
||||
[DataField("chemical", customTypeSerializer: typeof(PrototypeIdSerializer<ReagentPrototype>))]
|
||||
public string Chemical = "Ichor";
|
||||
|
||||
/// <summary>
|
||||
/// The amount of ichor injected per devour
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("healRate")]
|
||||
public float HealRate = 15f;
|
||||
|
||||
/// <summary>
|
||||
/// The favorite food not only feeds you, but also heals
|
||||
/// </summary>
|
||||
[DataField("foodPreference")]
|
||||
public FoodPreference FoodPreference = FoodPreference.All;
|
||||
}
|
||||
|
||||
94
Content.Shared/Devour/SharedDevourSystem.cs
Normal file
94
Content.Shared/Devour/SharedDevourSystem.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs;
|
||||
using Robust.Shared.Containers;
|
||||
using Content.Server.Devour.Components;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Devour;
|
||||
|
||||
public abstract class SharedDevourSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DevourerComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<DevourerComponent, DevourActionEvent>(OnDevourAction);
|
||||
}
|
||||
|
||||
protected void OnStartup(EntityUid uid, DevourerComponent component, ComponentStartup args)
|
||||
{
|
||||
//Devourer doesn't actually chew, since he sends targets right into his stomach.
|
||||
//I did it mom, I added ERP content into upstream. Legally!
|
||||
component.Stomach = _containerSystem.EnsureContainer<Container>(uid, "stomach");
|
||||
|
||||
if (component.DevourAction != null)
|
||||
_actionsSystem.AddAction(uid, component.DevourAction, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The devour action
|
||||
/// </summary>
|
||||
protected void OnDevourAction(EntityUid uid, DevourerComponent component, DevourActionEvent args)
|
||||
{
|
||||
if (args.Handled || component.Whitelist?.IsValid(args.Target, EntityManager) != true)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
var target = args.Target;
|
||||
|
||||
// Structure and mob devours handled differently.
|
||||
if (TryComp(target, out MobStateComponent? targetState))
|
||||
{
|
||||
switch (targetState.CurrentState)
|
||||
{
|
||||
case MobState.Critical:
|
||||
case MobState.Dead:
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(uid, component.DevourTime, new DevourDoAfterEvent(), uid, target: target, used: uid)
|
||||
{
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
_popupSystem.PopupEntity(Loc.GetString("devour-action-popup-message-fail-target-alive"), uid, uid);
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("devour-action-popup-message-structure"), uid, uid);
|
||||
|
||||
if (component.SoundStructureDevour != null)
|
||||
_audioSystem.PlayPvs(component.SoundStructureDevour, uid, component.SoundStructureDevour.Params);
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(uid, component.StructureDevourTime, new DevourDoAfterEvent(), uid, target: target, used: uid)
|
||||
{
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DevourActionEvent : EntityTargetActionEvent { }
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class DevourDoAfterEvent : SimpleDoAfterEvent { }
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum FoodPreference : byte
|
||||
{
|
||||
Humanoid = 0,
|
||||
All = 1
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Content.Shared.DoAfter;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Dragon;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class DragonDevourDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
}
|
||||
@@ -97,22 +97,25 @@
|
||||
types:
|
||||
Piercing: 15
|
||||
Slash: 15
|
||||
- type: Dragon
|
||||
spawnsLeft: 2
|
||||
spawnsProto: MobCarpDragon
|
||||
- type: Devourer
|
||||
foodPreference: Humanoid
|
||||
shouldStoreDevoured: true
|
||||
chemical: Ichor
|
||||
healRate: 15.0
|
||||
whitelist:
|
||||
components:
|
||||
- MobState
|
||||
- Door
|
||||
tags:
|
||||
- Wall
|
||||
devourAction:
|
||||
event: !type:DragonDevourActionEvent
|
||||
event: !type:DevourActionEvent
|
||||
icon: Interface/Actions/devour.png
|
||||
name: action-name-devour
|
||||
description: action-description-devour
|
||||
devourChemical: Ichor
|
||||
devourHealRate: 15.0
|
||||
whitelist:
|
||||
components:
|
||||
- MobState
|
||||
- Door
|
||||
tags:
|
||||
- Wall
|
||||
- type: Dragon
|
||||
spawnsLeft: 2
|
||||
spawnsProto: MobCarpDragon
|
||||
spawnRiftAction:
|
||||
event: !type:DragonSpawnRiftActionEvent
|
||||
icon:
|
||||
|
||||
Reference in New Issue
Block a user