Skip to content
Open
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
74 changes: 74 additions & 0 deletions Content.Shared/_DEN/Chitinid/Components/ChitinidComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;

namespace Content.Shared._DEN.Chitinid.Components;

/// <summary>
/// Allows an entity to heal a certain amount of damage up to a maximum amount. When the maximum amount is reached the
/// associated action is given a charge. The action is in charge of resetting TotalAbsorbed to allow healing to resume.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause]
public sealed partial class ChitinidComponent : Component
{
/// <summary>
/// The EntProtoId to spawn with the expulsion action.
/// </summary>
[DataField, AutoNetworkedField] public EntProtoId ProductPrototype = "Chitzite";

/// <summary>
/// The action prototype to be granted to the entity that has this component.
/// </summary>
[DataField, AutoNetworkedField] public EntProtoId ExpulsionActionPrototype = "ActionChitzite";

/// <summary>
/// The sound to play when the action is performed.
/// </summary>
[DataField, AutoNetworkedField] public SoundSpecifier ActionSound = new SoundPathSpecifier("/Audio/Animals/cat_hiss.ogg");

/// <summary>
/// The action entity after it has been granted.
/// </summary>
[DataField, AutoNetworkedField] public EntityUid? ActionEntity;

/// <summary>
/// The DamageSpecifier used for healing, this occurs every <see cref="UpdateInterval"/>
/// </summary>
[DataField, AutoNetworkedField] public DamageSpecifier Healing = new()
{
DamageDict = new Dictionary<ProtoId<DamageTypePrototype>, FixedPoint2>
{
{ "Radiation", -0.5f },
}
};

/// <summary>
/// How often this component is updated, specifically the amount of time between each 'tick' of healing.
/// </summary>
[DataField, AutoNetworkedField] public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1);

/// <summary>
/// When this component next needs to be updated.
/// </summary>
[DataField, AutoNetworkedField, AutoPausedField]
public TimeSpan NextUpdate;

/// <summary>
/// The amount of time that the Expulsion action should take. Usually should be equal to length of <see cref="ActionSound"/>
/// </summary>
[DataField, AutoNetworkedField, AutoPausedField]
public TimeSpan ExpulsionTime = TimeSpan.FromSeconds(2.15f);

/// <summary>
/// The maximum amount of damage that can be absorbed before needing to perform <see cref="ActionEntity"/>
/// </summary>
[DataField, AutoNetworkedField] public FixedPoint2 MaximumAbsorbed = 30.0f;

/// <summary>
/// The current amount that has been absorbed, building up towards the next charge of <see cref="ActionEntity"/>
/// </summary>
[DataField, AutoNetworkedField] public FixedPoint2 TotalAbsorbed = 0.0f;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Robust.Shared.Prototypes;

namespace Content.Shared._DEN.Chitinid.Components;

/// <summary>
/// Handles spawning a prototype after a certain delay.
/// </summary>
[RegisterComponent, AutoGenerateComponentState]
public sealed partial class ExpellingProductComponent : Component
{
/// <summary>
/// The prototype to spawn.
/// </summary>
[DataField, AutoNetworkedField] public EntProtoId ProductPrototype;

/// <summary>
/// When the prototype should be spawned. This is a point in time, not an offset.
/// </summary>
[DataField, AutoNetworkedField] public TimeSpan FinishedExpelling;
}
129 changes: 129 additions & 0 deletions Content.Shared/_DEN/Chitinid/EntitySystems/ChitinidSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using Content.Shared._DEN.Chitinid.Components;
using Content.Shared.Actions;
using Content.Shared.Charges.Systems;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Systems;
using Content.Shared.Mobs.Systems;
using Content.Shared.Nutrition;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Popups;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Timing;

namespace Content.Shared._DEN.Chitinid.EntitySystems;

/// <summary>
/// Handles healing of a particular damage type up to specified amount, as well as adding a charge to the associated
/// action when the limit is hit. Also provides handling for the ChitinidActionEvent, specifically, spawning a proto
/// and resetting the healed damage with the action.
/// </summary>
public sealed partial class ChitinidSystem : EntitySystem
{
[Dependency] private IGameTiming _timing = default!;
[Dependency] private SharedActionsSystem _actions = default!;
[Dependency] private SharedAudioSystem _audio = default!;
[Dependency] private DamageableSystem _damageable = default!;
[Dependency] private MobStateSystem _mobState = default!;
[Dependency] private SharedPopupSystem _popup = default!;
[Dependency] private SharedChargesSystem _sharedCharges = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<ChitinidComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ChitinidComponent, ChitinidActionEvent>(OnChitinidAction);
}

