mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-30 08:07:28 +00:00
* Nubody (#42419) * Nubody * fix test fails * gibbing * lung test returns * doc comment * hand organ test * giblet test * yaml formatting * returning * relocate * trimming * re-smite * oops thusd tweak * arachnids have slower metabolism i guess * never mind the old behaviour is bad actually * rider whyyy * style changes and allat * fix collision --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> * C# files done * Visual nubody (humanoid appearance refactor) (#42476) * initial visual nubody * oops overlay * im so pheeming rn * conversion... * tests * comeback of the underwear * oops eyes * blabbl * zeds * yaml linted * search and visible count constraints * reordering * preserve previously selected markings colors * fix test * some ui niceties * ordering * make DB changes backwards-compatible/downgrade-friendly * fix things again * fix migration * vulpkanin markings limit increase * wrapping * code cleanup and more code cleanup and more code cleanup and more code cleanup and * fix slop ports * better sampling API * make filter work + use the method i made for its intended purpose * fix test fails real quick * magic mirror cleanup, remove TODO * don't 0-init the organ profile data * remove deltastates --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> * Let vox eat trash again (#42503) * C# complete port * Getting closer * Harpy Nubody Port * Port Feroxi, Kitsune. Fix some yml typos and remove ipcs stuff * Port Oni * Port Rest, except Motorkind * Port Motorkind * Remove more surgery, restore too much deleted stuff * Fix Typos and change some organ names. Need to split eyes next * Harpy HAve Wings again * Some fixes, More Surgeyr Removal, Copying of eye organs * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Use correct Body imports * Markings Refactoring, Harpy Layering * Motorkind doesn't crash anymore. Some layer remappings. * Allow Organs to have Markings Displacements again. (#43604) * first shtep * readd markings support --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> * Fix STalk eyes and several layerings * Fix sexed organs (#42554) * Fix markings for several species, fit them for sexed organs and rename some files to fit them for it * Additional IPC-related removals * Vulpkanin eyes are now correct again * Fix Subtle Cultist Marks on species * Fix Asakim Sprites (sexed) and make CD imports work * Migration, error fixes, and Height * Fix humanoid profile voice being broken (#42550) Fix humanoid appearance voice being broken * Change Kitsune SexChanged Event to with ref * Fix asakim issue, Respirator fix * Simplify hands UI code (#42534) * Simplify hands UI code * i remembered about SortedHands in the component * minor cleanup --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> * Set size of Analyzer back to 350, Kitsune can't gib themselves no more. Migration * yml linter errors and an rudimentary onAdd Organ Functionality * Fixing it trying to remove nothing * midpoint migration * Why do you put so many organs everywhere * More missing prototypes * Migration of more organs * Forgot lungs * Move character preview handling into a specialized control (#41252) * Move character preview handling into a specialized control Co-authored-by: Quantum-cross <7065792+Quantum-cross@users.noreply.github.com> * Restore job name that I accidentally removed from character picker buttons * Just resolve dependencies the standard way --------- Co-authored-by: Quantum-cross <7065792+Quantum-cross@users.noreply.github.com> Co-authored-by: Janet Blackquill <uhhadd@gmail.com> * Drop ICharacterProfile/ICharacterAppearance interfaces (#42661) * Ensure profile loading only returns valid species (#42842) (Stable merge) (#42865) Co-authored-by: pathetic meowmeow <uhhadd@gmail.com> * Changes to make it work for us. * No more bald Harpy * OperatingTables instead of null * Adding Delta V Comments, commenting out instead of deleting * Fix broken Height Scale. * Remove HumanoidSilicon Mention * Did an error. Readded, uncommented. * Clean up Marking data structure, add tests for Zombie transformation (#42756) * Clean up Marking data structure, add tests for Zombie transformation * empty * AAAAAAAAAAAAAAAA --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> * Fix markings colour setting (#42771) * Fix: Markings Color For Downstream * Comment out A Leg. Remove Todo * Revert to original hidden species code --------- Co-authored-by: pathetic meowmeow <uhhadd@gmail.com> Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com> Co-authored-by: Absotively <jen@jenpollock.ca> Co-authored-by: Quantum-cross <7065792+Quantum-cross@users.noreply.github.com> Co-authored-by: Myra <vasilis@pikachu.systems>
289 lines
9.7 KiB
C#
289 lines
9.7 KiB
C#
using Content.Shared.Body;
|
|
using Content.Shared.DoAfter;
|
|
using Content.Shared.Humanoid;
|
|
using Content.Shared.Humanoid.Markings;
|
|
using Content.Shared.IdentityManagement;
|
|
using Content.Shared.Interaction;
|
|
using Content.Shared.Inventory;
|
|
using Content.Shared.Popups;
|
|
using Content.Shared.Tag;
|
|
using Content.Shared.UserInterface;
|
|
using Robust.Shared.Audio.Systems;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Serialization;
|
|
|
|
namespace Content.Shared.MagicMirror;
|
|
|
|
public sealed class MagicMirrorSystem : EntitySystem
|
|
{
|
|
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
|
[Dependency] private readonly SharedUserInterfaceSystem _userInterface = default!;
|
|
[Dependency] private readonly InventorySystem _inventory = default!;
|
|
[Dependency] private readonly TagSystem _tag = default!;
|
|
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
|
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
|
[Dependency] private readonly SharedVisualBodySystem _visualBody = default!;
|
|
|
|
private static readonly ProtoId<TagPrototype> HidesHairTag = "HidesHair";
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
SubscribeLocalEvent<MagicMirrorComponent, AfterInteractEvent>(OnMagicMirrorInteract);
|
|
SubscribeLocalEvent<MagicMirrorComponent, BeforeActivatableUIOpenEvent>(OnBeforeUIOpen);
|
|
SubscribeLocalEvent<MagicMirrorComponent, ActivatableUIOpenAttemptEvent>(OnAttemptOpenUI);
|
|
|
|
Subs.BuiEvents<MagicMirrorComponent>(MagicMirrorUiKey.Key,
|
|
subs =>
|
|
{
|
|
subs.Event<BoundUIClosedEvent>(OnUiClosed);
|
|
subs.Event<MagicMirrorSelectMessage>(OnMagicMirrorSelect);
|
|
});
|
|
|
|
SubscribeLocalEvent<MagicMirrorComponent, MagicMirrorSelectDoAfterEvent>(OnSelectSlotDoAfter);
|
|
|
|
SubscribeLocalEvent<MagicMirrorComponent, BoundUserInterfaceCheckRangeEvent>(OnMirrorRangeCheck);
|
|
}
|
|
|
|
|
|
private void OnMagicMirrorSelect(Entity<MagicMirrorComponent> ent, ref MagicMirrorSelectMessage args)
|
|
{
|
|
if (ent.Comp.Target is not { } target)
|
|
return;
|
|
|
|
// Check if the target getting their hair altered has any clothes that hides their hair
|
|
if (CheckHeadSlotOrClothes(target))
|
|
{
|
|
_popup.PopupEntity(
|
|
ent.Comp.Target == args.Actor
|
|
? Loc.GetString("magic-mirror-blocked-by-hat-self")
|
|
: Loc.GetString("magic-mirror-blocked-by-hat-self-target", ("target", Identity.Entity(args.Actor, EntityManager))),
|
|
args.Actor,
|
|
args.Actor,
|
|
PopupType.Medium);
|
|
return;
|
|
}
|
|
|
|
if (ent.Comp.DoAfter.HasValue)
|
|
{
|
|
_doAfter.Cancel(target, ent.Comp.DoAfter.Value);
|
|
ent.Comp.DoAfter = null;
|
|
}
|
|
|
|
var doafterTime = ent.Comp.ModifyTime;
|
|
if (ent.Comp.Target == args.Actor)
|
|
doafterTime /= 3;
|
|
|
|
var doAfter = new MagicMirrorSelectDoAfterEvent()
|
|
{
|
|
Markings = args.Markings,
|
|
};
|
|
|
|
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.Actor, doafterTime, doAfter, ent, target: target, used: ent)
|
|
{
|
|
DistanceThreshold = SharedInteractionSystem.InteractionRange,
|
|
BreakOnDamage = true,
|
|
BreakOnMove = true,
|
|
NeedHand = true,
|
|
},
|
|
out var doAfterId);
|
|
|
|
if (target == args.Actor)
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("magic-mirror-change-slot-self"), target, target, PopupType.Medium);
|
|
}
|
|
else
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("magic-mirror-change-slot-target", ("user", Identity.Entity(args.Actor, EntityManager))), target, target, PopupType.Medium);
|
|
}
|
|
|
|
ent.Comp.DoAfter = doAfterId?.Index;
|
|
_audio.PlayPredicted(ent.Comp.ChangeHairSound, ent, args.Actor);
|
|
}
|
|
|
|
private void OnSelectSlotDoAfter(Entity<MagicMirrorComponent> ent, ref MagicMirrorSelectDoAfterEvent args)
|
|
{
|
|
ent.Comp.DoAfter = null;
|
|
|
|
if (args.Handled || args.Target == null || args.Cancelled)
|
|
return;
|
|
|
|
if (ent.Comp.Target != args.Target)
|
|
return;
|
|
|
|
foreach (var (organ, markings) in args.Markings)
|
|
{
|
|
if (!ent.Comp.Organs.Contains(organ))
|
|
{
|
|
args.Markings.Remove(organ);
|
|
continue;
|
|
}
|
|
|
|
foreach (var layer in markings.Keys)
|
|
{
|
|
if (!ent.Comp.Layers.Contains(layer))
|
|
markings.Remove(layer);
|
|
}
|
|
}
|
|
|
|
_visualBody.ApplyMarkings(args.Target.Value, args.Markings);
|
|
}
|
|
|
|
private void OnUiClosed(Entity<MagicMirrorComponent> ent, ref BoundUIClosedEvent args)
|
|
{
|
|
ent.Comp.Target = null;
|
|
Dirty(ent);
|
|
}
|
|
|
|
private void OnMagicMirrorInteract(Entity<MagicMirrorComponent> mirror, ref AfterInteractEvent args)
|
|
{
|
|
if (!args.CanReach || args.Target == null)
|
|
return;
|
|
|
|
UpdateInterface(mirror, args.Target.Value);
|
|
_userInterface.TryOpenUi(mirror.Owner, MagicMirrorUiKey.Key, args.User);
|
|
}
|
|
|
|
private void OnMirrorRangeCheck(EntityUid uid, MagicMirrorComponent component, ref BoundUserInterfaceCheckRangeEvent args)
|
|
{
|
|
if (args.Result == BoundUserInterfaceRangeResult.Fail)
|
|
return;
|
|
|
|
if (component.Target == null || !Exists(component.Target))
|
|
{
|
|
component.Target = null;
|
|
args.Result = BoundUserInterfaceRangeResult.Fail;
|
|
return;
|
|
}
|
|
|
|
if (!_interaction.InRangeUnobstructed(component.Target.Value, uid))
|
|
args.Result = BoundUserInterfaceRangeResult.Fail;
|
|
}
|
|
|
|
private void OnAttemptOpenUI(EntityUid uid, MagicMirrorComponent component, ref ActivatableUIOpenAttemptEvent args)
|
|
{
|
|
var user = component.Target ?? args.User;
|
|
|
|
if (!HasComp<VisualBodyComponent>(user))
|
|
args.Cancel();
|
|
}
|
|
|
|
private void OnBeforeUIOpen(Entity<MagicMirrorComponent> ent, ref BeforeActivatableUIOpenEvent args)
|
|
{
|
|
UpdateInterface(ent, args.User);
|
|
}
|
|
|
|
private void UpdateInterface(Entity<MagicMirrorComponent> ent, EntityUid target)
|
|
{
|
|
if (!_visualBody.TryGatherMarkingsData(target, ent.Comp.Layers, out var profiles, out var markings, out var applied))
|
|
return;
|
|
|
|
ent.Comp.Target = target;
|
|
|
|
foreach (var profile in profiles)
|
|
{
|
|
if (!ent.Comp.Organs.Contains(profile.Key))
|
|
profiles.Remove(profile.Key);
|
|
}
|
|
|
|
foreach (var marking in markings)
|
|
{
|
|
if (!ent.Comp.Organs.Contains(marking.Key))
|
|
{
|
|
profiles.Remove(marking.Key);
|
|
continue;
|
|
}
|
|
|
|
marking.Value.Layers.IntersectWith(ent.Comp.Layers);
|
|
}
|
|
|
|
foreach (var appliedPair in applied)
|
|
{
|
|
if (!ent.Comp.Organs.Contains(appliedPair.Key))
|
|
applied.Remove(appliedPair.Key);
|
|
}
|
|
|
|
// TODO: Component states
|
|
var state = new MagicMirrorUiState(profiles, markings, applied);
|
|
_userInterface.SetUiState(ent.Owner, MagicMirrorUiKey.Key, state);
|
|
|
|
Dirty(ent);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Helper function that checks if the wearer has anything on their head
|
|
/// Or if they have any clothes that hides their hair
|
|
/// </summary>
|
|
private bool CheckHeadSlotOrClothes(EntityUid target)
|
|
{
|
|
if (!TryComp<InventoryComponent>(target, out var inventoryComp))
|
|
return false;
|
|
|
|
// any hat whatsoever will block haircutting
|
|
if (_inventory.TryGetSlotEntity(target, "head", out _, inventoryComp))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// maybe there's some kind of armor that has the HidesHair tag as well, so check every slot for it
|
|
var slots = _inventory.GetSlotEnumerator((target, inventoryComp), SlotFlags.WITHOUT_POCKET);
|
|
while (slots.MoveNext(out var slot))
|
|
{
|
|
if (slot.ContainedEntity != null && _tag.HasTag(slot.ContainedEntity.Value, HidesHairTag))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
[Serializable, NetSerializable]
|
|
public enum MagicMirrorUiKey : byte
|
|
{
|
|
Key
|
|
}
|
|
|
|
[Serializable, NetSerializable]
|
|
public sealed class MagicMirrorSelectMessage : BoundUserInterfaceMessage
|
|
{
|
|
public MagicMirrorSelectMessage(Dictionary<ProtoId<OrganCategoryPrototype>, Dictionary<HumanoidVisualLayers, List<Marking>>> markings)
|
|
{
|
|
Markings = markings;
|
|
}
|
|
|
|
public Dictionary<ProtoId<OrganCategoryPrototype>, Dictionary<HumanoidVisualLayers, List<Marking>>> Markings { get; }
|
|
}
|
|
|
|
|
|
[Serializable, NetSerializable]
|
|
public sealed class MagicMirrorUiState : BoundUserInterfaceState
|
|
{
|
|
public MagicMirrorUiState(Dictionary<ProtoId<OrganCategoryPrototype>, OrganProfileData> profiles,
|
|
Dictionary<ProtoId<OrganCategoryPrototype>, OrganMarkingData> markings,
|
|
Dictionary<ProtoId<OrganCategoryPrototype>, Dictionary<HumanoidVisualLayers, List<Marking>>> applied)
|
|
{
|
|
OrganProfileData = profiles;
|
|
OrganMarkingData = markings;
|
|
AppliedMarkings = applied;
|
|
}
|
|
|
|
public NetEntity Target;
|
|
|
|
public Dictionary<ProtoId<OrganCategoryPrototype>, OrganProfileData> OrganProfileData;
|
|
public Dictionary<ProtoId<OrganCategoryPrototype>, OrganMarkingData> OrganMarkingData;
|
|
public Dictionary<ProtoId<OrganCategoryPrototype>, Dictionary<HumanoidVisualLayers, List<Marking>>> AppliedMarkings;
|
|
}
|
|
|
|
[Serializable, NetSerializable]
|
|
public sealed partial class MagicMirrorSelectDoAfterEvent : DoAfterEvent
|
|
{
|
|
public Dictionary<ProtoId<OrganCategoryPrototype>, Dictionary<HumanoidVisualLayers, List<Marking>>> Markings;
|
|
|
|
public override DoAfterEvent Clone() => this;
|
|
}
|