Skip to content

Commit

Permalink
Merge branch 'frontier-guns' of https://github.com/sleepyyapril/TheDen
Browse files Browse the repository at this point in the history
…into frontier-guns
  • Loading branch information
sleepyyapril committed Jan 5, 2025
2 parents a8ee353 + 4b2c93a commit 320d39b
Show file tree
Hide file tree
Showing 87 changed files with 528 additions and 110 deletions.
89 changes: 89 additions & 0 deletions Content.Client/Floofstation/Leash/LeashVisualsOverlay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System.Numerics;
using Content.Shared.Floofstation.Leash.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;

namespace Content.Client.Floofstation.Leash;

public sealed class LeashVisualsOverlay : Overlay
{
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;

private readonly IEntityManager _entMan;
private readonly SpriteSystem _sprites;
private readonly SharedTransformSystem _xform;
private readonly EntityQuery<TransformComponent> _xformQuery;
private readonly EntityQuery<SpriteComponent> _spriteQuery;

public LeashVisualsOverlay(IEntityManager entMan)
{
_entMan = entMan;
_sprites = _entMan.System<SpriteSystem>();
_xform = _entMan.System<SharedTransformSystem>();
_xformQuery = _entMan.GetEntityQuery<TransformComponent>();
_spriteQuery = _entMan.GetEntityQuery<SpriteComponent>();
}

protected override void Draw(in OverlayDrawArgs args)
{
var worldHandle = args.WorldHandle;
worldHandle.SetTransform(Vector2.Zero, Angle.Zero);

var query = _entMan.EntityQueryEnumerator<LeashedVisualsComponent>();
while (query.MoveNext(out var visualsComp))
{
if (visualsComp.Source is not {Valid: true} source
|| visualsComp.Target is not {Valid: true} target
|| !_xformQuery.TryGetComponent(source, out var xformComp)
|| !_xformQuery.TryGetComponent(target, out var otherXformComp)
|| xformComp.MapID != args.MapId
|| otherXformComp.MapID != xformComp.MapID)
continue;

var texture = _sprites.Frame0(visualsComp.Sprite);
var width = texture.Width / (float) EyeManager.PixelsPerMeter;

var coordsA = xformComp.Coordinates;
var coordsB = otherXformComp.Coordinates;

// If both coordinates are in the same spot (e.g. the leash is being held by the leashed), don't render anything
if (coordsA.TryDistance(_entMan, _xform, coordsB, out var dist) && dist < 0.01f)
continue;

var rotA = xformComp.LocalRotation;
var rotB = otherXformComp.LocalRotation;
var offsetA = visualsComp.OffsetSource;
var offsetB = visualsComp.OffsetTarget;

// Sprite rotation is the rotation along the Z axis
// Which is different from transform rotation for all mobs that are seen from the side (instead of top-down)
if (_spriteQuery.TryGetComponent(source, out var spriteA))
{
offsetA *= spriteA.Scale;
rotA = spriteA.Rotation;
}
if (_spriteQuery.TryGetComponent(target, out var spriteB))
{
offsetB *= spriteB.Scale;
rotB = spriteB.Rotation;
}

coordsA = coordsA.Offset(rotA.RotateVec(offsetA));
coordsB = coordsB.Offset(rotB.RotateVec(offsetB));

var posA = _xform.ToMapCoordinates(coordsA).Position;
var posB = _xform.ToMapCoordinates(coordsB).Position;
var diff = (posB - posA);
var length = diff.Length();

// So basically, we find the midpoint, then create a box that describes the sprite boundaries, then rotate it
var midPoint = diff / 2f + posA;
var angle = (posB - posA).ToWorldAngle();
var box = new Box2(-width / 2f, -length / 2f, width / 2f, length / 2f);
var rotate = new Box2Rotated(box.Translated(midPoint), angle, midPoint);

worldHandle.DrawTextureRect(texture, rotate);
}
}
}
21 changes: 21 additions & 0 deletions Content.Client/Floofstation/Leash/LeashVisualsSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Content.Client.Physics;
using Robust.Client.Graphics;