public override void Update(float frameTime)
{
base.Update(frameTime);

var curTime = _timing.CurTime;
// Check all the chitinid components and handle healing and recording damage amount, as well as setting the
// action state.
var damageQuery = EntityQueryEnumerator<ChitinidComponent, DamageableComponent>();
while (damageQuery.MoveNext(out var uid, out var chitinid, out var damageable))
{
if (curTime < chitinid.NextUpdate)
continue;

chitinid.NextUpdate += chitinid.UpdateInterval;
DirtyField(uid, chitinid, nameof(ChitinidComponent.NextUpdate));

if (_mobState.IsDead(uid) || chitinid.TotalAbsorbed >= chitinid.MaximumAbsorbed)
continue;

if (_damageable.TryChangeDamage((uid, damageable),
chitinid.Healing,
out var delta,
true,
false))
{
chitinid.TotalAbsorbed += -delta.GetTotal();
if (chitinid.ActionEntity is { } action && chitinid.TotalAbsorbed >= chitinid.MaximumAbsorbed)
{
_sharedCharges.SetCharges(action, 1);
}
}
}

// Handle the time delay for spawning a component with ExpellingProductComponent
var expulsionQuery = EntityQueryEnumerator<ExpellingProductComponent, ChitinidComponent>();
while (expulsionQuery.MoveNext(out var uid, out var expulsion, out var chitinid))
{
if (curTime < expulsion.FinishedExpelling)
continue;

PredictedSpawnNextToOrDrop(expulsion.ProductPrototype, uid);
chitinid.TotalAbsorbed = 0;
RemCompDeferred(uid, expulsion);
}
}

/// <summary>
/// Initialize update times and add the action to the owner.
/// </summary>
private void OnMapInit(Entity<ChitinidComponent> entity, ref MapInitEvent evt)
{
entity.Comp.NextUpdate = _timing.CurTime + entity.Comp.UpdateInterval;
var addedAction = _actions.AddAction(entity, entity.Comp.ExpulsionActionPrototype);
if (addedAction is null)
{
Log.Warning($"Failed to add {entity.Comp.ExpulsionActionPrototype} to {ToPrettyString(entity)}");
return;
}

entity.Comp.ActionEntity = addedAction;
}

/// <summary>
/// Check if ingestion is blocked and then use the ExpellingProductComponent to delay item spawning until the sound
/// is finished playing.
/// </summary>
private void OnChitinidAction(Entity<ChitinidComponent> entity, ref ChitinidActionEvent evt)
{
var attempt = new IngestionAttemptEvent(IngestionSystem.DefaultFlags);
RaiseLocalEvent(entity, ref attempt);

if (attempt.Cancelled && attempt.Blocker is {} blocker)
{
_popup.PopupClient(Loc.GetString("chitzite-mask", ("mask", blocker)), entity, entity);
return;
}

_popup.PopupPredicted(Loc.GetString("chitzite-cough", ("name", Name(entity))), entity, entity);
_audio.PlayPredicted(entity.Comp.ActionSound, entity, entity, AudioParams.Default.WithVariation(0.15f));

var expulsion = EnsureComp<ExpellingProductComponent>(entity);
expulsion.FinishedExpelling = _timing.CurTime + entity.Comp.ExpulsionTime;
expulsion.ProductPrototype = entity.Comp.ProductPrototype;
evt.Handled = true;
}
}

/// <summary>
/// Sent by the Chitzite expulsion action to trigger the sound, entity spawning, and damage reset.
/// </summary>
public sealed partial class ChitinidActionEvent : InstantActionEvent;
9 changes: 9 additions & 0 deletions Resources/Audio/_DEN/Voice/Chitinid/attributions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
- files: ["moth_scream.ogg"]
license: "CC-BY-SA-3.0"
copyright: "Taken from https://github.com/tgstation/tgstation/commit/31c19654e0f641166ecd80c672ea05362fd19488"
source: "https://github.com/tgstation/tgstation/commits/master/sound/voice/moth/scream_moth.ogg"

- files: ["moth_laugh.ogg, moth_chitter.ogg, moth_squeak.ogg"]
license: "CC-BY-SA-3.0"
copyright: "Taken from https://github.com/BeeStation/BeeStation-Hornet/commit/11ba3fa04105c93dd96a63ad4afaef4b20c02d0d"
source: "https://github.com/BeeStation/BeeStation-Hornet/blob/11ba3fa04105c93dd96a63ad4afaef4b20c02d0d/sound/emotes/"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2 changes: 2 additions & 0 deletions Resources/Locale/en-US/_DEN/abilities/chitinid.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
chitzite-mask = Take off your {$mask} first.
chitzite-cough = {CAPITALIZE(THE($name))} starts coughing up a hunk of Chitzite!
10 changes: 9 additions & 1 deletion Resources/Locale/en-US/_DEN/chat/managers/chat-manager.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,12 @@ chat-manager-entity-subtle-wrap-message = [italic]{ PROPER($entity) ->
[true] {CAPITALIZE($entityName)} {$message}[/italic]
}

