-
Notifications
You must be signed in to change notification settings - Fork 523
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Harpy Playable Species * Update speech_emote_sounds.yml * Update uplink-catalog.ftl
- Loading branch information
Showing
147 changed files
with
2,729 additions
and
253 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
namespace Content.Client.DeltaV.Harpy; | ||
|
||
[RegisterComponent] | ||
public sealed partial class HarpyVisualsComponent : Component | ||
{ } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
using Robust.Client.Graphics; | ||
using Robust.Client.Player; | ||
using Robust.Shared.Enums; | ||
using Robust.Shared.Prototypes; | ||
using Content.Shared.DeltaV.Abilities; | ||
|
||
namespace Content.Client.DeltaV.Overlays; | ||
|
||
public sealed partial class UltraVisionOverlay : Overlay | ||
{ | ||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; | ||
[Dependency] private readonly IPlayerManager _playerManager = default!; | ||
[Dependency] IEntityManager _entityManager = default!; | ||
|
||
|
||
public override bool RequestScreenTexture => true; | ||
public override OverlaySpace Space => OverlaySpace.WorldSpace; | ||
private readonly ShaderInstance _ultraVisionShader; | ||
|
||
public UltraVisionOverlay() | ||
{ | ||
IoCManager.InjectDependencies(this); | ||
_ultraVisionShader = _prototypeManager.Index<ShaderPrototype>("UltraVision").Instance().Duplicate(); | ||
} | ||
|
||
protected override void Draw(in OverlayDrawArgs args) | ||
{ | ||
if (ScreenTexture == null) | ||
return; | ||
if (_playerManager.LocalPlayer?.ControlledEntity is not {Valid: true} player) | ||
return; | ||
if (!_entityManager.HasComponent<UltraVisionComponent>(player)) | ||
return; | ||
|
||
_ultraVisionShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture); | ||
|
||
|
||
var worldHandle = args.WorldHandle; | ||
var viewport = args.WorldBounds; | ||
worldHandle.SetTransform(Matrix3.Identity); | ||
worldHandle.UseShader(_ultraVisionShader); | ||
worldHandle.DrawRect(viewport, Color.White); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
using Content.Shared.DeltaV.Abilities; | ||
using Robust.Client.Graphics; | ||
|
||
namespace Content.Client.DeltaV.Overlays; | ||
|
||
public sealed partial class UltraVisionSystem : EntitySystem | ||
{ | ||
[Dependency] private readonly IOverlayManager _overlayMan = default!; | ||
|
||
private UltraVisionOverlay _overlay = default!; | ||
|
||
public override void Initialize() | ||
{ | ||
base.Initialize(); | ||
|
||
SubscribeLocalEvent<UltraVisionComponent, ComponentInit>(OnUltraVisionInit); | ||
SubscribeLocalEvent<UltraVisionComponent, ComponentShutdown>(OnUltraVisionShutdown); | ||
|
||
_overlay = new(); | ||
} | ||
|
||
private void OnUltraVisionInit(EntityUid uid, UltraVisionComponent component, ComponentInit args) | ||
{ | ||
_overlayMan.AddOverlay(_overlay); | ||
} | ||
|
||
private void OnUltraVisionShutdown(EntityUid uid, UltraVisionComponent component, ComponentShutdown args) | ||
{ | ||
_overlayMan.RemoveOverlay(_overlay); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
using Content.Server.Instruments; | ||
using Content.Server.Speech.Components; | ||
using Content.Server.UserInterface; | ||
using Content.Shared.Instruments; | ||
using Content.Shared.ActionBlocker; | ||
using Content.Shared.Damage; | ||
using Content.Shared.Damage.ForceSay; | ||
using Content.Shared.DeltaV.Harpy; | ||
using Content.Shared.FixedPoint; | ||
using Content.Shared.Inventory; | ||
using Content.Shared.Inventory.Events; | ||
using Content.Shared.Mobs; | ||
using Content.Shared.Popups; | ||
using Content.Shared.StatusEffect; | ||
using Content.Shared.Stunnable; | ||
using Content.Shared.UserInterface; | ||
using Content.Shared.Zombies; | ||
using Robust.Shared.Player; | ||
using Robust.Shared.Prototypes; | ||
using Content.Shared.DeltaV.Harpy.Components; | ||
|
||
namespace Content.Server.DeltaV.Harpy | ||
{ | ||
public sealed class HarpySingerSystem : EntitySystem | ||
{ | ||
[Dependency] private readonly InstrumentSystem _instrument = default!; | ||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!; | ||
[Dependency] private readonly InventorySystem _inventorySystem = default!; | ||
[Dependency] private readonly ActionBlockerSystem _blocker = default!; | ||
[Dependency] private readonly IPrototypeManager _prototype = default!; | ||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; | ||
|
||
public override void Initialize() | ||
{ | ||
base.Initialize(); | ||
|
||
SubscribeLocalEvent<InstrumentComponent, MobStateChangedEvent>(OnMobStateChangedEvent); | ||
SubscribeLocalEvent<GotEquippedEvent>(OnEquip); | ||
SubscribeLocalEvent<EntityZombifiedEvent>(OnZombified); | ||
SubscribeLocalEvent<InstrumentComponent, KnockedDownEvent>(OnKnockedDown); | ||
SubscribeLocalEvent<InstrumentComponent, StunnedEvent>(OnStunned); | ||
SubscribeLocalEvent<InstrumentComponent, SleepStateChangedEvent>(OnSleep); | ||
SubscribeLocalEvent<InstrumentComponent, StatusEffectAddedEvent>(OnStatusEffect); | ||
SubscribeLocalEvent<InstrumentComponent, DamageChangedEvent>(OnDamageChanged); | ||
SubscribeLocalEvent<HarpySingerComponent, BoundUIClosedEvent>(OnBoundUIClosed); | ||
SubscribeLocalEvent<HarpySingerComponent, BoundUIOpenedEvent>(OnBoundUIOpened); | ||
|
||
// This is intended to intercept the UI event and stop the MIDI UI from opening if the | ||
// singer is unable to sing. Thus it needs to run before the ActivatableUISystem. | ||
SubscribeLocalEvent<HarpySingerComponent, OpenUiActionEvent>(OnInstrumentOpen, before: new[] { typeof(ActivatableUISystem) }); | ||
} | ||
|
||
private void OnEquip(GotEquippedEvent args) | ||
{ | ||
// Check if an item that makes the singer mumble is equipped to their face | ||
// (not their pockets!). As of writing, this should just be the muzzle. | ||
if (TryComp<AddAccentClothingComponent>(args.Equipment, out var accent) && | ||
accent.ReplacementPrototype == "mumble" && | ||
args.Slot == "mask") | ||
{ | ||
CloseMidiUi(args.Equipee); | ||
} | ||
} | ||
|
||
private void OnMobStateChangedEvent(EntityUid uid, InstrumentComponent component, MobStateChangedEvent args) | ||
{ | ||
if (args.NewMobState is MobState.Critical or MobState.Dead) | ||
CloseMidiUi(args.Target); | ||
} | ||
|
||
private void OnZombified(ref EntityZombifiedEvent args) | ||
{ | ||
CloseMidiUi(args.Target); | ||
} | ||
|
||
private void OnKnockedDown(EntityUid uid, InstrumentComponent component, ref KnockedDownEvent args) | ||
{ | ||
CloseMidiUi(uid); | ||
} | ||
|
||
private void OnStunned(EntityUid uid, InstrumentComponent component, ref StunnedEvent args) | ||
{ | ||
CloseMidiUi(uid); | ||
} | ||
|
||
private void OnSleep(EntityUid uid, InstrumentComponent component, ref SleepStateChangedEvent args) | ||
{ | ||
if (args.FellAsleep) | ||
CloseMidiUi(uid); | ||
} | ||
|
||
private void OnStatusEffect(EntityUid uid, InstrumentComponent component, StatusEffectAddedEvent args) | ||
{ | ||
if (args.Key == "Muted") | ||
CloseMidiUi(uid); | ||
} | ||
|
||
/// <summary> | ||
/// Almost a copy of Content.Server.Damage.ForceSay.DamageForceSaySystem.OnDamageChanged. | ||
/// Done so because DamageForceSaySystem doesn't output an event, and my understanding is | ||
/// that we don't want to change upstream code more than necessary to avoid merge conflicts | ||
/// and maintenance overhead. It still reuses the values from DamageForceSayComponent, so | ||
/// any tweaks to that will keep ForceSay consistent with singing interruptions. | ||
/// </summary> | ||
private void OnDamageChanged(EntityUid uid, InstrumentComponent instrumentComponent, DamageChangedEvent args) | ||
{ | ||
if (!TryComp<DamageForceSayComponent>(uid, out var component) || | ||
args.DamageDelta == null || | ||
!args.DamageIncreased || | ||
args.DamageDelta.GetTotal() < component.DamageThreshold || | ||
component.ValidDamageGroups == null) | ||
return; | ||
|
||
var totalApplicableDamage = FixedPoint2.Zero; | ||
foreach (var (group, value) in args.DamageDelta.GetDamagePerGroup(_prototype)) | ||
{ | ||
if (!component.ValidDamageGroups.Contains(group)) | ||
continue; | ||
|
||
totalApplicableDamage += value; | ||
} | ||
|
||
if (totalApplicableDamage >= component.DamageThreshold) | ||
CloseMidiUi(uid); | ||
} | ||
|
||
/// <summary> | ||
/// Closes the MIDI UI if it is open. | ||
/// </summary> | ||
private void CloseMidiUi(EntityUid uid) | ||
{ | ||
if (HasComp<ActiveInstrumentComponent>(uid) && | ||
TryComp<ActorComponent>(uid, out var actor)) | ||
{ | ||
_instrument.ToggleInstrumentUi(uid, actor.PlayerSession); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Prevent the player from opening the MIDI UI under some circumstances. | ||
/// </summary> | ||
private void OnInstrumentOpen(EntityUid uid, HarpySingerComponent component, OpenUiActionEvent args) | ||
{ | ||
// CanSpeak covers all reasons you can't talk, including being incapacitated | ||
// (crit/dead), asleep, or for any reason mute inclding glimmer or a mime's vow. | ||
var canNotSpeak = !_blocker.CanSpeak(uid); | ||
var zombified = TryComp<ZombieComponent>(uid, out var _); | ||
var muzzled = _inventorySystem.TryGetSlotEntity(uid, "mask", out var maskUid) && | ||
TryComp<AddAccentClothingComponent>(maskUid, out var accent) && | ||
accent.ReplacementPrototype == "mumble"; | ||
|
||
// Set this event as handled when the singer should be incapable of singing in order | ||
// to stop the ActivatableUISystem event from opening the MIDI UI. | ||
args.Handled = canNotSpeak || muzzled || zombified; | ||
|
||
// Tell the user that they can not sing. | ||
if (args.Handled) | ||
_popupSystem.PopupEntity(Loc.GetString("no-sing-while-no-speak"), uid, uid, PopupType.Medium); | ||
} | ||
|
||
private void OnBoundUIClosed(EntityUid uid, HarpySingerComponent component, BoundUIClosedEvent args) | ||
{ | ||
if (args.UiKey is not InstrumentUiKey) | ||
return; | ||
|
||
TryComp(uid, out AppearanceComponent? appearance); | ||
_appearance.SetData(uid, HarpyVisualLayers.Singing, SingingVisualLayer.False, appearance); | ||
} | ||
|
||
private void OnBoundUIOpened(EntityUid uid, HarpySingerComponent component, BoundUIOpenedEvent args) | ||
{ | ||
if (args.UiKey is not InstrumentUiKey) | ||
return; | ||
|
||
TryComp(uid, out AppearanceComponent? appearance); | ||
_appearance.SetData(uid, HarpyVisualLayers.Singing, SingingVisualLayer.True, appearance); | ||
|
||
} | ||
} | ||
} |
114 changes: 114 additions & 0 deletions
114
Content.Server/DeltaV/Implants/SubdermalBionicSyrinxImplantSystem.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
using Content.Server.Administration.Logs; | ||
using Content.Server.Chat.Systems; | ||
using Content.Server.Popups; | ||
using Content.Server.VoiceMask; | ||
using Content.Server.Implants; | ||
using Content.Shared.Database; | ||
using Content.Shared.Implants; | ||
using Content.Shared.Implants.Components; | ||
using Content.Shared.Popups; | ||
using Content.Shared.Preferences; | ||
using Content.Shared.Tag; | ||
using Content.Shared.VoiceMask; | ||
using Robust.Server.GameObjects; | ||
using Robust.Shared.Containers; | ||
|
||
namespace Content.Server.DeltaV.Implants; | ||
|
||
public sealed class SubdermalBionicSyrinxImplantSystem : EntitySystem | ||
{ | ||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!; | ||
[Dependency] private readonly PopupSystem _popupSystem = default!; | ||
[Dependency] private readonly IAdminLogManager _adminLogger = default!; | ||
[Dependency] private readonly TagSystem _tag = default!; | ||
|
||
[ValidatePrototypeId<TagPrototype>] | ||
public const string BionicSyrinxImplant = "BionicSyrinxImplant"; | ||
|
||
|
||
public override void Initialize() | ||
{ | ||
base.Initialize(); | ||
|
||
SubscribeLocalEvent<VoiceMaskerComponent, ImplantImplantedEvent>(OnInsert); | ||
SubscribeLocalEvent<SyrinxVoiceMaskComponent, TransformSpeakerNameEvent>(OnSpeakerNameTransform); | ||
SubscribeLocalEvent<SyrinxVoiceMaskComponent, VoiceMaskChangeNameMessage>(OnChangeName); | ||
// We need to remove the SyrinxVoiceMaskComponent from the owner before the implant | ||
// is removed, so we need to execute before the SubdermalImplantSystem. | ||
SubscribeLocalEvent<VoiceMaskerComponent, EntGotRemovedFromContainerMessage>(OnRemove, before: new[] { typeof(SubdermalImplantSystem) }); | ||
} | ||
|
||
private void OnInsert(EntityUid uid, VoiceMaskerComponent component, ImplantImplantedEvent args) | ||
{ | ||
if (!args.Implanted.HasValue || | ||
!_tag.HasTag(args.Implant, BionicSyrinxImplant)) | ||
return; | ||
|
||
var voicemask = EnsureComp<SyrinxVoiceMaskComponent>(args.Implanted.Value); | ||
voicemask.VoiceName = MetaData(args.Implanted.Value).EntityName; | ||
Dirty(args.Implanted.Value, voicemask); | ||
} | ||
|
||
private void OnRemove(EntityUid uid, VoiceMaskerComponent component, EntGotRemovedFromContainerMessage args) | ||
{ | ||
if (!TryComp<SubdermalImplantComponent>(uid, out var implanted) || implanted.ImplantedEntity == null) | ||
return; | ||
|
||
RemComp<SyrinxVoiceMaskComponent>(implanted.ImplantedEntity.Value); | ||
} | ||
|
||
/// <summary> | ||
/// Copy from VoiceMaskSystem, adapted to work with SyrinxVoiceMaskComponent. | ||
/// </summary> | ||
private void OnChangeName(EntityUid uid, SyrinxVoiceMaskComponent component, VoiceMaskChangeNameMessage message) | ||
{ | ||
if (message.Name.Length > HumanoidCharacterProfile.MaxNameLength || message.Name.Length <= 0) | ||
{ | ||
_popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-failure"), uid, message.Session, PopupType.SmallCaution); | ||
return; | ||
} | ||
|
||
component.VoiceName = message.Name; | ||
if (message.Session.AttachedEntity != null) | ||
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(message.Session.AttachedEntity.Value):player} set voice of {ToPrettyString(uid):mask}: {component.VoiceName}"); | ||
else | ||
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"Voice of {ToPrettyString(uid):mask} set: {component.VoiceName}"); | ||
|
||
_popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-success"), uid, message.Session); | ||
TrySetLastKnownName(uid, message.Name); | ||
UpdateUI(uid, component); | ||
} | ||
|
||
/// <summary> | ||
/// Copy from VoiceMaskSystem, adapted to work with SyrinxVoiceMaskComponent. | ||
/// </summary> | ||
private void TrySetLastKnownName(EntityUid implanted, string lastName) | ||
{ | ||
if (!HasComp<VoiceMaskComponent>(implanted) | ||
|| !TryComp<VoiceMaskerComponent>(implanted, out var maskComp)) | ||
return; | ||
|
||
maskComp.LastSetName = lastName; | ||
} | ||
|
||
/// <summary> | ||
/// Copy from VoiceMaskSystem, adapted to work with SyrinxVoiceMaskComponent. | ||
/// </summary> | ||
private void UpdateUI(EntityUid owner, SyrinxVoiceMaskComponent? component = null) | ||
{ | ||
if (!Resolve(owner, ref component, logMissing: false)) | ||
return; | ||
|
||
if (_uiSystem.TryGetUi(owner, VoiceMaskUIKey.Key, out var bui)) | ||
_uiSystem.SetUiState(bui, new VoiceMaskBuiState(component.VoiceName)); | ||
} | ||
|
||
/// <summary> | ||
/// Copy from VoiceMaskSystem, adapted to work with SyrinxVoiceMaskComponent. | ||
/// </summary> | ||
private void OnSpeakerNameTransform(EntityUid uid, SyrinxVoiceMaskComponent component, TransformSpeakerNameEvent args) | ||
{ | ||
if (component.Enabled) | ||
args.Name = component.VoiceName; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace Content.Server.VoiceMask; | ||
|
||
[RegisterComponent] | ||
public sealed partial class SyrinxVoiceMaskComponent : Component | ||
{ | ||
[ViewVariables(VVAccess.ReadWrite)] public bool Enabled = true; | ||
|
||
[ViewVariables(VVAccess.ReadWrite)] public string VoiceName = "Unknown"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using Robust.Shared.GameStates; | ||
namespace Content.Shared.DeltaV.Abilities; | ||
|
||
[RegisterComponent] | ||
[NetworkedComponent] | ||
|
||
public sealed partial class UltraVisionComponent : Component | ||
{} |
Oops, something went wrong.