mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-17 05:27:38 +03:00
## About the PR This is a draft PR for an upcoming new playable species, the Lamia. Lamia are an older species from the 2022 era of Nyanotrasen, and were previously abandoned code that was dropped from the game on October 13th, 2022. I was able to locate what I believe to be the last remaining branch containing Lamia, from a defunct server repository that ran an October 12th, 2022 build of Nyanotrasen. Thus I began a project to refurbish this code for use on modern SS14. The Lamia I wish to PR are only recognizable from the original Lamia in that they share the Tail Segment System. That is, they prominently feature a completely unique mechanic whereby their body is composed of multiple entities linked together in a chain. The original version of this code had a great many bugs related to the game's physics system, and it was severely limited by the Robust Toolbox engine at the time. In the year since Lamia's abandonment, Robust Toolbox has gone through many iterations and improvements, and has deprecated large parts of the physics system that old Lamia utilized. ## Redesigns from the original 2022 Lamia, AKA Nyanotrasen Lamia. Vs. DeltaV Lamia The original Nyanotrasen Lamia were made with several limitations in mind. Their size was heavily restricted by the physics engine at the time, we aren't beholden to that same limitation anymore. Thus DeltaV Lamia are vastly larger than the Nyanotrasen Lamia, featuring a tail that is 5 tiles in length, with fully functional physics collisions. They were also not able to wear Hardsuits due to limitations of the SpriteComponent, and thus were instead designed around having a "Barotrauma resistance". On DeltaV code, we can arbitrarily state that species use different optional sprites for items, therefore its possible to have for example a Nukie Hardsuit, with its equipped-outerwear state, equipped-outerwear-lamia, equipped-outerwear-lamiainitialsegment, equipped-outerwear-lamiasegment, and so on. The Lamia Segments can simply state that if they equip a hardsuit, they utilize the -lamiasegment sprite option. Therefore its no longer necessary to create an entire new item solely so that snakes can wear a hardsuit. **Positive Traits** - Extreme Size. Lamia are 5 tiles long, and weigh as much as a car. They make a mockery of mass contests, and they can push physics objects around simply by slithering into them. - Significantly larger health bar. A Lamia is put into critical condition at 200 damage, and dies at 300 damage. This is offset by the Lamia having a certain percentage of damage taken by the tail transferred to the main body. - High resistance to forced movement. Space Wind at standard pressure cannot move them. - Unusual hybrid damage melee via their Hypo-Fangs. Lamia bite attacks deal 1 point of armor piercing, 2 points of poison, 2 points of asphyxiation, and inject 3u of Space Drugs. Planned traitor items exist that add a fillable chemical reservoir that they can inject into people with their attacks. **Negative Traits** - Extreme Size. Lamia are literally the size of a, "Broad side of a barn". A blind person could throw a rock, and still hit the Lamia. By extension its essentially impossible for a Lamia to evade attacks. Yea you can take hits, but you're also going to *take* hits. All of them. - Paramedics WORST ENEMY. Since they weigh as much as a car, even Oni struggle to drag them. Even a rollerbed only slightly helps drag a Lamia around. - Vulnerability to AOE damage. An explosion that simultaneously strikes a Lamia's entire tail, plus their body, will deal double damage. - Cannot wear shoes. Although not being able to wear magboots is also offset to their natural high resistance to space wind. - Extreme size also means Lamia are functionally uncloneable. They require 770 units of Biomass, the equivalent of 5.5 Onis, in order to be cloned. ## Why / Balance This PR is part of an ongoing project to add exciting new content to the DeltaV repository, with a focus on keeping the theme of "Monster People" species, per request by admins. ## Technical details The code regarding Tail Segments is actually unfinished, and still needs significant overhauling before this PR can be undrafted. Here's a few concerns: - [x] Implement "Marking Parity" - [x] Make/Commission/Request new markings for the Lamia and her tail segments - [x] Implement "Hardsuit Appearance Parity" - [x] ServerLamiaSystem now utilizes new physics engine options. - [x] "We need to be able to spawn 80 Lamias without slowing down the server -Debug", this is a hard requirement. Having 32 tail segments is not required. I would prefer that we have 32 tail segments, but if we optimize their code for performance and still find out we aren't meeting the 80 Lamia hard requirement, I am willing to reduce them to as low as 16 to 20 segments. - [x] Implement Wizden's upcoming "Shoot Over Corpses unless they're targetted" for Lamia Tails. - [x] Reimplement the mechanic for Segments sharing their healthbar with the Lamia. - [x] Possibly make it so that Lamia can only wear Jumpskirts? I'd want to outright get rid of the layer mask if possible. ## Non-Technical TODO list These are all the TODO's that don't necessarily involve C#, and primarily live in the YAML side of things. - [x] Implement marking customization for Lamia. They should have marking variations for More/Less humanlike versions. Such as a Snake Head(We can re-use the Lizard snake head), Medusa Head, changing how far up the scales go, and if the Lamia has human skin or full scales. I'd like to have tail pattern variations that can be set in the character customization, but that is also pending the VisualizerSystem for tail segments. - [x] Make their hardsuit variants. Not actually difficult, just takes some time. - [x] Finely tune their numerical values. Basically nothing on the YAML side of things is final, and is subject to change pending beta feedback and/or testing. ## Media  One of the downsides of having extreme mass.  Working Collision physics:  Finalized version of the damage system, also featuring significant improvements to the tail systems.  **Changelog** 🆑 VMSolidus, @Elijahrane, and @noctyrnal - add: Lamia have been added to the game as a new playable species! They are currently extremely buggy, and so are by default disabled as a roundstart species. To enable them for (Buggy) playtesting, go to /Species/lamia.yml, and set roundstart to true. --------- Signed-off-by: VMSolidus <evilexecutive@gmail.com> Co-authored-by: Rane <60792108+Elijahrane@users.noreply.github.com> Co-authored-by: Aiden <aiden@djkraz.com> (cherry picked from commit 1e356fbb38120a850fe5abc8a9ecef57f8c049f1)
173 lines
8.1 KiB
C#
173 lines
8.1 KiB
C#
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;
|
|
|
|
|
|
namespace Content.Client.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
|
|
Color? col = null;
|
|
if (_entManager.TryGetComponent<HumanoidAppearanceComponent>(uid, out var humanoid))
|
|
if (humanoid.MarkingSet.TryGetCategory(MarkingCategories.Tail, out var tailMarkings))
|
|
col = tailMarkings.First().MarkingColors.First();
|
|
|
|
DrawLamia(handle, lamia, col ?? 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);
|
|
}
|
|
}
|