-
Notifications
You must be signed in to change notification settings - Fork 166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Lamia & Segmented Entity System #11
base: master
Are you sure you want to change the base?
Changes from all commits
356edec
026cd3b
c647da9
30aa81d
6ed9df6
950b7b3
c2f4756
89a311e
9234a36
05edd44
9b29ee0
294edd3
e166895
c5bc6e8
2f67851
00a826c
14ebfad
53f50dd
91641b9
2ec632a
bc8e48c
94ea027
878b155
9a25681
27015d4
ab78fcd
c359dab
4a81c2a
5b4bf6e
f368ad9
45505f6
b0b5ef7
360c745
2198c31
ce8efc6
32499ec
c8b33fb
c97e000
adb5e87
b9e800d
f483415
479ee18
c5026c2
7c0d258
467f520
03d75cb
780deb8
238cb53
214c934
221991f
389b01a
c01fdd3
a8ad70d
2dd9d3f
85c83e4
6187158
5357f2f
8d7cde3
48ae4d7
c947677
5284df0
6192070
202ab28
de23fec
84edee0
640ac85
9b135ee
4046e6c
41eeba0
c630d63
933fc2a
96190fa
05d5581
98e1304
5e14210
b5a4fcc
501879e
c56af58
eba52fb
2efba19
2e46568
463f2a1
f81ca02
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,184 @@ | ||||
/* | ||||
* This file is licensed under AGPLv3 | ||||
* Copyright (c) 2024 Rane | ||||
* See AGPLv3.txt for details. | ||||
*/ | ||||
|
||||
using Content.Shared.SegmentedEntity; | ||||
using Content.Shared.Humanoid; | ||||
using Content.Shared.Humanoid.Markings; | ||||
using Content.Client.Resources; | ||||
using Robust.Client.ResourceManagement; | ||||
using Robust.Client.Graphics; | ||||
using Robust.Shared.Enums; | ||||
using System.Numerics; | ||||
using System.Linq; | ||||
|
||||
|
||||
Comment on lines
+16
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||
namespace Content.Client.DeltaV.Lamiae; | ||||
|
||||
/// <summary> | ||||
/// This draws lamia segments directly from polygons instead of sprites. This is a very novel approach as of the time this is being written (August 2024) but it wouldn't surprise me | ||||
/// if there's a better way to do this at some point. Currently we have a very heavy restriction on the tools we can make, forcing me to make several helpers that may be redundant later. | ||||
/// This will be overcommented because I know you haven't seen code like this before and you might want to copy it. | ||||
/// This is an expansion on some techniques I discovered in (https://github.com/Elijahrane/Delta-v/blob/49d76c437740eab79fc622ab50d628b926e6ddcb/Content.Client/DeltaV/Arcade/S3D/Renderer/S3DRenderer.cs) | ||||
/// </summary> | ||||
public sealed class SnakeOverlay : Overlay | ||||
{ | ||||
private readonly IResourceCache _resourceCache; | ||||
private readonly IEntityManager _entManager; | ||||
private readonly SharedTransformSystem _transform; | ||||
private readonly SharedHumanoidAppearanceSystem _humanoid = default!; | ||||
|
||||
// Look through these carefully. WorldSpace is useful for debugging. Note that this defaults to "screen space" which breaks when you try and get the world handle. | ||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities; | ||||
|
||||
// Overlays are strange and you need this pattern where you define readonly deps above, and then make a constructor with this pattern. Anything that creates this overlay will then | ||||
// have to provide all the deps. | ||||
public SnakeOverlay(IEntityManager entManager, IResourceCache resourceCache) | ||||
{ | ||||
_resourceCache = resourceCache; | ||||
// we get ent manager from SnakeOverlaySystem turning this on and passing it | ||||
_entManager = entManager; | ||||
// with ent manager we can fetch our other entity systems | ||||
_transform = _entManager.EntitySysManager.GetEntitySystem<SharedTransformSystem>(); | ||||
_humanoid = _entManager.EntitySysManager.GetEntitySystem<SharedHumanoidAppearanceSystem>(); | ||||
|
||||
// draw at drawdepth 3 | ||||
ZIndex = 3; | ||||
} | ||||
|
||||
// This step occurs each frame. For some overlays you may want to conisder limiting how often they update, but for player entities that move around fast we'll just do it every frame. | ||||
protected override void Draw(in OverlayDrawArgs args) | ||||
{ | ||||
// load the handle, the "pen" we draw with | ||||
var handle = args.WorldHandle; | ||||
|
||||
// Get all lamiae the client knows of and their transform in a way we can enumerate over | ||||
var enumerator = _entManager.AllEntityQueryEnumerator<SegmentedEntityComponent, TransformComponent>(); | ||||
|
||||
// I go over the collection above, pulling out an EntityUid and the two components I need for each. | ||||
while (enumerator.MoveNext(out var uid, out var lamia, out var xform)) | ||||
{ | ||||
// Skip ones that are off-map. "Map" in this context means interconnected stuff you can travel between by moving, rather than needing e.g. FTL to load a new map. | ||||
if (xform.MapID != args.MapId) | ||||
continue; | ||||
|
||||
// Skip ones where they are not loaded properly, uninitialized, or w/e | ||||
if (lamia.Segments.Count < lamia.NumberOfSegments) | ||||
{ | ||||
_entManager.Dirty(uid, lamia); // pls give me an update... | ||||
continue; | ||||
} | ||||
|
||||
// By the way, there's a hack to mitigate overdraw somewhat. Check out whatever is going on with the variable called "bounds" in DoAfterOverlay. | ||||
// I won't do it here because (1) it's ugly and (2) theoretically these entities can be fucking huge and you'll see the tail end of them when they are way off screen. | ||||
// On a PVS level I think segmented entities should be all-or-nothing when it comes to PVS range, that is you either load all of their segments or none. | ||||
|
||||
// Color.White is drawing without modifying color. For clothed tails, we should use White. For skin, we should use the color of the marking. | ||||
// TODO: Better way to cache this | ||||
if (_entManager.TryGetComponent<HumanoidAppearanceComponent>(uid, out var humanoid)) | ||||
{ | ||||
if (humanoid.MarkingSet.TryGetCategory(MarkingCategories.Tail, out var tailMarkings)) | ||||
{ | ||||
var col = tailMarkings.First().MarkingColors.First(); | ||||
DrawLamia(handle, lamia, col); | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
DrawLamia(handle, lamia, Color.White); | ||||
} | ||||
} | ||||
} | ||||
|
||||
// This is where we do the actual drawing. | ||||
private void DrawLamia(DrawingHandleWorld handle, SegmentedEntityComponent lamia, Color color) | ||||
{ | ||||
// We're going to store all our verticies in here and then draw them | ||||
List<DrawVertexUV2D> verts = new List<DrawVertexUV2D>(); | ||||
|
||||
// Radius of the initial segment | ||||
float radius = lamia.InitialRadius; | ||||
|
||||
// We're storing the left and right verticies of the last segment so we can start drawing from there without gaps | ||||
Vector2? lastPtCW = null; | ||||
Vector2? lastPtCCW = null; | ||||
|
||||
var tex = _resourceCache.GetTexture(lamia.TexturePath); | ||||
|
||||
int i = 1; | ||||
// do each segment except the last one normally | ||||
while (i < lamia.Segments.Count - 1) | ||||
{ | ||||
// get centerpoints of last segment and this one | ||||
var origin = _transform.GetWorldPosition(_entManager.GetEntity(lamia.Segments[i - 1])); | ||||
var destination = _transform.GetWorldPosition(_entManager.GetEntity(lamia.Segments[i])); | ||||
|
||||
// get direction between the two points and normalize it | ||||
var connectorVec = destination - origin; | ||||
connectorVec = connectorVec.Normalized(); | ||||
|
||||
//get one rotated 90 degrees clockwise | ||||
var offsetVecCW = new Vector2(connectorVec.Y, 0 - connectorVec.X); | ||||
|
||||
//and counterclockwise | ||||
var offsetVecCCW = new Vector2(0 - connectorVec.Y, connectorVec.X); | ||||
|
||||
/// tri 1: line across first segment and corner of second | ||||
if (lastPtCW == null) | ||||
{ | ||||
verts.Add(new DrawVertexUV2D(origin + offsetVecCW * radius, Vector2.Zero)); | ||||
} | ||||
else | ||||
{ | ||||
verts.Add(new DrawVertexUV2D((Vector2) lastPtCW, Vector2.Zero)); | ||||
} | ||||
|
||||
if (lastPtCCW == null) | ||||
{ | ||||
verts.Add(new DrawVertexUV2D(origin + offsetVecCCW * radius, new Vector2(1, 0))); | ||||
} | ||||
else | ||||
{ | ||||
verts.Add(new DrawVertexUV2D((Vector2) lastPtCCW, new Vector2(1, 0))); | ||||
} | ||||
|
||||
verts.Add(new DrawVertexUV2D(destination + offsetVecCW * radius, new Vector2(0, 1))); | ||||
|
||||
// tri 2: line across second segment and corner of first | ||||
if (lastPtCCW == null) | ||||
{ | ||||
verts.Add(new DrawVertexUV2D(origin + offsetVecCCW * radius, new Vector2(1, 0))); | ||||
} | ||||
else | ||||
{ | ||||
verts.Add(new DrawVertexUV2D((Vector2) lastPtCCW, new Vector2(1, 0))); | ||||
} | ||||
|
||||
lastPtCW = destination + offsetVecCW * radius; | ||||
verts.Add(new DrawVertexUV2D((Vector2) lastPtCW, new Vector2(0, 1))); | ||||
lastPtCCW = destination + offsetVecCCW * radius; | ||||
verts.Add(new DrawVertexUV2D((Vector2) lastPtCCW, new Vector2(1, 1))); | ||||
|
||||
// slim down a bit for next segment | ||||
radius *= lamia.SlimFactor; | ||||
|
||||
i++; | ||||
} | ||||
|
||||
// draw tail (1 tri) | ||||
if (lastPtCW != null && lastPtCCW != null) | ||||
{ | ||||
verts.Add(new DrawVertexUV2D((Vector2) lastPtCW, new Vector2(0, 0))); | ||||
verts.Add(new DrawVertexUV2D((Vector2) lastPtCCW, new Vector2(1, 0))); | ||||
|
||||
var destination = _transform.GetWorldPosition(_entManager.GetEntity(lamia.Segments.Last())); | ||||
|
||||
verts.Add(new DrawVertexUV2D(destination, new Vector2(0.5f, 1f))); | ||||
} | ||||
|
||||
// Draw all of the triangles we just pit in at once | ||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, texture: tex, verts.ToArray().AsSpan(), color); | ||||
} | ||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/* | ||
* This file is licensed under AGPLv3 | ||
* Copyright (c) 2024 Rane | ||
* See AGPLv3.txt for details. | ||
*/ | ||
|
||
Comment on lines
+1
to
+6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. eh |
||
using Robust.Client.Graphics; | ||
using Robust.Client.ResourceManagement; | ||
|
||
namespace Content.Client.DeltaV.Lamiae; | ||
|
||
/// <summary> | ||
/// This system turns on our always-on overlay. I have no opinion on this design pattern or the existence of this file. | ||
/// It also fetches the deps it needs. | ||
/// </summary> | ||
public sealed class SnakeOverlaySystem : EntitySystem | ||
{ | ||
[Dependency] private readonly IOverlayManager _overlay = default!; | ||
[Dependency] private readonly IResourceCache _resourceCache = default!; | ||
|
||
public override void Initialize() | ||
{ | ||
base.Initialize(); | ||
_overlay.AddOverlay(new SnakeOverlay(EntityManager, _resourceCache)); | ||
} | ||
|
||
public override void Shutdown() | ||
{ | ||
base.Shutdown(); | ||
_overlay.RemoveOverlay<SnakeOverlay>(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,10 @@ | ||||||||||||
namespace Content.Shared.SegmentedEntity; | ||||||||||||
public sealed class SegmentSpawnedEvent : EntityEventArgs | ||||||||||||
Comment on lines
+1
to
+2
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
{ | ||||||||||||
public EntityUid Lamia = default!; | ||||||||||||
|
||||||||||||
public SegmentSpawnedEvent(EntityUid lamia) | ||||||||||||
{ | ||||||||||||
Lamia = lamia; | ||||||||||||
} | ||||||||||||
} |
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,108 @@ | ||||||||||||
/* | ||||||||||||
* Delta-V - This file is licensed under AGPLv3 | ||||||||||||
* Copyright (c) 2024 Delta-V Contributors | ||||||||||||
* See AGPLv3.txt for details. | ||||||||||||
*/ | ||||||||||||
|
||||||||||||
Comment on lines
+1
to
+6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
using Robust.Shared.GameStates; | ||||||||||||
using Robust.Shared.Serialization; | ||||||||||||
using Robust.Shared.Utility; | ||||||||||||
|
||||||||||||
namespace Content.Shared.SegmentedEntity | ||||||||||||
{ | ||||||||||||
Comment on lines
+11
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Turn it into file scoped namespace |
||||||||||||
/// <summary> | ||||||||||||
/// Controls initialization of any Multi-segmented entity | ||||||||||||
/// </summary> | ||||||||||||
[RegisterComponent, NetworkedComponent] | ||||||||||||
[AutoGenerateComponentState] | ||||||||||||
public sealed partial class SegmentedEntityComponent : Component | ||||||||||||
{ | ||||||||||||
/// <summary> | ||||||||||||
/// A list of each UID attached to the Lamia, in order of spawn | ||||||||||||
/// </summary> | ||||||||||||
[DataField("segments")] | ||||||||||||
[AutoNetworkedField] | ||||||||||||
Comment on lines
+23
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
public List<NetEntity> Segments = new(); | ||||||||||||
|
||||||||||||
/// <summary> | ||||||||||||
/// A clamped variable that represents the number of segments to be spawned | ||||||||||||
/// </summary> | ||||||||||||
[DataField("numberOfSegments")] | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
public int NumberOfSegments = 18; | ||||||||||||
|
||||||||||||
/// <summary> | ||||||||||||
/// How wide the initial segment should be. | ||||||||||||
/// </summary> | ||||||||||||
[DataField("initialRadius")] | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
public float InitialRadius = 0.3f; | ||||||||||||
|
||||||||||||
/// <summary> | ||||||||||||
/// Texture of the segment. | ||||||||||||
/// </summary> | ||||||||||||
[DataField("texturePath", required: true)] | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
public string TexturePath; | ||||||||||||
|
||||||||||||
/// <summary> | ||||||||||||
/// If UseTaperSystem is true, this constant represents the rate at which a segmented entity will taper towards the tip. Tapering is on a logarithmic scale, and will asymptotically approach 0. | ||||||||||||
/// </summary> | ||||||||||||
[DataField("offsetConstant")] | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
public float OffsetConstant = 1.03f; | ||||||||||||
|
||||||||||||
/// <summary> | ||||||||||||
/// Represents the prototype used to parent all segments | ||||||||||||
/// </summary> | ||||||||||||
[DataField("initialSegmentId")] | ||||||||||||
public string InitialSegmentId = "LamiaInitialSegment"; | ||||||||||||
Comment on lines
+54
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
/// <summary> | ||||||||||||
/// Represents the segment prototype to be spawned | ||||||||||||
/// </summary> | ||||||||||||
[DataField("segmentId")] | ||||||||||||
public string SegmentId = "LamiaSegment"; | ||||||||||||
Comment on lines
+60
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
/// <summary> | ||||||||||||
/// How much to slim each successive segment. | ||||||||||||
/// </summary> | ||||||||||||
[DataField("slimFactor")] | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
public float SlimFactor = 0.93f; | ||||||||||||
|
||||||||||||
/// <summary> | ||||||||||||
/// Set to 1f for constant width | ||||||||||||
/// </summary> | ||||||||||||
[DataField("useTaperSystem")] | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
public bool UseTaperSystem = true; | ||||||||||||
|
||||||||||||
/// <summary> | ||||||||||||
/// The standard distance between the centerpoint of each segment. | ||||||||||||
/// </summary> | ||||||||||||
[DataField("staticOffset")] | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
public float StaticOffset = 0.15f; | ||||||||||||
|
||||||||||||
/// <summary> | ||||||||||||
/// The standard sprite scale of each segment. | ||||||||||||
/// </summary> | ||||||||||||
[DataField("staticScale")] | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
public float StaticScale = 1f; | ||||||||||||
|
||||||||||||
/// <summary> | ||||||||||||
/// Used to more finely tune how much damage should be transfered from tail to body. | ||||||||||||
/// </summary> | ||||||||||||
[DataField("damageModifierOffset")] | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
public float DamageModifierOffset = 0.4f; | ||||||||||||
|
||||||||||||
/// <summary> | ||||||||||||
/// A clamped variable that represents how far from the tip should tapering begin. | ||||||||||||
/// </summary> | ||||||||||||
[DataField("taperOffset")] | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
public int TaperOffset = 18; | ||||||||||||
|
||||||||||||
/// <summary> | ||||||||||||
/// Coefficient used to finely tune how much explosion damage should be transfered to the body. This is calculated multiplicatively with the derived damage modifier set. | ||||||||||||
/// </summary> | ||||||||||||
[DataField("explosiveModifierOffset")] | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
public float ExplosiveModifierOffset = 0.1f; | ||||||||||||
|
||||||||||||
[DataField("bulletPassover")] | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
public bool BulletPassover = true; | ||||||||||||
} | ||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eh