diff --git a/Content.Client/_CorvaxNext/Implants/Radio/RadioImplantSystem.cs b/Content.Client/_CorvaxNext/Implants/Radio/RadioImplantSystem.cs new file mode 100644 index 00000000000..9a6f515c57e --- /dev/null +++ b/Content.Client/_CorvaxNext/Implants/Radio/RadioImplantSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared._CorvaxNext.Implants.Radio; + +namespace Content.Client._CorvaxNext.Implants.Radio; + +/// +public sealed class RadioImplantSystem : SharedRadioImplantSystem +{ +} diff --git a/Content.Server/_CorvaxNext/Implants/Radio/RadioImplantSystem.cs b/Content.Server/_CorvaxNext/Implants/Radio/RadioImplantSystem.cs new file mode 100644 index 00000000000..feb1ef2eb09 --- /dev/null +++ b/Content.Server/_CorvaxNext/Implants/Radio/RadioImplantSystem.cs @@ -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; + +/// +public sealed class RadioImplantSystem : SharedRadioImplantSystem +{ + [Dependency] private readonly INetManager _netManager = default!; + [Dependency] private readonly RadioSystem _radioSystem = default!; + + private EntityQuery _actorQuery; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnInsertEncryptionKey); + SubscribeLocalEvent(OnRemoveEncryptionKey); + SubscribeLocalEvent(OnRadioReceive); + SubscribeLocalEvent(OnSpeak); + _actorQuery = GetEntityQuery(); + } + + /// + /// Ensures implants with fixed channels work. + /// + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + UpdateRadioReception(ent); + } + + /// + /// Handles the implantee's speech being forwarded onto the radio channel of the implant. + /// + private void OnSpeak(Entity 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(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; + } + } + + /// + /// Handles receiving radio messages and forwarding them to the implantee. + /// + 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); + } + + /// + /// Handles the addition of an encryption key to the implant's storage. + /// + private void OnInsertEncryptionKey(Entity 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(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); + } + + /// + /// Handles the removal of an encryption key from the implant's storage. + /// + private void OnRemoveEncryptionKey(Entity 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(args.Entity)) + return; + + // clear the radio channels since there's no encryption key inserted anymore. + ent.Comp.Channels.Clear(); + Dirty(ent); + UpdateRadioReception(ent); + } + + /// + /// Ensures that this thing can actually hear radio messages from channels the key provides. + /// + private void UpdateRadioReception(Entity ent) + { + if (ent.Comp.Channels.Count != 0) + { + // we need to add this comp to actually receive radio events. + var channels = EnsureComp(ent).Channels; + foreach (var channel in ent.Comp.Channels) + { + channels.Add(channel); + } + } + else + { + RemComp(ent); + } + } +} diff --git a/Content.Server/_CorvaxNext/Storage/EntitySystems/MouthStorageSystem.cs b/Content.Server/_CorvaxNext/Storage/EntitySystems/MouthStorageSystem.cs new file mode 100644 index 00000000000..166673683c3 --- /dev/null +++ b/Content.Server/_CorvaxNext/Storage/EntitySystems/MouthStorageSystem.cs @@ -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(OnAccent); + SubscribeLocalEvent(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(component.MouthId, out var storage)) + return; + + var firstItem = storage.Container.ContainedEntities[0]; + args.Blocker = firstItem; + args.Cancel(); + } +} \ No newline at end of file diff --git a/Content.Shared/Storage/EntitySystems/DumpableSystem.cs b/Content.Shared/Storage/EntitySystems/DumpableSystem.cs index 93c4b69e4dd..a0b4be6589b 100644 --- a/Content.Shared/Storage/EntitySystems/DumpableSystem.cs +++ b/Content.Shared/Storage/EntitySystems/DumpableSystem.cs @@ -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; @@ -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(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(uid, out var storage) + || !Resolve(uid, ref component)) + return; + + if (storage.Container.ContainedEntities.Count == 0) + // Corvax-Next-MouthStorage-End return; var dumpQueue = new Queue(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(args.Args.Target)) + else if (HasComp(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) { @@ -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 } } } diff --git a/Content.Shared/_CorvaxNext/Implants/Radio/HasRadioImplantComponent.cs b/Content.Shared/_CorvaxNext/Implants/Radio/HasRadioImplantComponent.cs new file mode 100644 index 00000000000..76cba880ec7 --- /dev/null +++ b/Content.Shared/_CorvaxNext/Implants/Radio/HasRadioImplantComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._CorvaxNext.Implants.Radio; + +/// +/// This indicates this entity has a radio implant implanted into themselves. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedRadioImplantSystem))] +public sealed partial class HasRadioImplantComponent : Component +{ + /// + /// The radio implant. We need this to be able to determine encryption keys. + /// + [DataField, AutoNetworkedField] + public EntityUid? Implant; +} \ No newline at end of file diff --git a/Content.Shared/_CorvaxNext/Implants/Radio/RadioImplantComponent.cs b/Content.Shared/_CorvaxNext/Implants/Radio/RadioImplantComponent.cs new file mode 100644 index 00000000000..9e05ddbddfd --- /dev/null +++ b/Content.Shared/_CorvaxNext/Implants/Radio/RadioImplantComponent.cs @@ -0,0 +1,24 @@ +using Content.Shared.Radio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._CorvaxNext.Implants.Radio; + +/// +/// This is for radio implants. Might be Syndie, might not be Syndie, but either way, it's an implant. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedRadioImplantSystem))] +public sealed partial class RadioImplantComponent : Component +{ + /// + /// The entity this implant got added to. + /// + [DataField, AutoNetworkedField] + public EntityUid? Implantee; + + /// + /// The channels this implant can talk on. + /// + [DataField, AutoNetworkedField] + public HashSet> Channels = new(); +} \ No newline at end of file diff --git a/Content.Shared/_CorvaxNext/Implants/Radio/SharedRadioImplantSystem.cs b/Content.Shared/_CorvaxNext/Implants/Radio/SharedRadioImplantSystem.cs new file mode 100644 index 00000000000..3b4ed357984 --- /dev/null +++ b/Content.Shared/_CorvaxNext/Implants/Radio/SharedRadioImplantSystem.cs @@ -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; + +/// +/// This handles radio implants, which you can implant to get access to a radio channel. +/// +public abstract class SharedRadioImplantSystem : EntitySystem +{ + /// + public override void Initialize() + { + SubscribeLocalEvent(OnImplanted); + SubscribeLocalEvent(OnPossiblyUnimplanted); + } + + /// + /// Handles implantation of the implant. + /// + 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(args.Implanted.Value); + hasRadioImplantComponent.Implant = uid; + Dirty(component.Implantee.Value, hasRadioImplantComponent); + } + + + /// + /// Handles removal of the implant from its containing mob. + /// + /// Done via because there is no specific event for an implant being removed. + 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(component.Implantee.Value); + component.Implantee = null; + } + } +} \ No newline at end of file diff --git a/Content.Shared/_CorvaxNext/Storage/Components/MouthStorageComponent.cs b/Content.Shared/_CorvaxNext/Storage/Components/MouthStorageComponent.cs new file mode 100644 index 00000000000..75bbf603c17 --- /dev/null +++ b/Content.Shared/_CorvaxNext/Storage/Components/MouthStorageComponent.cs @@ -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); +} \ No newline at end of file diff --git a/Content.Shared/_CorvaxNext/Storage/EntitySystems/SharedMouthStorageSystem.cs b/Content.Shared/_CorvaxNext/Storage/EntitySystems/SharedMouthStorageSystem.cs new file mode 100644 index 00000000000..126ada2b272 --- /dev/null +++ b/Content.Shared/_CorvaxNext/Storage/EntitySystems/SharedMouthStorageSystem.cs @@ -0,0 +1,84 @@ +using Content.Shared.Actions; +using Content.Shared.CombatMode; +using Content.Shared.Damage; +using Content.Shared._CorvaxNext.Storage.Components; +using Content.Shared.Examine; +using Content.Shared.IdentityManagement; +using Content.Shared.Standing; +using Content.Shared.Storage; +using Content.Shared.Storage.EntitySystems; +using Robust.Shared.Containers; +using Robust.Shared.Map; + +namespace Content.Shared._CorvaxNext.Storage.EntitySystems; + +public abstract class SharedMouthStorageSystem : EntitySystem +{ + [Dependency] private readonly DumpableSystem _dumpableSystem = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMouthStorageInit); + SubscribeLocalEvent(DropAllContents); + SubscribeLocalEvent(DropAllContents); + SubscribeLocalEvent(OnDamageModified); + SubscribeLocalEvent(OnExamined); + } + + protected bool IsMouthBlocked(MouthStorageComponent component) + { + if (!TryComp(component.MouthId, out var storage)) + return false; + + return storage.Container.ContainedEntities.Count > 0; + } + + private void OnMouthStorageInit(EntityUid uid, MouthStorageComponent component, MapInitEvent args) + { + if (string.IsNullOrWhiteSpace(component.MouthProto)) + return; + + component.Mouth = _containerSystem.EnsureContainer(uid, MouthStorageComponent.MouthContainerId); + component.Mouth.ShowContents = false; + component.Mouth.OccludesLight = false; + + var mouth = Spawn(component.MouthProto, new EntityCoordinates(uid, 0, 0)); + _containerSystem.Insert(mouth, component.Mouth); + component.MouthId = mouth; + + if (!string.IsNullOrWhiteSpace(component.OpenStorageAction) && component.Action == null) + _actionsSystem.AddAction(uid, ref component.Action, component.OpenStorageAction, mouth); + } + + private void DropAllContents(EntityUid uid, MouthStorageComponent component, EntityEventArgs args) + { + if (component.MouthId == null) + return; + + _dumpableSystem.DumpContents(component.MouthId.Value, uid, uid); + } + + private void OnDamageModified(EntityUid uid, MouthStorageComponent component, DamageChangedEvent args) + { + if (args.DamageDelta == null + || !args.DamageIncreased + || args.DamageDelta.GetTotal() < component.SpitDamageThreshold) + return; + + DropAllContents(uid, component, args); + } + + // Other people can see if this person has items in their mouth. + private void OnExamined(EntityUid uid, MouthStorageComponent component, ExaminedEvent args) + { + if (IsMouthBlocked(component)) + { + var subject = Identity.Entity(uid, EntityManager); + args.PushMarkup(Loc.GetString("mouth-storage-examine-condition-occupied", ("entity", subject))); + } + } +} \ No newline at end of file diff --git a/Resources/Locale/en-US/_corvaxnext/storage/mouth-storage-component.ftl b/Resources/Locale/en-US/_corvaxnext/storage/mouth-storage-component.ftl new file mode 100644 index 00000000000..135f30bdfa1 --- /dev/null +++ b/Resources/Locale/en-US/_corvaxnext/storage/mouth-storage-component.ftl @@ -0,0 +1 @@ +mouth-storage-examine-condition-occupied=[color=yellow]{CAPITALIZE(SUBJECT($entity))} has something in {POSS-ADJ($entity)} mouth.[/color] \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_corvaxnext/storage/mouth-storage-component.ftl b/Resources/Locale/ru-RU/_corvaxnext/storage/mouth-storage-component.ftl new file mode 100644 index 00000000000..dd4286ed1de --- /dev/null +++ b/Resources/Locale/ru-RU/_corvaxnext/storage/mouth-storage-component.ftl @@ -0,0 +1 @@ +mouth-storage-examine-condition-occupied=[color=yellow]{CAPITALIZE(SUBJECT($entity))} явно что-то держит во рту.[/color] \ No newline at end of file diff --git a/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/actions/types.ftl b/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/actions/types.ftl new file mode 100644 index 00000000000..cc7629e971d --- /dev/null +++ b/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/actions/types.ftl @@ -0,0 +1,4 @@ +ent-ActionOpenRadioImplant = Открыть радио-имплант. + .desc = Открывает контейнер ключа шифрования имплантированный в ваш череп. +ent-ActionOpenMouthStorage = Открыть рот. + .desc = Позволяет спрятать что-нибудь за щекой. \ No newline at end of file diff --git a/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/entities/objects/misc/implanters.ftl b/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/entities/objects/misc/implanters.ftl new file mode 100644 index 00000000000..66216865bbc --- /dev/null +++ b/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/entities/objects/misc/implanters.ftl @@ -0,0 +1,6 @@ +ent-GenericRadioImplanter = имплантер коммуниктора + .suffix = НЕ МАППИТЬ + .desc = Этот имплантер содержит радиоустройство со скрытым отсеком для ключа шифрования. Он позволяет своему владельцу общаться на каналах, доступных этому ключу. +ent-SyndicateRadioImplanter = имплантер коммуниктора синдиката + .suffix = синдикат + .desc = Этот имплантер содержит радиомодуль, который позволяет своему владельцу общаться на канале Синдиката. diff --git a/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/entities/objects/misc/mouth_storage.ftl b/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/entities/objects/misc/mouth_storage.ftl new file mode 100644 index 00000000000..4797569057a --- /dev/null +++ b/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/entities/objects/misc/mouth_storage.ftl @@ -0,0 +1,2 @@ +ent-CheekStorage = щёки + .desc = Позволяют вам прятать во рту маленькие секреты. diff --git a/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/entities/objects/misc/subdermal_implants.ftl b/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/entities/objects/misc/subdermal_implants.ftl new file mode 100644 index 00000000000..0c07df9ce55 --- /dev/null +++ b/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/entities/objects/misc/subdermal_implants.ftl @@ -0,0 +1,4 @@ +ent-RadioImplant = имплант коммуниктора + .desc = Этот имплант содержит радиоустройство со скрытым отсеком для ключа шифрования. Он позволяет своему владельцу общаться на каналах, доступных этому ключу. +ent-SyndicateRadioImplant = имплант коммуниктора синдиката + .desc = Этот имплант содержит радиомодуль, который позволяет своему владельцу общаться на канале Синдиката. diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 7bcdf52088f..9b4c378369f 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -286,6 +286,10 @@ Asphyxiation: -1.0 - type: FireVisuals alternateState: Standing + - type: MouthStorage # Corvax-Next-MouthStorage + mouthProto: CheekStorage + openStorageAction: ActionOpenMouthStorage + spitDamageThreshold: 3 - type: entity save: false diff --git a/Resources/Prototypes/_CorvaxNext/Actions/types.yml b/Resources/Prototypes/_CorvaxNext/Actions/types.yml index f8c817185c8..65dd3e2a7b3 100644 --- a/Resources/Prototypes/_CorvaxNext/Actions/types.yml +++ b/Resources/Prototypes/_CorvaxNext/Actions/types.yml @@ -22,4 +22,28 @@ icon: sprite: _CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi state: icon - event: !type:ToggleThermalVisionEvent \ No newline at end of file + event: !type:ToggleThermalVisionEvent + +- type: entity + id: ActionOpenRadioImplant + name: Open Radio Implant + description: Opens the bluespace key compartment of the radio implant embedded in your skull. + components: + - type: InstantAction + itemIconStyle: BigAction + priority: -20 + icon: + sprite: Clothing/Ears/Headsets/base.rsi + state: icon + event: !type:OpenStorageImplantEvent + +- type: entity + id: ActionOpenMouthStorage + name: Open cheek storage + description: Allows you to store items in your cheeks. + components: + - type: InstantAction + itemIconStyle: BigAction + priority: -10 + icon: _CorvaxNext/Interface/Actions/mouthStorageOpen.png + event: !type:OpenStorageImplantEvent \ No newline at end of file diff --git a/Resources/Prototypes/_CorvaxNext/Entities/Objects/Misc/implanters.yml b/Resources/Prototypes/_CorvaxNext/Entities/Objects/Misc/implanters.yml new file mode 100644 index 00000000000..4f708bb7c6c --- /dev/null +++ b/Resources/Prototypes/_CorvaxNext/Entities/Objects/Misc/implanters.yml @@ -0,0 +1,15 @@ +- type: entity + parent: BaseImplantOnlyImplanter + id: GenericRadioImplanter + suffix: generic radio + components: + - type: Implanter + implant: RadioImplant + +- type: entity + parent: BaseImplantOnlyImplanterSyndi + id: SyndicateRadioImplanter + suffix: syndicate radio + components: + - type: Implanter + implant: SyndicateRadioImplant diff --git a/Resources/Prototypes/_CorvaxNext/Entities/Objects/Misc/mouth_storage.yml b/Resources/Prototypes/_CorvaxNext/Entities/Objects/Misc/mouth_storage.yml new file mode 100644 index 00000000000..853c434ee11 --- /dev/null +++ b/Resources/Prototypes/_CorvaxNext/Entities/Objects/Misc/mouth_storage.yml @@ -0,0 +1,29 @@ +- type: entity + id: CheekStorage + name: cheek storage + description: The cheeks capable of storing small objects. + components: + - type: Storage + clickInsert: false + grid: + - 0,1,1,1 + maxItemSize: Small + blacklist: + components: + - Sharp + - MindContainer + - Injector + - Spray + - ResearchDisk + - BalloonPopper + - LightBulb + - NukeDisk + - type: ContainerContainer + containers: + storagebase: !type:Container + ents: [ ] + - type: UserInterface + interfaces: + enum.StorageUiKey.Key: + type: StorageBoundUserInterface + - type: Dumpable diff --git a/Resources/Prototypes/_CorvaxNext/Entities/Objects/Misc/subdermal_implants.yml b/Resources/Prototypes/_CorvaxNext/Entities/Objects/Misc/subdermal_implants.yml new file mode 100644 index 00000000000..cd067474c74 --- /dev/null +++ b/Resources/Prototypes/_CorvaxNext/Entities/Objects/Misc/subdermal_implants.yml @@ -0,0 +1,33 @@ +- type: entity + categories: [ HideSpawnMenu, Spawner ] + parent: StorageImplant + id: RadioImplant + name: generic radio implant + description: This implant contains a radio augmentation with a hidden compartment for an encryption key. It allows its user to communicate on the key's channels. + components: + - type: SubdermalImplant + implantAction: ActionOpenRadioImplant + whitelist: + components: + - Hands # the user needs to have hands to actually insert or remove a key, much like the storage implant + blacklist: + components: + - BorgChassis # borgs have "hands", but can't pick stuff up so the implant would be useless for them + - type: Storage + grid: + - 0,0,0,1 + whitelist: + components: + - EncryptionKey # encryption keys only! + - type: RadioImplant + +- type: entity + categories: [ HideSpawnMenu, Spawner ] + parent: BaseSubdermalImplant + id: SyndicateRadioImplant + name: syndicate radio implant + description: This implant contains a radio augmentation that allows its user to communicate on the Syndicate channel. + components: + - type: RadioImplant + channels: + - Syndicate diff --git a/Resources/Textures/_CorvaxNext/Interface/Actions/mouthStorageOpen.png b/Resources/Textures/_CorvaxNext/Interface/Actions/mouthStorageOpen.png new file mode 100644 index 00000000000..7586c2f0cbc Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Interface/Actions/mouthStorageOpen.png differ