using Content.Shared._White.DollyMixture; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.Shared.Utility; using System.Numerics; namespace Content.Client._White.DollyMixture; public sealed class DollyMixtureSystem : SharedDollyMixtureSystem { [Dependency] private readonly IResourceCache _res = default!; [Dependency] private readonly IEyeManager _eye = default!; [Dependency] private readonly SharedTransformSystem _xform = default!; [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IGameTiming _timing = default!; private ShaderPrototype _voxelProto = default!; private ShaderPrototype _voxelProtoEmissive = default!; private ShaderInstance _voxelDefaultShader = default!; private ShaderInstance _voxelEmissiveDefaultShader = default!; private const float DefaultHeight = 0.75f; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnRemove); SubscribeLocalEvent(OnAutoState); _voxelProto = _proto.Index("Voxel"); _voxelProtoEmissive = _proto.Index("VoxelEmissive"); _voxelDefaultShader = _voxelProto.InstanceUnique(); _voxelDefaultShader.SetParameter("height", DefaultHeight); _voxelEmissiveDefaultShader = _voxelProtoEmissive.InstanceUnique(); _voxelEmissiveDefaultShader.SetParameter("height", DefaultHeight); } public override void FrameUpdate(float frameTime) { var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var dollymix, out var sprite, out var xform)) { Angle angle = _xform.GetWorldRotation(xform) + _eye.CurrentEye.Rotation; if (dollymix.DirectionCount > 0) angle = Math.Round(angle / Math.Tau * dollymix.DirectionCount) * Math.Tau / dollymix.DirectionCount; const float MinAngleDelta = MathF.PI / 180 * 0.01f; if (MathHelper.CloseTo(dollymix.LastAngle, angle, MinAngleDelta)) continue; dollymix.LastAngle = angle; for (int i = 0; i < dollymix.LayerMappings.Count; i++) sprite.LayerSetRotation(dollymix.LayerMappings[i], angle); } } private void OnAutoState(EntityUid uid, DollyMixtureComponent comp, AfterAutoHandleStateEvent args) { UpdateDollyMixture(uid, comp); } public void UpdateDollyMixture(EntityUid uid, DollyMixtureComponent comp) { if (comp.CurrentRSIPath == comp.RSIPath) return; if (comp.RSIPath is null) { RemoveLayers(uid, comp); return; } if (comp.CurrentRSIPath is not null) RemoveLayers(uid, comp); BuildLayers(uid, comp); comp.CurrentRSIPath = comp.RSIPath; } private void OnRemove(EntityUid uid, DollyMixtureComponent comp, ComponentRemove args) { if (!_timing.IsFirstTimePredicted) return; RemoveLayers(uid, comp); } private void OnInit(EntityUid uid, DollyMixtureComponent comp, ComponentInit args) { if (!TryComp(uid, out var sprite)) // unlike OnRemove() and RemoveLayers(), this doesn't get executed when placing a prototype { // i cry Log.Error($"Failed to get SpriteComponent for {ToPrettyString(uid)}. Removing DollyMixtureComponent."); RemComp(uid); return; } sprite.NoRotation = true; UpdateDollyMixture(uid, comp); } public override void Apply3D(Entity entity, string rsiPath, string? statePrefix = null, Vector2? layerOffset = null) { entity.Comp ??= EnsureComp(entity); base.Apply3D(entity, rsiPath, statePrefix, layerOffset); UpdateDollyMixture(entity, entity.Comp); } public override void Remove3D(Entity entity) { if (!Resolve(entity, ref entity.Comp)) return; base.Remove3D(entity); UpdateDollyMixture(entity, entity.Comp); } private void RemoveLayers(EntityUid uid, DollyMixtureComponent comp) { //SpriteComponent? sprite = null; //if (!Resolve(uid, ref sprite, false)) // this gets executed after simply placing a prototype with this comp // return; // i assume it is some prediction-related bullshit if(!TryComp(uid, out var sprite)) return; foreach (var layerMapping in comp.LayerMappings) sprite.RemoveLayer(layerMapping); comp.CurrentRSIPath = null; comp.LayerMappings.Clear(); } private void BuildLayers(EntityUid uid, DollyMixtureComponent comp, SpriteComponent? sprite = null) { if (string.IsNullOrEmpty(comp.RSIPath)) { Log.Error($"An empty rsi path was passed to BuildLayers()."); return; } if (!Resolve(uid, ref sprite, false)) return; var xform = Transform(uid); if (!_res.TryGetResource($"/Textures/{comp.RSIPath}", out RSIResource? RSIres)) { Log.Error($"Failed to get RSI {$"/Textures/{comp.RSIPath}"} for a dolly mixture component."); return; } var RSI = RSIres.RSI; ShaderInstance voxelShader; if(comp.LayerHeight != DefaultHeight) { voxelShader = _voxelProto.InstanceUnique(); voxelShader.SetParameter("height", comp.LayerHeight); } else { voxelShader = _voxelDefaultShader; } int i = 1; while (RSI.TryGetState($"{comp.StatePrefix}{i}", out var state)) { Vector2 layerOffset = comp.Offset / EyeManager.PixelsPerMeter + new Vector2(0, comp.LayerHeight) / EyeManager.PixelsPerMeter * (i - 1); int layerIndex = sprite.AddBlankLayer(); sprite.LayerSetRSI(layerIndex, RSI); sprite.LayerSetState(layerIndex, state.StateId); sprite.LayerSetOffset(layerIndex, layerOffset); sprite.LayerSetRotation(layerIndex, xform.LocalRotation + _eye.CurrentEye.Rotation); string layerMap = $"dollymix-{comp.StatePrefix}{i}"; DebugTools.Assert(!sprite.LayerExists(layerMap), "Dollymix layer already present when building layers; improper cleanup?"); sprite.LayerMapSet(layerMap, layerIndex); comp.LayerMappings.Add(layerMap); if (RSI.TryGetState($"{comp.StatePrefix}{i}-unshaded", out var unshadedState)) { var voxelEmissiveShader = _voxelProtoEmissive.InstanceUnique(); voxelEmissiveShader.SetParameter("height", comp.LayerHeight); int unshadedLayerIndex = sprite.AddBlankLayer(); sprite.LayerSetRSI(unshadedLayerIndex, RSI); sprite.LayerSetState(unshadedLayerIndex, unshadedState.StateId); string unshadedLayerMap = $"{layerMap}-u"; sprite.LayerMapSet(unshadedLayerMap, unshadedLayerIndex); comp.LayerMappings.Add(unshadedLayerMap); sprite.TryGetLayer(unshadedLayerIndex, out var unshadedLayer); DebugTools.Assert(unshadedLayer is not null); SpriteComponent.CopyToShaderParameters ctsp = new(layerMap); ctsp.ParameterTexture = "emissiveTexture"; ctsp.ParameterUV = "emissiveUV"; unshadedLayer.CopyToShaderParameters = ctsp; sprite.LayerSetShader(layerIndex, voxelEmissiveShader); } else { sprite.LayerSetShader(layerIndex, voxelShader); } i++; } } }