using System.Linq; using System.Numerics; using Content.Server._CP14.Demiplane; using Content.Server._CP14.Demiplane.Components; using Content.Server.Destructible; using Content.Server.Station.Systems; using Content.Shared._CP14.Demiplane.Components; using Content.Shared._CP14.Demiplane.Prototypes; using Content.Shared._CP14.DemiplaneTraveling; using Content.Shared.Damage; using Content.Shared.Destructible; using Content.Shared.Popups; using Content.Shared.Pulling.Events; using Content.Shared.UserInterface; using Robust.Server.GameObjects; using Robust.Shared.Audio.Systems; using Robust.Shared.Prototypes; using Robust.Shared.Random; namespace Content.Server._CP14.DemiplaneTraveling; public sealed partial class CP14StationDemiplaneMapSystem : CP14SharedStationDemiplaneMapSystem { [Dependency] private readonly UserInterfaceSystem _userInterface = default!; [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly CP14DemiplaneSystem _demiplane = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly DestructibleSystem _destructible = default!; [Dependency] private readonly DamageableSystem _damageable = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(DemiplaneEjectAttempt); SubscribeLocalEvent(DemiplaneRevokeAttempt); SubscribeLocalEvent(OnBeforeActivatableUiOpen); SubscribeLocalEvent(OnNodeBlockerShutdown); SubscribeLocalEvent(OnCoreInit); SubscribeLocalEvent(OnCoreShutdown); } private void OnCoreShutdown(Entity ent, ref DestructionEventArgs args) { if (TryComp(ent.Comp.Demiplane, out var demiplane)) { _demiplane.StartDestructDemiplane((ent.Comp.Demiplane.Value, demiplane)); } var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var stationMap)) { if (stationMap.Nodes.TryGetValue(ent.Comp.Position, out var node)) { node.Completed = true; } } } private void OnCoreInit(Entity core, ref MapInitEvent args) { core.Comp.Demiplane = Transform(core).MapUid; if (!TryComp(core.Comp.Demiplane, out var demiBlocker)) return; core.Comp.Station = demiBlocker.Station; core.Comp.Position = demiBlocker.Position; EnsureComp(core, out var coreBlocker); coreBlocker.Position = core.Comp.Position; coreBlocker.Station = core.Comp.Station; coreBlocker.IncreaseNodeDifficulty = 0; } private void OnNodeBlockerShutdown(Entity ent, ref ComponentShutdown args) { if (!TryComp(ent.Comp.Station, out var stationMap)) return; if (!stationMap.Nodes.TryGetValue(ent.Comp.Position, out var node)) return; if (ent.Comp.IncreaseNodeDifficulty == 0) return; node.AdditionalLevel += ent.Comp.IncreaseNodeDifficulty; GenerateNodeData(node, clearOldModifiers: true); } private void DemiplaneEjectAttempt(Entity ent, ref CP14DemiplaneMapEjectMessage args) { var station = _station.GetOwningStation(ent, Transform(ent)); if (!TryComp(station, out var stationMap)) return; if (!stationMap.Nodes.TryGetValue(args.Position, out var node)) return; if (!node.InFrontierZone) return; //Eject! var key = SpawnAttachedTo(ent.Comp.KeyProto, Transform(ent).Coordinates); _audio.PlayPvs(ent.Comp.EjectSound, Transform(key).Coordinates); if (TryComp(key, out var demiData)) { demiData.Location = node.LocationConfig; demiData.SelectedModifiers.AddRange(node.Modifiers); } EnsureComp(key, out var blockerComp); blockerComp.Position = args.Position; blockerComp.Station = station; } private void DemiplaneRevokeAttempt(Entity ent, ref CP14DemiplaneMapRevokeMessage args) { var station = _station.GetOwningStation(ent, Transform(ent)); var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var blocker)) { if (blocker.Station != station) continue; if (blocker.Position != args.Position) continue; if (!TryComp(station, out var demiStation)) continue; if (!demiStation.Nodes.TryGetValue(args.Position, out var node)) continue; SpawnAttachedTo("CP14ImpactEffectMagicSplitting", Transform(ent).Coordinates); //If it's a demiplane, initiate closure. If not, just delete it. if (TryComp(uid, out var demiplane)) { _demiplane.StartDestructDemiplane((uid, demiplane)); _popup.PopupEntity(Loc.GetString("cp14-demiplane-revoke-map"), ent); } else { SpawnAttachedTo("CP14ImpactEffectMagicSplitting", Transform(uid).Coordinates); _popup.PopupEntity(Loc.GetString("cp14-demiplane-revoke-item"), ent); _popup.PopupEntity(Loc.GetString("cp14-demiplane-revoke-item"), uid); _damageable.TryChangeDamage(uid, ent.Comp.RevokeDamage, true); _destructible.DestroyEntity(uid); } } } private void OnBeforeActivatableUiOpen(Entity ent, ref BeforeActivatableUIOpenEvent args) { var station = _station.GetOwningStation(ent, Transform(ent)); if (!TryComp(station, out var stationMap)) return; UpdateNodesStatus((station.Value, stationMap)); _userInterface.SetUiState(ent.Owner, CP14DemiplaneMapUiKey.Key, new CP14DemiplaneMapUiState(stationMap.Nodes, stationMap.Edges)); } private void OnMapInit(Entity ent, ref MapInitEvent args) { GenerateDemiplaneMap(ent); } private void GenerateDemiplaneMap(Entity ent) { ent.Comp.Nodes.Clear(); ent.Comp.Edges.Clear(); var allSpecials = _proto.EnumeratePrototypes().ToList(); _random.Shuffle(allSpecials); var grid = new Dictionary(); //Spawn start room at 0 0 var startPos = new Vector2i(0, 0); var startNode = new CP14DemiplaneMapNode(0, startPos, true); grid[startPos] = startNode; //Spawn special rooms var specialCount = _random.Next(ent.Comp.Specials.Min, ent.Comp.Specials.Max + 1); var placedSpecials = 0; var specialPositions = new List(); foreach (var special in allSpecials) { if (placedSpecials >= specialCount) break; var specialLevel = special.Levels.Next(_random); var possiblePositions = new List(); for (var x = -specialLevel; x <= specialLevel; x++) { var y = specialLevel - Math.Abs(x); if (y != 0) possiblePositions.Add(new Vector2i(x, y)); possiblePositions.Add(new Vector2i(x, -y)); } _random.Shuffle(possiblePositions); var specialPos = new Vector2i(0, 0); foreach (var pos in possiblePositions) { if (!grid.ContainsKey(pos)) { specialPos = pos; break; } } if (grid.ContainsKey(specialPos)) continue; var specialNode = new CP14DemiplaneMapNode( specialLevel, new Vector2(specialPos.X, specialPos.Y), false, locationConfig: special.Location, modifiers: [..special.Modifiers] ); grid[specialPos] = specialNode; specialPositions.Add(specialPos); placedSpecials++; } // Build meandering paths to each special room and add edges foreach (var specialPos in specialPositions) { var current = startPos; while (current != specialPos) { var delta = specialPos - current; var options = new List(); if (delta.X != 0) options.Add(new Vector2i(Math.Sign(delta.X), 0)); if (delta.Y != 0) options.Add(new Vector2i(0, Math.Sign(delta.Y))); // Add the possibility of a "mistaken" step to the side if (_random.Prob(0.3f)) // 30% chance to take a side step { if (delta.X != 0 && delta.Y != 0) { options.Add(new Vector2i(0, Math.Sign(delta.Y)) * -1); options.Add(new Vector2i(Math.Sign(delta.X), 0) * -1); } } _random.Shuffle(options); var step = options[0]; var next = current + step; if (!grid.TryGetValue(next, out var nextNode)) { nextNode = new CP14DemiplaneMapNode(Math.Abs(next.X) + Math.Abs(next.Y), new Vector2(next.X, next.Y), false); grid[next] = nextNode; } ent.Comp.Edges.Add((current, next)); current = next; } } //Fill nodes with random data foreach (var node in grid.Values) { GenerateNodeData(node); } // Random visual offset foreach (var node in grid.Values) { var x = node.UiPosition.X + _random.NextFloat(-0.2f, 0.2f); var y = node.UiPosition.Y + _random.NextFloat(-0.2f, 0.2f); node.UiPosition = new Vector2(x, y); } //Add all rooms into component ent.Comp.Nodes = grid; } private void GenerateNodeData(CP14DemiplaneMapNode node, bool clearOldModifiers = false) { if (node.Level == 0) return; var location = _demiplane.GenerateDemiplaneLocation(node.Level); node.LocationConfig ??= location; var limits = new Dictionary, float> { { "Danger", (node.Level + node.AdditionalLevel) * 0.2f }, { "GhostRoleDanger", 1f }, { "Reward", Math.Max(node.Level * 0.2f, 0.5f) }, { "Ore", Math.Max(node.Level * 0.2f, 0.5f) }, { "Fun", 1f }, { "Weather", 1f }, { "MapLight", 1f }, }; var mods = _demiplane.GenerateDemiplaneModifiers(node.Level, location, limits); if (clearOldModifiers) node.Modifiers.Clear(); foreach (var mod in mods) { node.Modifiers.Add(mod); } } private void UpdateNodesStatus(Entity ent) { foreach (var node in ent.Comp.Nodes) { node.Value.InFrontierZone = NodeInFronrierZone(ent.Comp.Nodes, ent.Comp.Edges, node.Key); node.Value.InUsing = false; } var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var blocker)) { if (!TryComp(blocker.Station, out var stationMap)) continue; if (!stationMap.Nodes.TryGetValue(blocker.Position, out var node)) continue; node.InUsing = true; } } }