Skip to content

Commit

Permalink
RadioImplant and MouthStorage
Browse files Browse the repository at this point in the history
  • Loading branch information
Vonsant committed Dec 7, 2024
1 parent 306bd79 commit 76516fd
Show file tree
Hide file tree
Showing 17 changed files with 518 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Content.Shared._CorvaxNext.Implants.Radio;

namespace Content.Client._CorvaxNext.Implants.Radio;

/// <inheritdoc />
public sealed class RadioImplantSystem : SharedRadioImplantSystem
{
}
126 changes: 126 additions & 0 deletions Content.Server/_CorvaxNext/Implants/Radio/RadioImplantSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using Content.Server.Chat.Systems;
using Content.Server.Radio;
using Content.Server.Radio.Components;
using Content.Server.Radio.EntitySystems;
using Content.Shared._CorvaxNext.Implants.Radio;
using Content.Shared.Radio.Components;
using Robust.Shared.Containers;
using Robust.Shared.Network;
using Robust.Shared.Player;

namespace Content.Server._CorvaxNext.Implants.Radio;

/// <inheritdoc />
public sealed class RadioImplantSystem : SharedRadioImplantSystem
{
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly RadioSystem _radioSystem = default!;

private EntityQuery<ActorComponent> _actorQuery;

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RadioImplantComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<RadioImplantComponent, EntInsertedIntoContainerMessage>(OnInsertEncryptionKey);
SubscribeLocalEvent<RadioImplantComponent, EntRemovedFromContainerMessage>(OnRemoveEncryptionKey);
SubscribeLocalEvent<RadioImplantComponent, RadioReceiveEvent>(OnRadioReceive);
SubscribeLocalEvent<HasRadioImplantComponent, EntitySpokeEvent>(OnSpeak);
_actorQuery = GetEntityQuery<ActorComponent>();
}

/// <summary>
/// Ensures implants with fixed channels work.
/// </summary>
private void OnMapInit(Entity<RadioImplantComponent> ent, ref MapInitEvent args)
{
UpdateRadioReception(ent);
}

/// <summary>
/// Handles the implantee's speech being forwarded onto the radio channel of the implant.
/// </summary>
private void OnSpeak(Entity<HasRadioImplantComponent> ent, ref EntitySpokeEvent args)
{
// not a radio message, or already handled by another radio
if (args.Channel is null)
return;

// does the implant have access to the channel the implantee is trying to speak on?
if (ent.Comp.Implant is {} implant
&& TryComp<RadioImplantComponent>(implant, out var radioImplantComponent)
&& radioImplantComponent.Channels.Contains(args.Channel.ID))
{
_radioSystem.SendRadioMessage(ent, args.Message, args.Channel.ID, implant);
// prevent other radios they might be wearing from sending the message again
args.Channel = null;
}
}

/// <summary>
/// Handles receiving radio messages and forwarding them to the implantee.
/// </summary>
private void OnRadioReceive(EntityUid uid, RadioImplantComponent component, ref RadioReceiveEvent args)
{
if (_actorQuery.TryComp(component.Implantee, out var actorComponent))
_netManager.ServerSendMessage(args.ChatMsg, actorComponent.PlayerSession.Channel);
}

/// <summary>
/// Handles the addition of an encryption key to the implant's storage.
/// </summary>
private void OnInsertEncryptionKey(Entity<RadioImplantComponent> ent, ref EntInsertedIntoContainerMessage args)
{
// check if the insertion is actually something getting inserted into the radio implant storage, since
// this evt also fires when the radio implant is being inserted into a person.
if (ent.Owner != args.Container.Owner
|| !TryComp<EncryptionKeyComponent>(args.Entity, out var encryptionKeyComponent))
return;

// copy over the radio channels that can be accessed
ent.Comp.Channels.Clear();
foreach (var channel in encryptionKeyComponent.Channels)
{
ent.Comp.Channels.Add(channel);
}
Dirty(ent);
UpdateRadioReception(ent);
}

/// <summary>
/// Handles the removal of an encryption key from the implant's storage.
/// </summary>
private void OnRemoveEncryptionKey(Entity<RadioImplantComponent> ent, ref EntRemovedFromContainerMessage args)
{
// check if the insertion is actually something getting inserted into the radio implant storage, since
// this evt also fires when the radio implant is being inserted into a person.
if (ent.Owner != args.Container.Owner
|| !HasComp<EncryptionKeyComponent>(args.Entity))
return;

// clear the radio channels since there's no encryption key inserted anymore.
ent.Comp.Channels.Clear();
Dirty(ent);
UpdateRadioReception(ent);
}