namespace Content.Client.Floofstation.Leash;

public sealed class LeashVisualsSystem : EntitySystem
{
[Dependency] private readonly IOverlayManager _overlay = default!;

public override void Initialize()
{
base.Initialize();
_overlay.AddOverlay(new LeashVisualsOverlay(EntityManager));
}

public override void Shutdown()
{
base.Shutdown();
_overlay.RemoveOverlay<LeashVisualsOverlay>();
}
}
3 changes: 0 additions & 3 deletions Content.Server/Shadowkin/ShadowkinSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,6 @@ private void OnRejuvenate(EntityUid uid, ShadowkinComponent component, Rejuvenat
magic.ManaGain = 0.25f;
magic.MindbreakingFeedback = "shadowkin-blackeye";
magic.NoMana = "shadowkin-tired";

if (_prototypeManager.TryIndex<PsionicPowerPrototype>("ShadowkinPowers", out var shadowkinPowers))
_psionicAbilitiesSystem.InitializePsionicPower(uid, shadowkinPowers);
}

// FloofStation Edit
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Numerics;

namespace Content.Shared.Floofstation.Leash.Components;

/// <summary>
Expand All @@ -6,4 +8,9 @@ namespace Content.Shared.Floofstation.Leash.Components;
[RegisterComponent]
public sealed partial class LeashAnchorComponent : Component
{
/// <summary>
/// The visual offset of the "anchor point".
/// </summary>
[DataField]
public Vector2 Offset = Vector2.Zero;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ public sealed partial class LeashComponent : Component
[DataField, AutoNetworkedField]
public float Length = 3.5f;

/// <summary>
/// List of possible lengths this leash may be assigned to be the user. If null, the length cannot be changed.
/// </summary>
[DataField, AutoNetworkedField]
public float[]? LengthConfigs;

/// <summary>
/// Maximum distance between the anchor and the puller beyond which the leash will break.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Numerics;
using Robust.Shared.GameStates;
using Robust.Shared.Utility;

namespace Content.Shared.Floofstation.Leash.Components;

/// <summary>
/// Draws a line between this entity and the target. Same as JointVisualsComponent.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class LeashedVisualsComponent : Component
{
[DataField(required: true), AutoNetworkedField]
public SpriteSpecifier Sprite = default!;

[DataField, AutoNetworkedField]
public EntityUid Source, Target;

[DataField, AutoNetworkedField]
public Vector2 OffsetSource, OffsetTarget;
}
114 changes: 83 additions & 31 deletions Content.Shared/Floofstation/Leash/LeashSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public sealed class LeashSystem : EntitySystem
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly SharedTransformSystem _xform = default!;

public static VerbCategory LeashLengthConfigurationCategory =
new("verb-categories-leash-config", "/Textures/Floof/Interface/VerbIcons/resize.svg.192dpi.png");

#region Lifecycle

public override void Initialize()
{
UpdatesBefore.Add(typeof(SharedPhysicsSystem));
Expand All @@ -47,6 +52,7 @@ public override void Initialize()

SubscribeLocalEvent<LeashComponent, EntGotInsertedIntoContainerMessage>(OnLeashInserted);
SubscribeLocalEvent<LeashComponent, EntGotRemovedFromContainerMessage>(OnLeashRemoved);
SubscribeLocalEvent<LeashComponent, GetVerbsEvent<AlternativeVerb>>(OnGetLeashVerbs);

SubscribeLocalEvent<LeashAnchorComponent, LeashAttachDoAfterEvent>(OnAttachDoAfter);
SubscribeLocalEvent<LeashedComponent, LeashDetachDoAfterEvent>(OnDetachDoAfter);
Expand All @@ -69,37 +75,50 @@ public override void Update(float frameTime)
while (leashQuery.MoveNext(out var leashEnt, out var leash, out var physics))
{
var sourceXForm = Transform(leashEnt);

foreach (var data in leash.Leashed.ToList())
{
if (data.Pulled == NetEntity.Invalid || !TryGetEntity(data.Pulled, out var target))
continue;

// Client side only: set max distance to infinity to prevent the client from ever predicting leashes.
if (_net.IsClient
&& data.JointId is not null
&& TryComp<JointComponent>(target, out var jointComp)
&& jointComp.GetJoints.TryGetValue(data.JointId, out var joint)
&& joint is DistanceJoint distanceJoint
)
distanceJoint.MaxLength = float.MaxValue;

if (_net.IsClient)
continue;

// Break each leash joint whose entities are on different maps or are too far apart
var targetXForm = Transform(target.Value);
if (targetXForm.MapUid != sourceXForm.MapUid
|| !sourceXForm.Coordinates.TryDistance(EntityManager, targetXForm.Coordinates, out var dst)
|| dst > leash.MaxDistance
)
RemoveLeash(target.Value, (leashEnt, leash));
}
UpdateLeash(data, sourceXForm, leash, leashEnt);
}

leashQuery.Dispose();
}

private void UpdateLeash(LeashComponent.LeashData data, TransformComponent sourceXForm, LeashComponent leash, EntityUid leashEnt)
{
if (data.Pulled == NetEntity.Invalid || !TryGetEntity(data.Pulled, out var target))
return;

DistanceJoint? joint = null;
if (data.JointId is not null
&& TryComp<JointComponent>(target, out var jointComp)
&& jointComp.GetJoints.TryGetValue(data.JointId, out var _joint)
)
joint = _joint as DistanceJoint;

// Client: set max distance to infinity to prevent the client from ever predicting leashes.
if (_net.IsClient)
{
if (joint is not null)
joint.MaxLength = float.MaxValue;

return;
}

// Server: break each leash joint whose entities are on different maps or are too far apart
var targetXForm = Transform(target.Value);
if (targetXForm.MapUid != sourceXForm.MapUid
|| !sourceXForm.Coordinates.TryDistance(EntityManager, targetXForm.Coordinates, out var dst)
|| dst > leash.MaxDistance
)
RemoveLeash(target.Value, (leashEnt, leash));

// Server: update leash lengths if necessary/possible
// The length can be increased freely, but can only be decreased if the pulled entity is close enough
if (joint is not null && (leash.Length >= joint.MaxLength || leash.Length >= joint.Length))
joint.MaxLength = leash.Length;
}

#endregion

#region event handling

private void OnAnchorUnequipping(Entity<LeashAnchorComponent> ent, ref BeingUnequippedAttemptEvent args)
Expand All @@ -108,7 +127,7 @@ private void OnAnchorUnequipping(Entity<LeashAnchorComponent> ent, ref BeingUneq
if (TryGetLeashTarget(args.Equipment, out var leashTarget)
&& TryComp<LeashedComponent>(leashTarget, out var leashed)
&& leashed.Puller is not null
)
)
args.Cancel();
}