chat-manager-entity-subtle-ooc-wrap-message = [italic](OOC) {$entityName} {$message}[/italic]
chat-manager-entity-subtle-ooc-wrap-message = [italic](OOC) {$entityName} {$message}[/italic]

# Chitinid Start
chat-speech-verb-name-chitinid = Chitinid
chat-speech-verb-chitinid-1 = clicks
chat-speech-verb-chitinid-2 = chitters
chat-speech-verb-chitinid-3 = hisses
chat-speech-verb-chitinid-4 = buzzes
# Chitinid End
69 changes: 69 additions & 0 deletions Resources/Locale/en-US/_DEN/datasets/chitinid.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Female

names-chitinid-first-female-1 = Amer'ix
names-chitinid-first-female-2 = An'bela
names-chitinid-first-female-3 = An'ora
names-chitinid-first-female-4 = Aza'ran
names-chitinid-first-female-5 = Be'riah
names-chitinid-first-female-6 = Bel'os
names-chitinid-first-female-7 = Da'lrah
names-chitinid-first-female-8 = Di'azo
names-chitinid-first-female-9 = E'nzo
names-chitinid-first-female-10 = Em'era
names-chitinid-first-female-11 = Fi'n'rah
names-chitinid-first-female-12 = He'teka
names-chitinid-first-female-13 = Ir'iska
names-chitinid-first-female-14 = Ish'kar
names-chitinid-first-female-15 = Isha'ba
names-chitinid-first-female-16 = Jes'sri'ka
names-chitinid-first-female-17 = Kalz'za
names-chitinid-first-female-18 = Kaz'zek
names-chitinid-first-female-19 = Lot'tikz
names-chitinid-first-female-20 = Ral'zol
names-chitinid-first-female-21 = Ri'isano
names-chitinid-first-female-22 = Talzz'ark
names-chitinid-first-female-23 = Tess'ara
names-chitinid-first-female-24 = Tez'mal'zar
names-chitinid-first-female-25 = Thri'kis
names-chitinid-first-female-26 = Vani'si'kar
names-chitinid-first-female-27 = Ve'rai
names-chitinid-first-female-28 = Vish'ra
names-chitinid-first-female-29 = Zan'ova
names-chitinid-first-female-30 = Zen'ofi
names-chitinid-first-female-31 = Zzer'ak

# Male

names-chitinid-first-male-1 = Al'vos
names-chitinid-first-male-2 = Amue'val
names-chitinid-first-male-3 = Barma'tos
names-chitinid-first-male-4 = Ben'idar
names-chitinid-first-male-5 = Bil'verrok
names-chitinid-first-male-6 = Crik'xis
names-chitinid-first-male-7 = Daru'nta
names-chitinid-first-male-8 = Dee'aldas
names-chitinid-first-male-9 = Drx'var
names-chitinid-first-male-10 = Hen'sra
names-chitinid-first-male-11 = Hux'von
names-chitinid-first-male-12 = Ilv'imon
names-chitinid-first-male-13 = Is'irax
names-chitinid-first-male-14 = Ish'nax
names-chitinid-first-male-15 = Jax'zaril'va
names-chitinid-first-male-16 = L'ofa
names-chitinid-first-male-17 = Lo'zok
names-chitinid-first-male-18 = Lu'vurx
names-chitinid-first-male-19 = Luc'irax
names-chitinid-first-male-20 = Mer'tex
names-chitinid-first-male-21 = Od'dalis
names-chitinid-first-male-22 = Si'ley
names-chitinid-first-male-23 = Sim'sker
names-chitinid-first-male-24 = Tal'vos
names-chitinid-first-male-25 = Ti'ril
names-chitinid-first-male-26 = Vir'lker
names-chitinid-first-male-27 = Vir'muel
names-chitinid-first-male-28 = Vix'vol
names-chitinid-first-male-29 = Von'draz
names-chitinid-first-male-30 = Vu'lta'voss
names-chitinid-first-male-31 = Xixa'ba
names-chitinid-first-male-32 = Yarr'wat
names-chitinid-first-male-33 = Zay'zz
Loading
Loading