/// <summary>
/// Ensures that this thing can actually hear radio messages from channels the key provides.
/// </summary>
private void UpdateRadioReception(Entity<RadioImplantComponent> ent)
{
if (ent.Comp.Channels.Count != 0)
{
// we need to add this comp to actually receive radio events.
var channels = EnsureComp<ActiveRadioComponent>(ent).Channels;
foreach (var channel in ent.Comp.Channels)
{
channels.Add(channel);
}
}
else
{
RemComp<ActiveRadioComponent>(ent);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Content.Server.Nutrition;
using Content.Server.Speech;
using Content.Server.Speech.EntitySystems;
using Content.Shared._CorvaxNext.Storage.Components;
using Content.Shared._CorvaxNext.Storage.EntitySystems;
using Content.Shared.Storage;

namespace Content.Server._CorvaxNext.Storage.EntitySystems;

public sealed class MouthStorageSystem : SharedMouthStorageSystem
{
[Dependency] private readonly ReplacementAccentSystem _replacement = default!;
public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<MouthStorageComponent, AccentGetEvent>(OnAccent);
SubscribeLocalEvent<MouthStorageComponent, IngestionAttemptEvent>(OnIngestAttempt);
}

// Force you to mumble if you have items in your mouth
private void OnAccent(EntityUid uid, MouthStorageComponent component, AccentGetEvent args)
{
if (IsMouthBlocked(component))
args.Message = _replacement.ApplyReplacements(args.Message, "mumble");
}

// Attempting to eat or drink anything with items in your mouth won't work
private void OnIngestAttempt(EntityUid uid, MouthStorageComponent component, IngestionAttemptEvent args)
{
if (!IsMouthBlocked(component))
return;

if (!TryComp<StorageComponent>(component.MouthId, out var storage))
return;

var firstItem = storage.Container.ContainedEntities[0];
args.Blocker = firstItem;
args.Cancel();
}
}
29 changes: 23 additions & 6 deletions Content.Shared/Storage/EntitySystems/DumpableSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Content.Shared.Placeable;
using Content.Shared.Storage.Components;
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
Expand Down Expand Up @@ -139,27 +140,43 @@ private void StartDoAfter(EntityUid storageUid, EntityUid targetUid, EntityUid u

private void OnDoAfter(EntityUid uid, DumpableComponent component, DumpableDoAfterEvent args)
{
if (args.Handled || args.Cancelled || !TryComp<StorageComponent>(uid, out var storage) || storage.Container.ContainedEntities.Count == 0)
// Corvax-Next-MouthStorage-Start
if (args.Handled || args.Cancelled)
return;

DumpContents(uid, args.Args.Target, args.Args.User, component);
}

// Refactor to allow dumping that doesn't require a verb
[PublicAPI]
public void DumpContents(EntityUid uid, EntityUid? target, EntityUid user, DumpableComponent? component = null)
{
if (!TryComp<StorageComponent>(uid, out var storage)
|| !Resolve(uid, ref component))
return;

if (storage.Container.ContainedEntities.Count == 0)
// Corvax-Next-MouthStorage-End
return;

var dumpQueue = new Queue<EntityUid>(storage.Container.ContainedEntities);

var dumped = false;

if (_disposalUnitSystem.HasDisposals(args.Args.Target))
if (_disposalUnitSystem.HasDisposals(target)) // Corvax-Next-MouthStorage
{
dumped = true;

foreach (var entity in dumpQueue)
{
_disposalUnitSystem.DoInsertDisposalUnit(args.Args.Target.Value, entity, args.Args.User);
_disposalUnitSystem.DoInsertDisposalUnit(target.Value, entity, user); // Corvax-Next-MouthStorage
}
}
else if (HasComp<PlaceableSurfaceComponent>(args.Args.Target))
else if (HasComp<PlaceableSurfaceComponent>(target)) // Corvax-Next-MouthStorage
{
dumped = true;

var (targetPos, targetRot) = _transformSystem.GetWorldPositionRotation(args.Args.Target.Value);
var (targetPos, targetRot) = _transformSystem.GetWorldPositionRotation(target.Value); // Corvax-Next-MouthStorage

foreach (var entity in dumpQueue)
{
Expand All @@ -179,7 +196,7 @@ private void OnDoAfter(EntityUid uid, DumpableComponent component, DumpableDoAft

if (dumped)
{
_audio.PlayPredicted(component.DumpSound, uid, args.User);
_audio.PlayPredicted(component.DumpSound, uid, user);// Corvax-Next-MouthStorage
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Robust.Shared.GameStates;

namespace Content.Shared._CorvaxNext.Implants.Radio;

/// <summary>
/// This indicates this entity has a radio implant implanted into themselves.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedRadioImplantSystem))]
public sealed partial class HasRadioImplantComponent : Component
{
/// <summary>
/// The radio implant. We need this to be able to determine encryption keys.
/// </summary>
[DataField, AutoNetworkedField]
public EntityUid? Implant;
}
24 changes: 24 additions & 0 deletions Content.Shared/_CorvaxNext/Implants/Radio/RadioImplantComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Content.Shared.Radio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;

namespace Content.Shared._CorvaxNext.Implants.Radio;

/// <summary>
/// This is for radio implants. Might be Syndie, might not be Syndie, but either way, it's an implant.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedRadioImplantSystem))]
public sealed partial class RadioImplantComponent : Component
{
/// <summary>
/// The entity this implant got added to.
/// </summary>
[DataField, AutoNetworkedField]
public EntityUid? Implantee;

/// <summary>
/// The channels this implant can talk on.
/// </summary>
[DataField, AutoNetworkedField]
public HashSet<ProtoId<RadioChannelPrototype>> Channels = new();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using Content.Shared.Actions;
using Content.Shared.Implants;
using Content.Shared.Storage;
using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Containers;

namespace Content.Shared._CorvaxNext.Implants.Radio;

/// <summary>
/// This handles radio implants, which you can implant to get access to a radio channel.
/// </summary>
public abstract class SharedRadioImplantSystem : EntitySystem
{
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<RadioImplantComponent, ImplantImplantedEvent>(OnImplanted);
SubscribeLocalEvent<RadioImplantComponent, EntGotRemovedFromContainerMessage>(OnPossiblyUnimplanted);
}

/// <summary>
/// Handles implantation of the implant.
/// </summary>
private void OnImplanted(EntityUid uid, RadioImplantComponent component, ImplantImplantedEvent args)
{
if (args.Implanted is not { Valid: true })
return;

component.Implantee = args.Implanted.Value;
Dirty(uid, component);

// make sure the person entity gets slapped with a component so it can react to it talking.
var hasRadioImplantComponent = EnsureComp<HasRadioImplantComponent>(args.Implanted.Value);
hasRadioImplantComponent.Implant = uid;
Dirty(component.Implantee.Value, hasRadioImplantComponent);
}


/// <summary>
/// Handles removal of the implant from its containing mob.
/// </summary>
/// <remarks>Done via <see cref="EntGotRemovedFromContainerMessage"/> because there is no specific event for an implant being removed.</remarks>
private void OnPossiblyUnimplanted(EntityUid uid, RadioImplantComponent component, EntGotRemovedFromContainerMessage args)
{
if (Terminating(uid))
return;

// this gets fired if it gets removed from ANY container but really, we just want to know if it was removed from its owner...
// so check if the ent we got implanted into matches the container's owner (here, the container's owner is the entity)
if (component.Implantee is not null && component.Implantee == args.Container.Owner)
{
RemComp<HasRadioImplantComponent>(component.Implantee.Value);
component.Implantee = null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Content.Shared._CorvaxNext.Storage.EntitySystems;
using Content.Shared.FixedPoint;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._CorvaxNext.Storage.Components;

[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedMouthStorageSystem))]
public sealed partial class MouthStorageComponent : Component
{
public const string MouthContainerId = "mouth";

[DataField, AutoNetworkedField]
public EntProtoId? OpenStorageAction;

[DataField, AutoNetworkedField]
public EntityUid? Action;

[DataField]
public EntProtoId MouthProto = "ActionOpenMouthStorage";

[ViewVariables]
public Container Mouth = default!;

[DataField]
public EntityUid? MouthId;

// Mimimum inflicted damage on hit to spit out items
[DataField]
public FixedPoint2 SpitDamageThreshold = FixedPoint2.New(2);
}
Loading

0 comments on commit 76516fd

Please sign in to comment.