Expand All @@ -130,13 +149,14 @@ private void OnGetEquipmentVerbs(Entity<LeashAnchorComponent> ent, ref GetVerbsE
leashVerb.Message = Loc.GetString("verb-leash-error-message");
leashVerb.Disabled = true;
}

args.Verbs.Add(leashVerb);


if (!TryGetLeashTarget(ent!, out var leashTarget)
|| !TryComp<LeashedComponent>(leashTarget, out var leashedComp)
|| leashedComp.Puller != leash
|| HasComp<LeashedComponent>(leashTarget)) // This one means that OnGetLeashedVerbs will add a verb to remove it
|| HasComp<LeashedComponent>(ent)) // This one means that OnGetLeashedVerbs will add a verb to remove it
return;

var unleashVerb = new EquipmentVerb
Expand All @@ -163,6 +183,23 @@ private void OnGetLeashedVerbs(Entity<LeashedComponent> ent, ref GetVerbsEvent<I
});
}

private void OnGetLeashVerbs(Entity<LeashComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
if (ent.Comp.LengthConfigs is not { } configurations)
return;

// Add a menu listing each length configuration
foreach (var length in configurations)
{
args.Verbs.Add(new AlternativeVerb
{
Text = Loc.GetString("verb-leash-set-length-text", ("length", length)),
Act = () => SetLeashLength(ent, length),
Category = LeashLengthConfigurationCategory
});
}
}

