Added a component to eat bodies for health #8922 (#16345)

This commit is contained in:
PilgrimViis
2023-05-16 00:44:35 +02:00
committed by GitHub
parent 4d66f817b8
commit 832ba7b6f3
8 changed files with 248 additions and 161 deletions

View File

@@ -0,0 +1,6 @@
using Content.Shared.Devour;
namespace Content.Client.Devour;
public sealed class DevourSystem : SharedDevourSystem
{
}

View 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);
}
}

View File

@@ -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 {}

View File

@@ -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,
});
}
}
}

View 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;
}

View 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
}

View File

@@ -1,9 +0,0 @@
using Content.Shared.DoAfter;
using Robust.Shared.Serialization;
namespace Content.Shared.Dragon;
[Serializable, NetSerializable]
public sealed class DragonDevourDoAfterEvent : SimpleDoAfterEvent
{
}

View File

@@ -97,22 +97,25 @@
types:
Piercing: 15
Slash: 15
- type: Dragon
spawnsLeft: 2
spawnsProto: MobCarpDragon
devourAction:
event: !type:DragonDevourActionEvent
icon: Interface/Actions/devour.png
name: action-name-devour
description: action-description-devour
devourChemical: Ichor
devourHealRate: 15.0
- type: Devourer
foodPreference: Humanoid
shouldStoreDevoured: true
chemical: Ichor
healRate: 15.0
whitelist:
components:
- MobState
- Door
tags:
- Wall
devourAction:
event: !type:DevourActionEvent
icon: Interface/Actions/devour.png
name: action-name-devour
description: action-description-devour
- type: Dragon
spawnsLeft: 2
spawnsProto: MobCarpDragon
spawnRiftAction:
event: !type:DragonSpawnRiftActionEvent
icon: