Skip to content

Commit

Permalink
Merge branch 'master' into 2024-03-22-Jerma
Browse files Browse the repository at this point in the history
  • Loading branch information
dvir001 committed Mar 25, 2024
2 parents 4ddb290 + 5e24fbb commit 4acddfd
Show file tree
Hide file tree
Showing 428 changed files with 31,044 additions and 4,152 deletions.
5 changes: 5 additions & 0 deletions Content.Client/DeltaV/Harpy/HarpyVisualsComponent.cs
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
{ }
44 changes: 44 additions & 0 deletions Content.Client/DeltaV/Overlays/UltraVisionOverlay.cs
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);
}
}
31 changes: 31 additions & 0 deletions Content.Client/DeltaV/Overlays/UltraVisionSystem.cs
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);
}
}
180 changes: 180 additions & 0 deletions Content.Server/DeltaV/Harpy/HarpySingerSystem.cs
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 Content.Server/DeltaV/Implants/SubdermalBionicSyrinxImplantSystem.cs
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;
}
}
9 changes: 9 additions & 0 deletions Content.Server/DeltaV/VoiceMask/SyrinxVoiceMaskComponent.cs
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";
}
Loading

0 comments on commit 4acddfd

Please sign in to comment.