private void OnJointRemoved(Entity<LeashedComponent> ent, ref JointRemovedEvent args)
{
var id = args.Joint.ID;
Expand Down Expand Up @@ -301,11 +338,15 @@ public bool CanCreateJoint(EntityUid a, EntityUid b)
private DistanceJoint CreateLeashJoint(string jointId, Entity<LeashComponent> leash, EntityUid leashTarget)
{
var joint = _joints.CreateDistanceJoint(leash, leashTarget, id: jointId);
// If the soon-to-be-leashed entity is too far away, we don't force it any closer.
// The system will automatically reduce the length of the leash once it gets closer.
var length = Transform(leashTarget).Coordinates.TryDistance(EntityManager, Transform(leash).Coordinates, out var dist)
? MathF.Max(dist, leash.Comp.Length)
: leash.Comp.Length;

joint.CollideConnected = false;
joint.Length = leash.Comp.Length;
joint.MinLength = 0f;
joint.MaxLength = leash.Comp.Length;
joint.MaxLength = length;
joint.Stiffness = 1f;
joint.CollideConnected = true; // This is just for performance reasons and doesn't actually make mobs collide.
joint.Damping = 1f;
Expand Down Expand Up @@ -423,9 +464,11 @@ public void DoLeash(Entity<LeashAnchorComponent> anchor, Entity<LeashComponent>
_container.EnsureContainer<ContainerSlot>(leashTarget, LeashedComponent.VisualsContainerName);
if (EntityManager.TrySpawnInContainer(null, leashTarget, LeashedComponent.VisualsContainerName, out var visualEntity))
{
var visualComp = EnsureComp<JointVisualsComponent>(visualEntity.Value);
var visualComp = EnsureComp<LeashedVisualsComponent>(visualEntity.Value);
visualComp.Sprite = sprite;
visualComp.Target = leash;
visualComp.Source = leash;
visualComp.Target = leashTarget;
visualComp.OffsetTarget = anchor.Comp.Offset;

data.LeashVisuals = GetNetEntity(visualEntity);
}
Expand Down Expand Up @@ -459,6 +502,15 @@ public void RemoveLeash(Entity<LeashedComponent?> leashed, Entity<LeashComponent
Dirty(leash);
}

/// <summary>
/// Sets the desired length of the leash. The actual length will be updated on the next physics tick.
/// </summary>
public void SetLeashLength(Entity<LeashComponent> leash, float length)
{
leash.Comp.Length = length;
RefreshJoints(leash);
}

/// <summary>
/// Refreshes all joints for the specified leash.
/// This will remove all obsolete joints, such as those for which CanCreateJoint returns false,
Expand Down
5 changes: 4 additions & 1 deletion Content.Shared/Roles/StartingGearPrototype.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
using Content.Shared.Preferences;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
Expand Down Expand Up @@ -47,10 +48,12 @@ public string GetGear(string slot, HumanoidCharacterProfile? profile)
{
if (profile != null)
{
var forceSkirt = new[] { "Harpy", "Lamia" };

switch (slot)
{
case "jumpsuit" when profile.Clothing == ClothingPreference.Jumpskirt && !string.IsNullOrEmpty(InnerClothingSkirt):
case "jumpsuit" when profile.Species == "Harpy" && !string.IsNullOrEmpty(InnerClothingSkirt):
case "jumpsuit" when forceSkirt.Contains(profile.Species) && !string.IsNullOrEmpty(InnerClothingSkirt):
return InnerClothingSkirt;
case "back" when profile.Backpack == BackpackPreference.Satchel && !string.IsNullOrEmpty(Satchel):
return Satchel;
Expand Down
Loading

0 comments on commit 320d39b

Please sign in to comment.