Gathering resourses redesign (#1594)

* Add Crash to Windlands survival gamemode and map

Introduces the CP14CrashToWindlandsRule and its component for a new survival gamemode where a ship crashes into wildlands. Adds the 'nautilus_ship' map, updates English and Russian locale files with new gamemode titles and descriptions, and modifies relevant prototype and map pool files to support the new mode.

* fix FTL map

* firebombing is real

* fix biome dungen all grid overriding

* Update PostMapInitTest.cs

* Update DungeonJob.CP14Biome.cs

* Refactor demiplane generation and crash rules

Replaces the old demiplane job system with a new procedural location generation system (CP14LocationGenerationSystem and CP14SpawnProceduralLocationJob). Splits the crash-to-windlands rule into CP14CrashingShipRule (handles explosions) and CP14ExpeditionToWindlandsRule (handles map generation and FTL), with corresponding new components. Updates roundstart game rule prototype and moves/renames several files for clarity and modularity.

* Refactor location generation to support optional seed and position

Updated the GenerateLocation method to accept an optional seed and position, defaulting to a random seed if none is provided. Adjusted all call sites and the procedural job to support these changes, improving flexibility and consistency in procedural location generation.

* procedural integration into game map

* Demiplanes deletion

* clear demiplane content

* remapping procedural + frigid coast deletion

* clear demiplane guidebook

* dungeons generations

* Refactor procedural location configs and add ComossIsland

Consolidated and renamed procedural location and dungeonConfig prototypes for demiplane locations, replacing T1-prefixed and legacy IDs with new, consistent names. Updated map YAMLs to reference new location IDs and configs. Added a new ComossIsland location and dungeonConfig. Refactored code to support passing custom dungeon layers and removed unused ExamineProb field from CP14ProceduralLocationPrototype.

* Enhance procedural world gen and location configs

Improved procedural world generation by adding location generation probability, adjusting level ranges, and refining modifier uniqueness. Updated CP14ProceduralLocationPrototype and CP14ProceduralModifierPrototype, refactored node data generation logic, and made related test and map changes. Added new venicialis_fort station map and updated several procedural location and modifier YAMLs for consistency.

* fix

* connections room spawners

* track finishing global world generation

* real connection

* Update PostMapInitTest.cs

* Update venicialis.yml

* Update venicialis.yml

* fix raids, decrease city island sizes

* Update migration.yml

* Update migration.yml

* fix shutdowning

* Update CP14SpawnProceduralLocationJob.cs
This commit is contained in:
Red
2025-08-04 13:35:55 +03:00
committed by GitHub
parent 35206a003b
commit 6b5c27125b
143 changed files with 17348 additions and 370055 deletions

View File

@@ -1,7 +0,0 @@
using Content.Shared._CP14.DemiplaneTraveling;
namespace Content.Client._CP14.DemiplaneTraveling;
public sealed partial class CP14ClientStationDemiplaneMapSystem : CP14SharedStationDemiplaneMapSystem
{
}

View File

@@ -1,34 +0,0 @@
using Content.Shared._CP14.DemiplaneTraveling;
using Robust.Client.UserInterface;
namespace Content.Client._CP14.DemiplaneTraveling;
public sealed class CP14DemiplaneMapBoundUserInterface : BoundUserInterface
{
private CP14DemiplaneMapWindow? _window;
public CP14DemiplaneMapBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}
protected override void Open()
{
base.Open();
_window = this.CreateWindow<CP14DemiplaneMapWindow>();
_window.OnEject += pos => SendMessage(new CP14DemiplaneMapEjectMessage(pos));
//_window.OnRevoke += pos => SendMessage(new CP14DemiplaneMapRevokeMessage(pos));
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (_window == null || state is not CP14DemiplaneMapUiState mapState)
return;
_window?.UpdateState(mapState);
}
}

View File

@@ -1,84 +0,0 @@
<demiplaneTraveling:CP14DemiplaneMapWindow
xmlns="https://spacestation14.io"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:parallax="clr-namespace:Content.Client.Parallax"
xmlns:nodeTree="clr-namespace:Content.Client._CP14.UserInterface.Systems.NodeTree"
xmlns:demiplaneTraveling="clr-namespace:Content.Client._CP14.DemiplaneTraveling"
Title="{Loc 'cp14-demiplane-map-title'}"
MinSize="1000 650"
SetSize="1800 950">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
<!-- Selected Location -->
<BoxContainer Margin="10 10 10 10" MaxWidth="240" SetWidth="240" Orientation="Vertical"
HorizontalExpand="False" VerticalExpand="True">
<!-- Location View -->
<PanelContainer Name="BackPanel" HorizontalAlignment="Center">
<PanelContainer.PanelOverride>
<graphics:StyleBoxTexture Modulate="#1B1B1E" PatchMarginBottom="10" PatchMarginLeft="10"
PatchMarginRight="10" PatchMarginTop="10" />
</PanelContainer.PanelOverride>
<BoxContainer HorizontalExpand="True" VerticalExpand="True">
<TextureRect Stretch="Scale" Name="LocationView" SetSize="64 64" HorizontalAlignment="Center"
VerticalAlignment="Center" MinSize="64 64"
HorizontalExpand="True" VerticalExpand="True" Access="Public" />
</BoxContainer>
</PanelContainer>
<customControls:HSeparator StyleClasses="HighDivider" Margin="0 15 0 10" />
<!-- Location Data -->
<BoxContainer Name="NodeViewContainer" Orientation="Vertical" VerticalExpand="True">
<ScrollContainer HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical" HorizontalExpand="False" VerticalExpand="True">
<BoxContainer Name="InfoContainer" Orientation="Vertical" HorizontalExpand="True"
VerticalExpand="True">
<BoxContainer HorizontalExpand="True">
<Label Name="Name" Access="Public" StyleClasses="LabelHeadingBigger" VAlign="Center"
HorizontalExpand="True" HorizontalAlignment="Center" />
</BoxContainer>
<!-- Description -->
<BoxContainer HorizontalExpand="True">
<RichTextLabel Name="Description" HorizontalExpand="True" Access="Public" />
</BoxContainer>
</BoxContainer>
</BoxContainer>
</ScrollContainer>
<Control MinHeight="5" />
<!-- Buttons -->
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Button Name="EjectButton" Text="{Loc 'cp14-demiplane-map-eject'}"
ToolTip="{Loc 'cp14-demiplane-map-eject-tooltip'}" StyleClasses="OpenRight"
HorizontalExpand="True" MinHeight="35" Access="Public" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Button Name="RevokeButton" Text="{Loc 'cp14-demiplane-map-revoke'}"
ToolTip="{Loc 'cp14-demiplane-map-revoke-tooltip'}" StyleClasses="OpenRight"
HorizontalExpand="True" MinHeight="35" Access="Public" />
</BoxContainer>
</BoxContainer>
</BoxContainer>
<customControls:VSeparator StyleClasses="LowDivider" />
<!-- Demiplane map Tree -->
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer HorizontalExpand="True">
<Label Name="TreeName" Access="Public" StyleClasses="LabelHeadingBigger" VAlign="Center"
HorizontalExpand="True" HorizontalAlignment="Center" />
</BoxContainer>
<PanelContainer Margin="10 10 10 10" HorizontalExpand="True" VerticalExpand="True" RectClipContent="True">
<parallax:ParallaxControl Name="ParallaxBackground" ScaleX="4" ScaleY="4"
ParallaxPrototype="KettleStation" Access="Public" SpeedX="10" SpeedY="5" />
<BoxContainer Margin="10 10 10 10" Orientation="Horizontal" HorizontalExpand="True"
VerticalExpand="True">
<nodeTree:CP14NodeTreeGraphControl Name="GraphControl" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Access="Public" />
</BoxContainer>
<!-- Admin Description -->
<BoxContainer Name="AdminPanel" Visible="False" HorizontalExpand="True" HorizontalAlignment="Right" VerticalAlignment="Top">
<RichTextLabel Name="AdminDescription" HorizontalExpand="True" Access="Public" />
</BoxContainer>
</PanelContainer>
</BoxContainer>
</BoxContainer>
</demiplaneTraveling:CP14DemiplaneMapWindow>

View File

@@ -1,266 +0,0 @@
using System.Numerics;
using System.Text;
using Content.Client._CP14.UserInterface.Systems.NodeTree;
using Content.Client.Administration.Managers;
using Content.Shared._CP14.DemiplaneTraveling;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.Player;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Utility;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client._CP14.DemiplaneTraveling;
[GenerateTypedNameReferences]
public sealed partial class CP14DemiplaneMapWindow : DefaultWindow
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly ILogManager _log = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IClientAdminManager _admin = default!;
private CP14DemiplaneMapUiState? _cachedState;
private CP14DemiplaneMapNode? _selectedNode;
public event Action<Vector2i>? OnEject;
public event Action<Vector2i>? OnRevoke;
private ISawmill Sawmill { get; init; }
public CP14DemiplaneMapWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
Sawmill = _log.GetSawmill("cp14_demiplane_map_window");
EjectButton.OnPressed += EjectPressed;
RevokeButton.OnPressed += RevokePressed;
GraphControl.OnOffsetChanged += offset =>
{
ParallaxBackground.Offset = -offset * 0.25f + new Vector2(1000, 1000); //hardcoding is bad
};
GraphControl.OnNodeSelected += SelectNode;
}
private void RevokePressed(BaseButton.ButtonEventArgs obj)
{
if (_selectedNode == null)
return;
if (_cachedState == null)
return;
foreach (var node in _cachedState.Nodes)
{
if (node.Value != _selectedNode)
continue;
OnRevoke?.Invoke(node.Key);
Close();
return;
}
}
private void EjectPressed(BaseButton.ButtonEventArgs obj)
{
if (_selectedNode == null)
return;
if (_cachedState == null)
return;
foreach (var node in _cachedState.Nodes)
{
if (node.Value != _selectedNode)
continue;
OnEject?.Invoke(node.Key);
Close();
return;
}
}
public void UpdateState(CP14DemiplaneMapUiState state)
{
_cachedState = state;
HashSet<CP14NodeTreeElement> nodeTreeElements = new();
foreach (var node in state.Nodes)
{
if (node.Value.Start)
{
var startElement = new CP14NodeTreeElement(
nodeKey: node.Key.ToString(),
gained: true,
active: false,
node.Value.UiPosition * 100,
icon: new SpriteSpecifier.Rsi(new ResPath("_CP14/Interface/NodeTree/demiplane_map.rsi"), "center"));
nodeTreeElements.Add(startElement);
}
else
{
_prototype.TryIndex(node.Value.LocationConfig, out var location);
var treeElement = new CP14NodeTreeElement(
nodeKey: node.Key.ToString(),
gained: node.Value.Completed,
active: node.Value.InFrontierZone || node.Value.Completed,
node.Value.UiPosition * 100,
icon: location?.Icon);
nodeTreeElements.Add(treeElement);
}
}
var edges = new HashSet<(string, string)>();
foreach (var edge in state.Edges)
{
edges.Add((edge.Item1.ToString(), edge.Item2.ToString()));
}
GraphControl.UpdateState(
new CP14NodeTreeUiState(
nodeTreeElements,
edges: edges,
frameIcon: new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Interface/NodeTree/demiplane_map.rsi"),
"frame"),
hoveredIcon: new SpriteSpecifier.Rsi(
new ResPath("/Textures/_CP14/Interface/NodeTree/demiplane_map.rsi"),
"hovered"),
selectedIcon: new SpriteSpecifier.Rsi(
new ResPath("/Textures/_CP14/Interface/NodeTree/demiplane_map.rsi"),
"selected"),
learnedIcon: new SpriteSpecifier.Rsi(
new ResPath("/Textures/_CP14/Interface/NodeTree/demiplane_map.rsi"),
"learned"),
activeLineColor: new Color(172, 102, 190),
lineColor: new Color(83, 40, 121)
)
);
}
private void SelectNode(CP14NodeTreeElement? node)
{
if (node == null)
{
DeselectNode();
return;
}
if (_cachedState == null)
{
Sawmill.Error("Tried to select node without a cached state.");
return;
}
if (node.NodeKey.Trim('(', ')').Split(',') is { Length: 2 } parts
&& int.TryParse(parts[0], out var x)
&& int.TryParse(parts[1], out var y)
&& _cachedState.Nodes.TryGetValue(new Vector2i(x, y), out var mapNode))
{
SelectNode(mapNode);
}
else
{
Sawmill.Error($"Tried to select node {node.NodeKey} that doesn't exist in the map.");
DeselectNode();
}
}
private void SelectNode(CP14DemiplaneMapNode? node)
{
_selectedNode = node;
var isAdmin = _admin.IsAdmin();
if (node == null)
{
DeselectNode();
return;
}
if (_cachedState == null)
{
Sawmill.Error("Tried to select node without a cached state.");
return;
}
if (node.LocationConfig != null && _prototype.TryIndex(node.LocationConfig, out var location))
{
if (location.Name is not null)
Name.Text = Loc.GetString(location.Name);
//Generate description
HashSet<LocId> modifierNames = new();
foreach (var modifier in node.Modifiers)
{
if (!_prototype.TryIndex(modifier, out var indexedModifier))
continue;
if (indexedModifier.Name is null)
continue;
modifierNames.Add(indexedModifier.Name.Value);
}
var sb = new StringBuilder();
foreach (var name in modifierNames)
{
sb.Append("- " + Loc.GetString(name) + "\n");
}
sb.Append("\n");
if (!node.InFrontierZone && !node.Completed)
sb.Append(Loc.GetString("cp14-demiplane-map-status-blocked"));
else if (node.InFrontierZone && !node.InUsing)
sb.Append(Loc.GetString("cp14-demiplane-map-status-allowed"));
else if (node.InUsing)
sb.Append(Loc.GetString("cp14-demiplane-map-status-used"));
else if (node.Completed)
sb.Append(Loc.GetString("cp14-demiplane-map-status-scanned"));
sb.Append("\n \n");
if (node.AdditionalLevel > 0 && !node.Completed)
{
sb.Append(Loc.GetString("cp14-demiplane-map-add-level", ("count", node.AdditionalLevel))+"\n");
sb.Append(Loc.GetString("cp14-demiplane-map-add-level-tooltip")+"\n");
}
Description.Text = sb.ToString();
LocationView.Texture = location.Icon?.Frame0();
}
else
{
Name.Text = string.Empty;
Description.Text = string.Empty;
LocationView.Texture = null;
}
EjectButton.Disabled = !node.InFrontierZone || node.InUsing;
RevokeButton.Disabled = !node.InUsing;
//Admin part
AdminPanel.Visible = isAdmin;
var adminSb = new StringBuilder();
adminSb.Append("Modifiers: \n");
foreach (var modifier in node.Modifiers)
{
adminSb.Append("- " + Loc.GetString(modifier.Id) + "\n");
}
AdminDescription.Text = adminSb.ToString();
}
private void DeselectNode()
{
Name.Text = string.Empty;
Description.Text = string.Empty;
LocationView.Texture = null;
EjectButton.Disabled = true;
RevokeButton.Disabled = true;
}
}

View File

@@ -1,5 +1,3 @@
using Content.Client._CP14.DemiplaneTraveling;
using Content.Shared._CP14.DemiplaneTraveling;
using Content.Shared._CP14.Religion.Systems;
using Robust.Client.UserInterface;

View File

@@ -65,7 +65,7 @@
<customControls:VSeparator StyleClasses="LowDivider" />
<!-- Demiplane map Tree -->
<!-- Trading positions map Tree -->
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer HorizontalExpand="True">
<Label Name="TreeName" Access="Public" StyleClasses="LabelHeadingBigger" VAlign="Center"

View File

@@ -69,7 +69,7 @@ namespace Content.IntegrationTests.Tests
"MeteorArena",
"Comoss",
"Venicialis",
"Frigid_Coast",
//"NautilusShip",
//CrystallEdge Map replacement end
};

View File

@@ -223,6 +223,12 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
case CP14RoomsDunGen cp14RoomsDunGen:
await PostGen(cp14RoomsDunGen, dungeons, reservedTiles, random);
break;
case CP14BiomeDunGen cp14BiomesDunGen:
await PostGen(cp14BiomesDunGen, dungeons, reservedTiles, random);
break;
case CP14ReserveGrid cp14ReserveGrid:
await PostGen(cp14ReserveGrid, reservedTiles);
break;
//CP14 zone end
case AutoCablingDunGen cabling:
await PostGen(cabling, dungeons[^1], reservedTiles, random);

View File

@@ -137,6 +137,23 @@ public sealed partial class ShuttleSystem
var parallax = EnsureComp<ParallaxComponent>(mapUid);
parallax.Parallax = ftlMap.Parallax;
//CP14 FTL map tweaks
var mapLight = EnsureComp<MapLightComponent>(mapUid);
mapLight.AmbientLightColor = ftlMap.AmbientColor;
var moles = new float[Atmospherics.AdjustedNumberOfGases];
moles[(int) Gas.Oxygen] = 21.824779f;
moles[(int) Gas.Nitrogen] = 82.10312f;
var mixture = new GasMixture(moles, Atmospherics.T20C);
_atmos.SetMapAtmosphere(mapUid, false, mixture);
var gravity = EnsureComp<GravityComponent>(mapUid);
gravity.Enabled = true;
gravity.Inherent = true;
//CP14 FTL map tweaks ends
return mapUid;
}
@@ -465,7 +482,7 @@ public sealed partial class ShuttleSystem
var xform = _xformQuery.GetComponent(uid);
var body = _physicsQuery.GetComponent(uid);
var comp = entity.Comp1;
//DoTheDinosaur(xform); //CP14 without stunning
DoTheDinosaur(xform);
_dockSystem.SetDockBolts(entity, false);
_physics.SetLinearVelocity(uid, Vector2.Zero, body: body);

View File

@@ -1,4 +1,5 @@
using Content.Server.Administration.Logs;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Systems;
using Content.Server.Buckle.Systems;
using Content.Server.Parallax;
@@ -63,6 +64,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
[Dependency] private readonly ThrusterSystem _thruster = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly TurfSystem _turf = default!;
[Dependency] private readonly AtmosphereSystem _atmos = default!;
private EntityQuery<BuckleComponent> _buckleQuery;
private EntityQuery<MapGridComponent> _gridQuery;
@@ -104,7 +106,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
return;
EnsureComp<ShuttleComponent>(ev.EntityUid);
EnsureComp<ImplicitRoofComponent>(ev.EntityUid);
//EnsureComp<ImplicitRoofComponent>(ev.EntityUid); //CP14 - grids (ships) without roofs!
}
private void OnShuttleStartup(EntityUid uid, ShuttleComponent component, ComponentStartup args)

View File

@@ -1,124 +0,0 @@
using Content.Shared._CP14.Demiplane.Components;
using Robust.Shared.Random;
namespace Content.Server._CP14.Demiplane;
public sealed partial class CP14DemiplaneSystem
{
private void InitConnections()
{
SubscribeLocalEvent<CP14DemiplaneRiftComponent, MapInitEvent>(OnRiftInit);
SubscribeLocalEvent<CP14DemiplaneRiftComponent, ComponentShutdown>(OnRiftShutdown);
}
private void OnRiftInit(Entity<CP14DemiplaneRiftComponent> rift, ref MapInitEvent args)
{
var map = Transform(rift).MapUid;
if (_demiplaneQuery.TryComp(map, out var demiplane)) // In demiplane
{
if (rift.Comp.TryAutoLinkToMap)
rift.Comp.Demiplane = map.Value;
if (rift.Comp.ActiveTeleport)
AddDemiplaneRandomEntryPoint((map.Value, demiplane), rift);
}
else if (rift.Comp.Demiplane is not null) //We out of demiplane
{
if (_demiplaneQuery.TryComp(rift.Comp.Demiplane, out var riftDemiplane))
{
if (rift.Comp.ActiveTeleport)
AddDemiplaneRandomExitPoint((rift.Comp.Demiplane.Value, riftDemiplane), rift);
}
}
}
private void OnRiftShutdown(Entity<CP14DemiplaneRiftComponent> rift, ref ComponentShutdown args)
{
if (rift.Comp.Demiplane is null)
return;
if (!_demiplaneQuery.TryComp(rift.Comp.Demiplane, out var riftDemiplane))
return;
RemoveDemiplaneRandomEntryPoint((rift.Comp.Demiplane.Value, riftDemiplane), rift);
RemoveDemiplaneRandomExitPoint((rift.Comp.Demiplane.Value, riftDemiplane), rift);
}
/// <summary>
///Add a position in the real world where you can get out of this demiplane
/// </summary>
private void AddDemiplaneRandomExitPoint(Entity<CP14DemiplaneComponent> demiplane,
Entity<CP14DemiplaneRiftComponent> exitPoint)
{
demiplane.Comp.ExitPoints.Add(exitPoint);
exitPoint.Comp.Demiplane = demiplane;
}
/// <summary>
/// Removing the demiplane exit point, one of which the player can exit to
/// </summary>
private void RemoveDemiplaneRandomExitPoint(Entity<CP14DemiplaneComponent>? demiplane,
EntityUid exitPoint)
{
if (!TryComp<CP14DemiplaneRiftComponent>(exitPoint, out var riftComp))
return;
if (demiplane is not null && demiplane.Value.Comp.ExitPoints.Contains(exitPoint))
{
demiplane.Value.Comp.ExitPoints.Remove(exitPoint);
riftComp.Demiplane = null;
}
if (riftComp.DeleteAfterDisconnect && exitPoint.Valid)
QueueDel(exitPoint);
}
/// <summary>
/// Add a position within the demiplane that can be entered into the demiplane
/// </summary>
private void AddDemiplaneRandomEntryPoint(Entity<CP14DemiplaneComponent> demiplane,
Entity<CP14DemiplaneRiftComponent> entryPoint)
{
demiplane.Comp.EntryPoints.Add(entryPoint);
entryPoint.Comp.Demiplane = demiplane;
}
private void RemoveDemiplaneRandomEntryPoint(Entity<CP14DemiplaneComponent>? demiplane,
EntityUid entryPoint)
{
if (!TryComp<CP14DemiplaneRiftComponent>(entryPoint, out var riftComp))
return;
if (demiplane is not null && demiplane.Value.Comp.EntryPoints.Contains(entryPoint))
{
demiplane.Value.Comp.EntryPoints.Remove(entryPoint);
riftComp.Demiplane = null;
}
if (riftComp.DeleteAfterDisconnect && entryPoint.Valid)
QueueDel(entryPoint);
}
public bool TryGetDemiplaneEntryPoint(Entity<CP14DemiplaneComponent> demiplane, out EntityUid? entryPoint)
{
entryPoint = null;
if (demiplane.Comp.EntryPoints.Count == 0)
return false;
entryPoint = _random.Pick(demiplane.Comp.EntryPoints);
return true;
}
public bool TryGetDemiplaneExitPoint(Entity<CP14DemiplaneComponent> demiplane,
out EntityUid? exitPoint)
{
exitPoint = null;
if (demiplane.Comp.ExitPoints.Count == 0)
return false;
exitPoint = _random.Pick(demiplane.Comp.ExitPoints);
return true;
}
}

View File

@@ -1,74 +0,0 @@
using Content.Server._CP14.WeatherControl;
using Content.Server.Weather;
using Content.Shared._CP14.Demiplane.Components;
using Content.Shared.Weather;
using Robust.Shared.Audio;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Demiplane;
public sealed partial class CP14DemiplaneSystem
{
[Dependency] private readonly WeatherSystem _weather = default!;
private void InitDestruction()
{
SubscribeLocalEvent<CP14DemiplaneTimedDestructionComponent, ComponentAdd>(OnDestructionStarted);
}
public void StartDestructDemiplane(Entity<CP14DemiplaneComponent> demiplane)
{
if (!TryComp<MapComponent>(demiplane, out var map))
return;
if (HasComp<CP14DemiplaneTimedDestructionComponent>(demiplane))
return;
EnsureComp<CP14DemiplaneTimedDestructionComponent>(demiplane);
if (HasComp<CP14WeatherControllerComponent>(demiplane))
{
RemCompDeferred<CP14WeatherControllerComponent>(demiplane);
}
if (!_proto.TryIndex<WeatherPrototype>("CP14DemiplaneDestructionStorm", out var indexedWeather))
return;
_weather.SetWeather(map.MapId, indexedWeather, null);
}
private void OnDestructionStarted(Entity<CP14DemiplaneTimedDestructionComponent> ent, ref ComponentAdd args)
{
ent.Comp.EndTime = _timing.CurTime + ent.Comp.TimeToDestruction;
ent.Comp.SelectedSong = new SoundPathSpecifier(_audio.GetSound(ent.Comp.Sound));
}
private void UpdateDestruction(float frameTime)
{
var query = EntityQueryEnumerator<CP14DemiplaneTimedDestructionComponent, CP14DemiplaneComponent>();
while (query.MoveNext(out var uid, out var destruction, out var demiplane))
{
var remaining = destruction.EndTime - _timing.CurTime;
if (destruction.SelectedSong is null)
continue;
var audioLength = _audio.GetAudioLength(destruction.SelectedSong.Path.ToString());
if (destruction.Stream is null && remaining < audioLength)
{
var audio = _audio.PlayPvs(destruction.Sound, uid);
destruction.Stream = audio?.Entity;
_audio.SetMapAudio(audio);
Dirty(uid, destruction);
DemiplaneAnnounce(uid, Loc.GetString("cp14-demiplane-countdown", ("duration", audioLength.Minutes)));
}
if (remaining <= TimeSpan.Zero)
{
_audio.Stop(destruction.Stream);
DeleteDemiplane((uid, demiplane));
}
}
}
}

View File

@@ -1,34 +0,0 @@
using Content.Server.Chat.Systems;
using Robust.Shared.Random;
namespace Content.Server._CP14.Demiplane;
public sealed partial class CP14DemiplaneSystem
{
private void InitEchoes()
{
SubscribeLocalEvent<EntitySpokeEvent>(OnSpeak);
}
private void OnSpeak(EntitySpokeEvent ev)
{
var map = Transform(ev.Source).MapUid;
if (!_demiplaneQuery.TryComp(map, out var demiplane))
return;
//Get random exit, and send message there
if (demiplane.ExitPoints.Count == 0)
return;
var exit = _random.Pick(demiplane.ExitPoints);
_chat.TrySendInGameICMessage(exit,
ev.Message,
InGameICChatType.Whisper,
ChatTransmitRange.Normal,
true,
checkRadioPrefix: false,
nameOverride: Loc.GetString("cp14-demiplane-echoes"),
ignoreActionBlocker: false);
}
}

View File

@@ -1,419 +0,0 @@
using System.Linq;
using System.Threading;
using Content.Server._CP14.Demiplane.Components;
using Content.Server._CP14.Demiplane.Jobs;
using Content.Server.GameTicking;
using Content.Shared._CP14.Demiplane.Components;
using Content.Shared._CP14.Demiplane.Prototypes;
using Content.Shared.Examine;
using Content.Shared.Interaction.Events;
using Content.Shared.Verbs;
using Robust.Shared.CPUJob.JobQueues;
using Robust.Shared.CPUJob.JobQueues.Queues;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using Robust.Shared.Map.Components;
namespace Content.Server._CP14.Demiplane;
public sealed partial class CP14DemiplaneSystem
{
private readonly JobQueue _expeditionQueue = new();
private readonly List<(CP14SpawnRandomDemiplaneJob Job, CancellationTokenSource CancelToken)> _expeditionJobs = new();
private const double JobMaxTime = 0.002;
[Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
private void InitGeneration()
{
SubscribeLocalEvent<CP14DemiplaneRandomGeneratorComponent, MapInitEvent>(GeneratorMapInit);
SubscribeLocalEvent<CP14DemiplaneUsingOpenComponent, UseInHandEvent>(GeneratorUsedInHand);
SubscribeLocalEvent<CP14DemiplaneDataComponent, GetVerbsEvent<ExamineVerb>>(OnVerbExamine);
}
private void GeneratorMapInit(Entity<CP14DemiplaneRandomGeneratorComponent> generator, ref MapInitEvent args)
{
if (!TryComp<CP14DemiplaneDataComponent>(generator, out var data))
return;
CP14DemiplaneLocationPrototype? selectedConfig = null;
//Location generation
if (data.Location is null || generator.Comp.OverrideLocation)
{
selectedConfig = GenerateDemiplaneLocation(generator.Comp.Level);
data.Location = selectedConfig;
}
else
{
if (!_proto.TryIndex(data.Location, out selectedConfig))
return;
}
//Modifier generation
var newModifiers = GenerateDemiplaneModifiers(
generator.Comp.Level,
selectedConfig,
generator.Comp.Limits);
foreach (var mod in newModifiers)
{
data.SelectedModifiers.Add(mod);
}
}
private void GeneratorUsedInHand(Entity<CP14DemiplaneUsingOpenComponent> ent, ref UseInHandEvent args)
{
if (args.Handled)
return;
if (!TryComp<CP14DemiplaneDataComponent>(ent, out var generator))
return;
args.Handled = true;
UseGenerator((ent, generator), args.User);
QueueDel(ent);
}
//Ed: I hate this function.
private void UseGenerator(Entity<CP14DemiplaneDataComponent> generator, EntityUid? user = null)
{
//block the opening of demiplanes after the end of a round
if (_gameTicker.RunLevel != GameRunLevel.InRound)
{
if (user is not null)
_popup.PopupEntity(Loc.GetString("cp14-demiplan-cannot-open-end-round"), generator, user.Value);
return;
}
//We cant open demiplane in another demiplane or if parent is not Map
if (_demiplaneQuery.HasComp(Transform(generator).MapUid))
{
if (user is not null)
_popup.PopupEntity(Loc.GetString("cp14-demiplan-cannot-open", ("name", MetaData(generator).EntityName)), generator, user.Value);
return;
}
if (generator.Comp.Location is null)
return;
//an attempt to open demiplanes can be intercepted by other systems that substitute a map instead of generating the planned demiplane.
Entity<CP14DemiplaneComponent>? demiplane = null;
var ev = new CP14DemiplaneGenerationCatchAttemptEvent();
RaiseLocalEvent(ev);
if (ev.Demiplane is null)
{
SpawnRandomDemiplane(generator.Comp.Location.Value, generator.Comp.SelectedModifiers, out demiplane, out var mapId);
if (demiplane is not null && TryComp<CP14DemiplaneMapNodeBlockerComponent>(generator, out var blocker))
{
EnsureComp<CP14DemiplaneMapNodeBlockerComponent>(demiplane.Value, out var blockerMap);
blockerMap.Position = blocker.Position;
blockerMap.Station = blocker.Station;
blockerMap.IncreaseNodeDifficulty = 1;
}
}
else
{
demiplane = ev.Demiplane;
}
if (demiplane is null)
return;
//Admin log needed
EnsureComp<CP14DemiplaneDestroyWithoutStabilizationComponent>(demiplane.Value);
//Rifts spawning
foreach (var rift in generator.Comp.AutoRifts)
{
var spawnedRift = EntityManager.Spawn(rift);
_transform.SetCoordinates(spawnedRift, Transform(generator).Coordinates);
_transform.AttachToGridOrMap(spawnedRift);
var connection = EnsureComp<CP14DemiplaneRiftComponent>(spawnedRift);
AddDemiplaneRandomExitPoint(demiplane.Value, (spawnedRift, connection));
}
}
private void OnVerbExamine(Entity<CP14DemiplaneDataComponent> ent, ref GetVerbsEvent<ExamineVerb> args)
{
if (!args.CanInteract || !args.CanAccess)
return;
var markup = GetDemiplanExamine(ent.Comp);
_examine.AddDetailedExamineVerb(
args,
ent.Comp,
markup,
Loc.GetString("cp14-demiplan-examine"),
"/Textures/Interface/VerbIcons/dot.svg.192dpi.png"); //TODO custom icon
}
private FormattedMessage GetDemiplanExamine(CP14DemiplaneDataComponent comp)
{
var msg = new FormattedMessage();
if (!_proto.TryIndex(comp.Location, out var indexedLocation))
return msg;
msg.AddMarkupOrThrow(
indexedLocation.Name is not null/* && _random.Prob(indexedLocation.ExamineProb)*/
? Loc.GetString("cp14-demiplane-examine-title", ("location", Loc.GetString(indexedLocation.Name)))
: Loc.GetString("cp14-demiplane-examine-title-unknown"));
List<LocId> modifierNames = new();
foreach (var modifier in comp.SelectedModifiers)
{
if (!_proto.TryIndex(modifier, out var indexedModifier))
continue;
//if (!_random.Prob(indexedModifier.ExamineProb)) //temp disable
// continue;
if (indexedModifier.Name is null)
continue;
if (modifierNames.Contains(indexedModifier.Name.Value))
continue;
modifierNames.Add(indexedModifier.Name.Value);
}
if (modifierNames.Count > 0)
{
msg.AddMarkupOrThrow("\n" + Loc.GetString("cp14-demiplane-examine-modifiers"));
foreach (var name in modifierNames)
{
msg.AddMarkupOrThrow("\n- " + Loc.GetString(name));
}
}
return msg;
}
private void UpdateGeneration(float frameTime)
{
_expeditionQueue.Process();
foreach (var (job, cancelToken) in _expeditionJobs.ToArray())
{
switch (job.Status)
{
case JobStatus.Finished:
_expeditionJobs.Remove((job, cancelToken));
break;
}
}
}
/// <summary>
/// Generates a new random demiplane based on the specified parameters
/// </summary>
public void SpawnRandomDemiplane(ProtoId<CP14DemiplaneLocationPrototype> location, List<ProtoId<CP14DemiplaneModifierPrototype>> modifiers, out Entity<CP14DemiplaneComponent>? demiplane, out MapId mapId)
{
var mapUid = _mapSystem.CreateMap(out mapId, runMapInit: false);
var demiComp = EntityManager.EnsureComponent<CP14DemiplaneComponent>(mapUid);
demiplane = (mapUid, demiComp);
var cancelToken = new CancellationTokenSource();
var job = new CP14SpawnRandomDemiplaneJob(
JobMaxTime,
EntityManager,
_logManager,
_proto,
_dungeon,
_metaData,
_mapSystem,
mapUid,
mapId,
location,
modifiers,
_random.Next(-10000, 10000),
cancelToken.Token);
_expeditionJobs.Add((job, cancelToken));
_expeditionQueue.EnqueueJob(job);
}
/// <summary>
/// Returns a suitable demiplane location for the specified difficulty level.
/// </summary>
public CP14DemiplaneLocationPrototype GenerateDemiplaneLocation(int level)
{
CP14DemiplaneLocationPrototype? selectedConfig = null;
HashSet<CP14DemiplaneLocationPrototype> suitableConfigs = new();
foreach (var locationConfig in _proto.EnumeratePrototypes<CP14DemiplaneLocationPrototype>())
{
suitableConfigs.Add(locationConfig);
}
while (suitableConfigs.Count > 0)
{
var randomConfig = _random.Pick(suitableConfigs);
//LevelRange filter
if (level < randomConfig.Levels.Min || level > randomConfig.Levels.Max)
{
suitableConfigs.Remove(randomConfig);
continue;
}
selectedConfig = randomConfig;
break;
}
if (selectedConfig is null)
throw new Exception($"No suitable demiplane location config found for level {level}!");
return selectedConfig;
}
/// <summary>
/// Returns a set of modifiers under the specified difficulty level that are appropriate for the specified demiplane location
/// </summary>
public List<CP14DemiplaneModifierPrototype> GenerateDemiplaneModifiers(
int level,
CP14DemiplaneLocationPrototype location,
Dictionary<ProtoId<CP14DemiplaneModifierCategoryPrototype>,float> modifierLimits)
{
List<CP14DemiplaneModifierPrototype> selectedModifiers = new();
//Modifier generation
Dictionary<CP14DemiplaneModifierPrototype, float> suitableModifiersWeights = new();
foreach (var modifier in _proto.EnumeratePrototypes<CP14DemiplaneModifierPrototype>())
{
var passed = true;
//Random prob filter
if (passed)
{
if (!_random.Prob(modifier.GenerationProb))
{
passed = false;
}
}
//Levels filter
if (passed)
{
if (level < modifier.Levels.Min || level > modifier.Levels.Max)
{
passed = false;
}
}
//Tag blacklist filter
foreach (var configTag in location.Tags)
{
if (modifier.BlacklistTags.Count != 0 && modifier.BlacklistTags.Contains(configTag))
{
passed = false;
break;
}
}
//Tag required filter
if (passed)
{
foreach (var reqTag in modifier.RequiredTags)
{
if (!location.Tags.Contains(reqTag))
{
passed = false;
break;
}
}
}
if (passed)
suitableModifiersWeights.Add(modifier, modifier.GenerationWeight);
}
//Limits calculation
Dictionary<ProtoId<CP14DemiplaneModifierCategoryPrototype>, float> limits = new();
foreach (var limit in modifierLimits)
{
limits.Add(limit.Key, limit.Value);
}
while (suitableModifiersWeights.Count > 0)
{
var selectedModifier = ModifierPick(suitableModifiersWeights, _random);
//Fill demiplane under limits
var passed = true;
foreach (var category in selectedModifier.Categories)
{
if (!limits.ContainsKey(category.Key))
{
suitableModifiersWeights.Remove(selectedModifier);
passed = false;
break;
}
if (limits[category.Key] - category.Value < 0)
{
suitableModifiersWeights.Remove(selectedModifier);
passed = false;
break;
}
}
if (!passed)
continue;
selectedModifiers.Add(selectedModifier);
foreach (var category in selectedModifier.Categories)
{
limits[category.Key] -= category.Value;
}
if (selectedModifier.Unique)
suitableModifiersWeights.Remove(selectedModifier);
}
return selectedModifiers;
}
/// <summary>
/// Optimization moment: avoid re-indexing for weight selection
/// </summary>
private static CP14DemiplaneModifierPrototype ModifierPick(Dictionary<CP14DemiplaneModifierPrototype, float> weights, IRobustRandom random)
{
var picks = weights;
var sum = picks.Values.Sum();
var accumulated = 0f;
var rand = random.NextFloat() * sum;
foreach (var (key, weight) in picks)
{
accumulated += weight;
if (accumulated >= rand)
{
return key;
}
}
// Shouldn't happen
throw new InvalidOperationException($"Invalid weighted pick in CP14DemiplanSystem.Generation!");
}
}
public sealed class CP14DemiplaneGenerationCatchAttemptEvent : EntityEventArgs
{
public bool Handled = false;
public Entity<CP14DemiplaneComponent>? Demiplane;
}

View File

@@ -1,99 +0,0 @@
using Content.Shared._CP14.Demiplane.Components;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Mobs.Systems;
using Content.Shared.SSDIndicator;
namespace Content.Server._CP14.Demiplane;
public sealed partial class CP14DemiplaneSystem
{
private readonly TimeSpan _checkFrequency = TimeSpan.FromSeconds(15f);
private TimeSpan _nextCheckTime = TimeSpan.Zero;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
private void InitStabilization()
{
_nextCheckTime = _timing.CurTime + _checkFrequency;
SubscribeLocalEvent<CP14DemiplaneDestroyWithoutStabilizationComponent, MapInitEvent>(OnStabilizationMapInit);
}
private void OnStabilizationMapInit(Entity<CP14DemiplaneDestroyWithoutStabilizationComponent> ent,
ref MapInitEvent args)
{
ent.Comp.EndProtectionTime = _timing.CurTime + ent.Comp.ProtectedSpawnTime;
}
private void UpdateStabilization(float frameTime)
{
if (_timing.CurTime < _nextCheckTime)
return;
_nextCheckTime = _timing.CurTime + _checkFrequency;
HashSet<EntityUid> stabilizedMaps = new();
var query = EntityQueryEnumerator<CP14DemiplaneStabilizerComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var stabilizer, out var transform))
{
var map = transform.MapUid;
if (map is null)
continue;
if (!stabilizer.Enabled)
continue;
if (stabilizer.RequireAlive && !(_mobState.IsAlive(uid) || _mobState.IsCritical(uid)))
continue;
if (stabilizedMaps.Contains(map.Value))
continue;
if (TryComp(uid, out SSDIndicatorComponent? ssd) && ssd.IsSSD)
continue;
stabilizedMaps.Add(map.Value);
}
var query2 = EntityQueryEnumerator<CP14DemiplaneComponent, CP14DemiplaneDestroyWithoutStabilizationComponent>();
while (query2.MoveNext(out var uid, out var demiplane, out var stabilization))
{
if (_timing.CurTime < stabilization.EndProtectionTime)
continue;
if (!stabilizedMaps.Contains(uid))
DeleteDemiplane((uid, demiplane));
}
}
public void DeleteAllDemiplanes(bool safe = true)
{
var query = EntityQueryEnumerator<CP14DemiplaneComponent>();
while (query.MoveNext(out var uid, out var demiplane))
{
DeleteDemiplane((uid, demiplane), safe);
}
}
public void DeleteDemiplane(Entity<CP14DemiplaneComponent> demiplane, bool safe = false)
{
var query = EntityQueryEnumerator<CP14DemiplaneForceExtractComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var extract, out var xform))
{
if (!extract.Enabled)
continue;
if (!TryTeleportOutDemiplane(demiplane, uid))
continue;
if (!safe)
_damageable.TryChangeDamage(uid, extract.ExtractDamage);
}
QueueDel(demiplane);
}
}

View File

@@ -1,186 +0,0 @@
using Content.Server._CP14.Demiplane.Components;
using Content.Server.Chat.Managers;
using Content.Server.Chat.Systems;
using Content.Server.Flash;
using Content.Server.Procedural;
using Content.Shared._CP14.Demiplane;
using Content.Shared._CP14.Demiplane.Components;
using Content.Shared.Chat;
using Content.Shared.Popups;
using Robust.Server.Audio;
using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server._CP14.Demiplane;
public sealed partial class CP14DemiplaneSystem : CP14SharedDemiplaneSystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly DungeonSystem _dungeon = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly FlashSystem _flash = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
private EntityQuery<CP14DemiplaneComponent> _demiplaneQuery;
public override void Initialize()
{
base.Initialize();
_demiplaneQuery = GetEntityQuery<CP14DemiplaneComponent>();
InitGeneration();
InitConnections();
InitStabilization();
InitEchoes();
InitDestruction();
SubscribeLocalEvent<CP14DemiplaneComponent, ComponentShutdown>(OnDemiplanShutdown);
SubscribeLocalEvent<CP14SpawnOutOfDemiplaneComponent, MapInitEvent>(OnSpawnOutOfDemiplane);
}
private void OnSpawnOutOfDemiplane(Entity<CP14SpawnOutOfDemiplaneComponent> ent, ref MapInitEvent args)
{
//Check if entity is in demiplane
var map = Transform(ent).MapUid;
if (!_demiplaneQuery.TryComp(map, out var demiplane))
return;
//Get random exit demiplane point and spawn entity there
if (demiplane.ExitPoints.Count == 0)
return;
var exit = _random.Pick(demiplane.ExitPoints);
var coordinates = Transform(exit).Coordinates;
var proto = ent.Comp.Proto;
if (proto is null)
proto = MetaData(ent).EntityPrototype?.ID;
Spawn(proto, coordinates);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdateGeneration(frameTime);
UpdateStabilization(frameTime);
UpdateDestruction(frameTime);
}
/// <summary>
/// Teleports the entity inside the demiplane, to one of the random entry points.
/// </summary>
/// <param name="demiplane">The demiplane the entity will be teleported to</param>
/// <param name="entity">The entity to be teleported</param>
/// <returns></returns>
public override bool TryTeleportIntoDemiplane(Entity<CP14DemiplaneComponent> demiplane, EntityUid? entity)
{
if (entity is null)
return false;
if (!TryGetDemiplaneEntryPoint(demiplane, out var entryPoint) || entryPoint is null)
{
Log.Error($"{entity} cant get in demiplane {demiplane}: no active entry points!");
return false;
}
TeleportEntityToCoordinate(entity.Value, Transform(entryPoint.Value).Coordinates, demiplane.Comp.ArrivalSound);
return true;
}
/// <summary>
/// Simple teleportation, with common special effects for all the game's teleportation mechanics
/// </summary>
/// <param name="entity"></param>
/// <param name="coordinates"></param>
/// <param name="sound"></param>
public void TeleportEntityToCoordinate(EntityUid? entity, EntityCoordinates coordinates, SoundSpecifier? sound = null)
{
if (entity is null)
return;
_flash.Flash(entity.Value, null, null, TimeSpan.FromSeconds(3f), 0.5f);
_transform.SetCoordinates(entity.Value, coordinates);
_audio.PlayGlobal(sound, entity.Value);
}
/// <summary>
/// Teleports an entity from the demiplane to the real world, to one of the random exit points in the real world.
/// </summary>
/// <param name="demiplane">The demiplane from which the entity will be teleported</param>
/// <param name="entity">An entity that will be teleported into the real world. This entity must be in the demiplane, otherwise the function will not work.</param>
/// <returns></returns>
public override bool TryTeleportOutDemiplane(Entity<CP14DemiplaneComponent> demiplane, EntityUid? entity)
{
if (entity is null)
return false;
if (Transform(entity.Value).MapUid != demiplane.Owner)
return false;
if (!TryGetDemiplaneExitPoint(demiplane, out var connection) || connection is null)
{
Log.Error($"{entity} cant get out of demiplane {demiplane}: no active connections!");
return false;
}
TeleportEntityToCoordinate(entity.Value, Transform(connection.Value).Coordinates, demiplane.Comp.DepartureSound);
return true;
}
private void OnDemiplanShutdown(Entity<CP14DemiplaneComponent> demiplane, ref ComponentShutdown args)
{
//We stop asynchronous generation of a demiplane early if for some reason this demiplane is deleted before generation is complete
foreach (var (job, cancelToken) in _expeditionJobs.ToArray())
{
if (job.DemiplaneMapUid == demiplane.Owner)
{
cancelToken.Cancel();
_expeditionJobs.Remove((job, cancelToken));
}
}
foreach (var exit in demiplane.Comp.ExitPoints)
{
RemoveDemiplaneRandomExitPoint(demiplane, exit);
}
foreach (var entry in demiplane.Comp.EntryPoints)
{
RemoveDemiplaneRandomEntryPoint(demiplane, entry);
}
}
private void DemiplaneAnnounce(EntityUid mapUid, string text)
{
var mapId = Comp<MapComponent>(mapUid).MapId;
_chatManager.ChatMessageToManyFiltered(
Filter.BroadcastMap(mapId),
ChatChannel.Radio,
text,
text,
_mapManager.GetMapEntityId(mapId),
false,
true,
null);
}
}

View File

@@ -1,19 +0,0 @@
using Content.Server._CP14.DemiplaneTraveling;
namespace Content.Server._CP14.Demiplane.Components;
/// <summary>
/// Demiplane Core - stores a position on the demiplane map to mark it as “passed” when all conditions are met
/// </summary>
[RegisterComponent, Access(typeof(CP14DemiplaneSystem), typeof(CP14StationDemiplaneMapSystem))]
public sealed partial class CP14DemiplaneCoreComponent : Component
{
[DataField]
public EntityUid? Demiplane;
[DataField]
public EntityUid? Station;
[DataField]
public Vector2i Position;
}

View File

@@ -1,21 +0,0 @@
using Content.Server._CP14.DemiplaneTraveling;
using Content.Shared._CP14.Demiplane.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Demiplane.Components;
/// <summary>
/// Stores all the information needed to generate a new demiplane
/// </summary>
[RegisterComponent, Access(typeof(CP14DemiplaneSystem), typeof(CP14StationDemiplaneMapSystem))]
public sealed partial class CP14DemiplaneDataComponent : Component
{
[DataField]
public ProtoId<CP14DemiplaneLocationPrototype>? Location;
[DataField]
public List<ProtoId<CP14DemiplaneModifierPrototype>> SelectedModifiers = new();
[DataField]
public List<EntProtoId> AutoRifts = new() { "CP14DemiplaneTimedRadiusPassway", "CP14DemiplanRiftCore" };
}

View File

@@ -1,19 +0,0 @@
using Content.Server._CP14.DemiplaneTraveling;
namespace Content.Server._CP14.Demiplane.Components;
/// <summary>
/// The existence of an entity with this component will block the discovery of a particular coordinate in the demiplane navigation map.
/// </summary>
[RegisterComponent, Access(typeof(CP14StationDemiplaneMapSystem), typeof(CP14DemiplaneSystem))]
public sealed partial class CP14DemiplaneMapNodeBlockerComponent : Component
{
[DataField]
public EntityUid? Station = null;
[DataField]
public Vector2i Position = new (0,0);
[DataField]
public int IncreaseNodeDifficulty = 0;
}

View File

@@ -1,23 +0,0 @@
using Content.Shared._CP14.Demiplane.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Demiplane.Components;
/// <summary>
/// Fills the DemiplaneDataComponent with random modifiers and location
/// </summary>
[RegisterComponent, Access(typeof(CP14DemiplaneSystem))]
public sealed partial class CP14DemiplaneRandomGeneratorComponent : Component
{
[DataField]
public bool OverrideLocation = false;
/// <summary>
/// Demiplane Difficulty Level. By design, the plan so far is for a framework of 1 to 10, but technically could support more.
/// </summary>
[DataField(required: true)]
public int Level = 1;
[DataField(required: true)]
public Dictionary<ProtoId<CP14DemiplaneModifierCategoryPrototype>, float> Limits = new();
}

View File

@@ -1,9 +0,0 @@
namespace Content.Server._CP14.Demiplane.Components;
/// <summary>
/// Open demiplane from using in hand
/// </summary>
[RegisterComponent, Access(typeof(CP14DemiplaneSystem))]
public sealed partial class CP14DemiplaneUsingOpenComponent : Component
{
}

View File

@@ -1,16 +0,0 @@
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Demiplane.Components;
/// <summary>
/// Creates an entity on demiplane exit points when that entity appears.
/// </summary>
[RegisterComponent]
public sealed partial class CP14SpawnOutOfDemiplaneComponent : Component
{
/// <summary>
/// If null, the ProtoId of this entity is taken from the entity itself.
/// </summary>
[DataField]
public EntProtoId? Proto;
}

View File

@@ -1,123 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Procedural;
using Content.Shared._CP14.Demiplane.Prototypes;
using Content.Shared.Atmos;
using Content.Shared.Gravity;
using Content.Shared.Procedural;
using Robust.Shared.CPUJob.JobQueues;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Demiplane.Jobs;
public sealed class CP14SpawnRandomDemiplaneJob : Job<bool>
{
private readonly IEntityManager _entManager;
//private readonly IGameTiming _timing;
private readonly IPrototypeManager _prototypeManager;
//private readonly AnchorableSystem _anchorable;
private readonly DungeonSystem _dungeon;
private readonly MetaDataSystem _metaData;
//private readonly SharedTransformSystem _xforms;
private readonly SharedMapSystem _map;
private readonly ProtoId<CP14DemiplaneLocationPrototype> _config;
private readonly List<ProtoId<CP14DemiplaneModifierPrototype>> _modifiers;
private readonly int _seed;
public readonly EntityUid DemiplaneMapUid;
private readonly MapId _demiplaneMapId;
private readonly ISawmill _sawmill;
public CP14SpawnRandomDemiplaneJob(
double maxTime,
IEntityManager entManager,
ILogManager logManager,
IPrototypeManager protoManager,
DungeonSystem dungeon,
MetaDataSystem metaData,
SharedMapSystem map,
EntityUid demiplaneMapUid,
MapId demiplaneMapId,
ProtoId<CP14DemiplaneLocationPrototype> config,
List<ProtoId<CP14DemiplaneModifierPrototype>> modifiers,
int seed,
CancellationToken cancellation = default) : base(maxTime, cancellation)
{
_entManager = entManager;
_prototypeManager = protoManager;
_dungeon = dungeon;
_metaData = metaData;
_map = map;
DemiplaneMapUid = demiplaneMapUid;
_demiplaneMapId = demiplaneMapId;
_config = config;
_modifiers = modifiers;
_seed = seed;
_sawmill = logManager.GetSawmill("cp14_demiplane_job");
}
protected override async Task<bool> Process()
{
_sawmill.Debug($"Spawning demiplane `{_config.Id}` with seed {_seed}");
var gridComp = _entManager.EnsureComponent<MapGridComponent>(DemiplaneMapUid);
MetaDataComponent? metadata = null;
DungeonConfigPrototype dungeonConfig = new();
_metaData.SetEntityName(DemiplaneMapUid, $"Demiplane {_config.Id} - {_seed}");
//Setup demiplane config
var expeditionConfig = _prototypeManager.Index(_config);
var indexedLocation = _prototypeManager.Index(expeditionConfig.LocationConfig);
dungeonConfig.Layers.AddRange(indexedLocation.Layers);
dungeonConfig.ReserveTiles = indexedLocation.ReserveTiles;
//Add map components
_entManager.AddComponents(DemiplaneMapUid, expeditionConfig.Components);
//Apply modifiers
foreach (var modifier in _modifiers)
{
if (!_prototypeManager.TryIndex(modifier, out var indexedModifier))
continue;
if (indexedModifier.Layers != null)
dungeonConfig.Layers.AddRange(indexedModifier.Layers);
if (indexedModifier.Components != null)
_entManager.AddComponents(DemiplaneMapUid, indexedModifier.Components);
_sawmill.Debug($"Added modifier: {_seed} - {modifier.Id}");
}
//Setup gravity
var gravity = _entManager.EnsureComponent<GravityComponent>(DemiplaneMapUid);
gravity.Enabled = true;
_entManager.Dirty(DemiplaneMapUid, gravity, metadata);
// Setup default atmos
var moles = new float[Atmospherics.AdjustedNumberOfGases];
moles[(int) Gas.Oxygen] = 21.824779f;
moles[(int) Gas.Nitrogen] = 82.10312f;
var mixture = new GasMixture(moles, Atmospherics.T20C);
_entManager.System<AtmosphereSystem>().SetMapAtmosphere(DemiplaneMapUid, false, mixture);
_map.InitializeMap(_demiplaneMapId);
_map.SetPaused(_demiplaneMapId, false);
//Spawn modified config
_dungeon.GenerateDungeon(dungeonConfig,
DemiplaneMapUid,
gridComp,
Vector2i.Zero,
_seed); //TODO: Transform to Async
return true;
}
}

View File

@@ -1,30 +0,0 @@
using Content.Server._CP14.Demiplane;
using Content.Shared._CP14.Demiplane.Components;
using Robust.Shared.Map.Components;
namespace Content.Server._CP14.DemiplaneAdmin;
public sealed partial class CP14DemiplaneAdminSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14DemiplaneGenerationCatchAttemptEvent>(OnAdminDemiplaneCatch);
}
private void OnAdminDemiplaneCatch(CP14DemiplaneGenerationCatchAttemptEvent ev)
{
if (ev.Handled)
return;
var query = EntityQueryEnumerator<CP14DemiplaneRiftCatcherComponent, MapComponent, CP14DemiplaneComponent>();
while (query.MoveNext(out var uid, out var catcher, out var map, out var demiplane))
{
ev.Demiplane = (uid, demiplane);
ev.Handled = true;
RemCompDeferred(uid, catcher);
return;
}
}
}

View File

@@ -1,9 +0,0 @@
namespace Content.Server._CP14.DemiplaneAdmin;
/// <summary>
/// This demiplane can be added to a map by the admins, which will redirect the next opened demiplane key to that map
/// </summary>
[RegisterComponent]
public sealed partial class CP14DemiplaneRiftCatcherComponent : Component
{
}

View File

@@ -1,229 +0,0 @@
using Content.Server._CP14.Demiplane;
using Content.Server._CP14.RoundEnd;
using Content.Server.Interaction;
using Content.Server.Mind;
using Content.Server.Popups;
using Content.Shared._CP14.Demiplane;
using Content.Shared._CP14.Demiplane.Components;
using Content.Shared._CP14.DemiplaneTraveling;
using Content.Shared._CP14.Religion.Components;
using Content.Shared.Ghost;
using Content.Shared.Item;
using Content.Shared.Movement.Pulling.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server._CP14.DemiplaneTraveling;
public sealed partial class CP14DemiplaneTravelingSystem : EntitySystem
{
[Dependency] private readonly CP14DemiplaneSystem _demiplan = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly InteractionSystem _interaction = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14DemiplaneRadiusTimedPasswayComponent, MapInitEvent>(RadiusMapInit);
SubscribeLocalEvent<CP14MonolithTimedPasswayComponent, MapInitEvent>(MonolithMapInit);
SubscribeLocalEvent<CP14DemiplaneRiftOpenedComponent, CP14DemiplanPasswayUseDoAfter>(OnOpenRiftInteractDoAfter);
}
// !!!SHITCODE WARNING!!!
// This whole module is saturated with shitcode, code duplication and other delights. Why? Because.
//TODO: Refactor this shitcode
public override void Update(float frameTime)
{
base.Update(frameTime);
DemiplaneTeleportUpdate();
var query = EntityQueryEnumerator<CP14MonolithTimedPasswayComponent>();
while (query.MoveNext(out var uid, out var passWay))
{
if (_timing.CurTime < passWay.NextTimeTeleport)
continue;
passWay.NextTimeTeleport = _timing.CurTime + passWay.Delay;
//Get all teleporting entities
HashSet<EntityUid> teleportedEnts = new();
var nearestEnts = _lookup.GetEntitiesInRange(uid, passWay.Radius);
foreach (var ent in nearestEnts)
{
if (HasComp<GhostComponent>(ent))
continue;
if (HasComp<CP14ReligionEntityComponent>(ent)) //TODO: make some generic way to whitelist entities from teleporting
continue;
if (!_mind.TryGetMind(ent, out var mindId, out var mind))
continue;
teleportedEnts.Add(ent);
}
while (teleportedEnts.Count > passWay.MaxEntities)
{
teleportedEnts.Remove(_random.Pick(teleportedEnts));
}
//Aaaand teleport it
var monoliths = EntityQueryEnumerator<CP14MagicContainerRoundFinisherComponent>();
while (monoliths.MoveNext(out var monolithUid, out var monolith))
{
var coord = Transform(monolithUid).Coordinates;
//Shitcode select first one
foreach (var ent in teleportedEnts)
{
if (TryComp<PullerComponent>(ent, out var puller))
_demiplan.TeleportEntityToCoordinate(puller.Pulling, coord);
_demiplan.TeleportEntityToCoordinate(ent, coord);
_audio.PlayPvs(passWay.ArrivalSound, ent);
}
break;
}
_audio.PlayPvs(passWay.DepartureSound, Transform(uid).Coordinates);
QueueDel(uid);
}
}
private void DemiplaneTeleportUpdate()
{
//Radius passway
var query = EntityQueryEnumerator<CP14DemiplaneRadiusTimedPasswayComponent, CP14DemiplaneRiftComponent>();
while (query.MoveNext(out var uid, out var passWay, out var rift))
{
if (_timing.CurTime < passWay.NextTimeTeleport)
continue;
passWay.NextTimeTeleport = _timing.CurTime + passWay.Delay;
//Get all teleporting entities
HashSet<EntityUid> teleportedEnts = new();
var nearestEnts = _lookup.GetEntitiesInRange(uid, passWay.Radius);
foreach (var ent in nearestEnts)
{
if (HasComp<GhostComponent>(ent))
continue;
if (HasComp<CP14ReligionEntityComponent>(ent)) //TODO: make some generic way to whitelist entities from teleporting
continue;
if (!_mind.TryGetMind(ent, out var mindId, out var mind))
continue;
if (!_interaction.InRangeUnobstructed(ent, uid))
continue;
// Talking swords are not party members, and should not be teleported separately from their wielders
if (HasComp<ItemComponent>(ent))
continue;
teleportedEnts.Add(ent);
}
while (teleportedEnts.Count > passWay.MaxEntities)
{
teleportedEnts.Remove(_random.Pick(teleportedEnts));
}
//Aaaand teleport it
var map = Transform(uid).MapUid;
if (TryComp<CP14DemiplaneComponent>(map, out var demiplan))
{
if (!_demiplan.TryGetDemiplaneExitPoint((map.Value, demiplan), out _))
break;
foreach (var ent in teleportedEnts) //We in demiplan, tp OUT
{
if (TryComp<PullerComponent>(ent, out var puller))
_demiplan.TryTeleportOutDemiplane((map.Value, demiplan), puller.Pulling);
_demiplan.TryTeleportOutDemiplane((map.Value, demiplan), ent);
_audio.PlayPvs(passWay.ArrivalSound, ent);
}
}
else
{
if (rift.Demiplane is not null &&
TryComp<CP14DemiplaneComponent>(rift.Demiplane.Value, out var riftDemiplane))
{
if (!_demiplan.TryGetDemiplaneEntryPoint((rift.Demiplane.Value, riftDemiplane), out _))
break;
foreach (var ent in teleportedEnts) //We out demiplan, tp IN
{
if (TryComp<PullerComponent>(ent, out var puller))
_demiplan.TryTeleportIntoDemiplane((rift.Demiplane.Value, riftDemiplane), puller.Pulling);
_demiplan.TryTeleportIntoDemiplane((rift.Demiplane.Value, riftDemiplane), ent);
_audio.PlayPvs(passWay.ArrivalSound, ent);
}
}
}
_audio.PlayPvs(passWay.DepartureSound, Transform(uid).Coordinates);
QueueDel(uid);
}
}
private void RadiusMapInit(Entity<CP14DemiplaneRadiusTimedPasswayComponent> radiusPassWay, ref MapInitEvent args)
{
radiusPassWay.Comp.NextTimeTeleport = _timing.CurTime + radiusPassWay.Comp.Delay;
}
private void MonolithMapInit(Entity<CP14MonolithTimedPasswayComponent> radiusPassWay, ref MapInitEvent args)
{
radiusPassWay.Comp.NextTimeTeleport = _timing.CurTime + radiusPassWay.Comp.Delay;
}
private void OnOpenRiftInteractDoAfter(Entity<CP14DemiplaneRiftOpenedComponent> passWay,
ref CP14DemiplanPasswayUseDoAfter args)
{
if (args.Cancelled || args.Handled)
return;
var used = false;
var map = Transform(passWay).MapUid;
if (TryComp<CP14DemiplaneComponent>(map, out var demiplan))
{
if (TryComp<PullerComponent>(args.User, out var puller))
_demiplan.TryTeleportOutDemiplane((map.Value, demiplan), puller.Pulling);
used = _demiplan.TryTeleportOutDemiplane((map.Value, demiplan), args.User);
}
else
{
if (TryComp<CP14DemiplaneRiftComponent>(passWay, out var exitPoint) && exitPoint.Demiplane is not null &&
TryComp<CP14DemiplaneComponent>(exitPoint.Demiplane.Value, out var exitDemiplane))
{
if (TryComp<PullerComponent>(args.User, out var puller))
_demiplan.TryTeleportIntoDemiplane((exitPoint.Demiplane.Value, exitDemiplane), puller.Pulling);
used = _demiplan.TryTeleportIntoDemiplane((exitPoint.Demiplane.Value, exitDemiplane), args.User);
}
}
if (used)
{
_audio.PlayPvs(passWay.Comp.DepartureSound, Transform(passWay).Coordinates);
_audio.PlayPvs(passWay.Comp.ArrivalSound, args.User);
if (passWay.Comp.MaxUse > 0)
{
passWay.Comp.MaxUse--;
if (passWay.Comp.MaxUse == 0)
QueueDel(passWay);
}
}
args.Handled = true;
}
}

View File

@@ -1,356 +0,0 @@
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<CP14StationDemiplaneMapComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<CP14DemiplaneNavigationMapComponent, CP14DemiplaneMapEjectMessage>(DemiplaneEjectAttempt);
SubscribeLocalEvent<CP14DemiplaneNavigationMapComponent, CP14DemiplaneMapRevokeMessage>(DemiplaneRevokeAttempt);
SubscribeLocalEvent<CP14DemiplaneNavigationMapComponent, BeforeActivatableUIOpenEvent>(OnBeforeActivatableUiOpen);
SubscribeLocalEvent<CP14DemiplaneMapNodeBlockerComponent, ComponentShutdown>(OnNodeBlockerShutdown);
SubscribeLocalEvent<CP14DemiplaneCoreComponent, MapInitEvent>(OnCoreInit);
SubscribeLocalEvent<CP14DemiplaneCoreComponent, DestructionEventArgs>(OnCoreShutdown);
}
private void OnCoreShutdown(Entity<CP14DemiplaneCoreComponent> ent, ref DestructionEventArgs args)
{
if (TryComp<CP14DemiplaneComponent>(ent.Comp.Demiplane, out var demiplane))
{
_demiplane.StartDestructDemiplane((ent.Comp.Demiplane.Value, demiplane));
}
var query = EntityQueryEnumerator<CP14StationDemiplaneMapComponent>();
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<CP14DemiplaneCoreComponent> core, ref MapInitEvent args)
{
core.Comp.Demiplane = Transform(core).MapUid;
if (!TryComp<CP14DemiplaneMapNodeBlockerComponent>(core.Comp.Demiplane, out var demiBlocker))
return;
core.Comp.Station = demiBlocker.Station;
core.Comp.Position = demiBlocker.Position;
EnsureComp<CP14DemiplaneMapNodeBlockerComponent>(core, out var coreBlocker);
coreBlocker.Position = core.Comp.Position;
coreBlocker.Station = core.Comp.Station;
coreBlocker.IncreaseNodeDifficulty = 0;
}
private void OnNodeBlockerShutdown(Entity<CP14DemiplaneMapNodeBlockerComponent> ent, ref ComponentShutdown args)
{
if (!TryComp<CP14StationDemiplaneMapComponent>(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<CP14DemiplaneNavigationMapComponent> ent, ref CP14DemiplaneMapEjectMessage args)
{
var station = _station.GetOwningStation(ent, Transform(ent));
if (!TryComp<CP14StationDemiplaneMapComponent>(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<CP14DemiplaneDataComponent>(key, out var demiData))
{
demiData.Location = node.LocationConfig;
demiData.SelectedModifiers.AddRange(node.Modifiers);
}
EnsureComp<CP14DemiplaneMapNodeBlockerComponent>(key, out var blockerComp);
blockerComp.Position = args.Position;
blockerComp.Station = station;
}
private void DemiplaneRevokeAttempt(Entity<CP14DemiplaneNavigationMapComponent> ent, ref CP14DemiplaneMapRevokeMessage args)
{
var station = _station.GetOwningStation(ent, Transform(ent));
var query = EntityQueryEnumerator<CP14DemiplaneMapNodeBlockerComponent>();
while (query.MoveNext(out var uid, out var blocker))
{
if (blocker.Station != station)
continue;
if (blocker.Position != args.Position)
continue;
if (!TryComp<CP14StationDemiplaneMapComponent>(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<CP14DemiplaneComponent>(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<CP14DemiplaneNavigationMapComponent> ent,
ref BeforeActivatableUIOpenEvent args)
{
var station = _station.GetOwningStation(ent, Transform(ent));
if (!TryComp<CP14StationDemiplaneMapComponent>(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<CP14StationDemiplaneMapComponent> ent, ref MapInitEvent args)
{
GenerateDemiplaneMap(ent);
}
private void GenerateDemiplaneMap(Entity<CP14StationDemiplaneMapComponent> ent)
{
ent.Comp.Nodes.Clear();
ent.Comp.Edges.Clear();
var allSpecials = _proto.EnumeratePrototypes<CP14SpecialDemiplanePrototype>().ToList();
_random.Shuffle(allSpecials);
var grid = new Dictionary<Vector2i, CP14DemiplaneMapNode>();
//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<Vector2i>();
foreach (var special in allSpecials)
{
if (placedSpecials >= specialCount)
break;
var specialLevel = special.Levels.Next(_random);
var possiblePositions = new List<Vector2i>();
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<Vector2i>();
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<ProtoId<CP14DemiplaneModifierCategoryPrototype>, 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<CP14StationDemiplaneMapComponent> 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<CP14DemiplaneMapNodeBlockerComponent>();
while (query.MoveNext(out var uid, out var blocker))
{
if (!TryComp<CP14StationDemiplaneMapComponent>(blocker.Station, out var stationMap))
continue;
if (!stationMap.Nodes.TryGetValue(blocker.Position, out var node))
continue;
node.InUsing = true;
}
}
}

View File

@@ -0,0 +1,60 @@
using System.Linq;
using System.Numerics;
using Content.Server._CP14.GameTicking.Rules.Components;
using Content.Server._CP14.Procedural;
using Content.Server.GameTicking.Rules;
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Systems;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
using Content.Shared.GameTicking.Components;
using Robust.Shared.Map;
namespace Content.Server._CP14.GameTicking.Rules;
public sealed class CP14ExpeditionToWindlandsRule : GameRuleSystem<CP14ExpeditionToWindlandsRuleComponent>
{
[Dependency] private readonly ShuttleSystem _shuttles = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
[Dependency] private readonly CP14LocationGenerationSystem _generation = default!;
private ISawmill _sawmill = default!;
public override void Initialize()
{
base.Initialize();
_sawmill = _logManager.GetSawmill("cp14_expedition_to_windlands_rule");
}
protected override void Started(EntityUid uid,
CP14ExpeditionToWindlandsRuleComponent component,
GameRuleComponent gameRule,
GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
var station = _station.GetStations().First();
if (!TryComp<StationDataComponent>(station, out var stationData))
{
_sawmill.Error($"Station {station} does not have a StationDataComponent.");
return;
}
var largestStationGrid = _station.GetLargestGrid(stationData);
if (largestStationGrid is null)
{
_sawmill.Error($"Station {station} does not have a grid.");
return;
}
EnsureComp<ShuttleComponent>(largestStationGrid.Value, out var shuttleComp);
var windlands = _mapSystem.CreateMap(out var mapId, runMapInit: false);
_generation.GenerateLocation(windlands, mapId, component.Location, component.Modifiers);
_shuttles.FTLToCoordinates(largestStationGrid.Value, shuttleComp, new EntityCoordinates(windlands, Vector2.Zero), 0f, 0f, component.FloatingTime);
}
}

View File

@@ -0,0 +1,116 @@
using System.Linq;
using Content.Server._CP14.GameTicking.Rules.Components;
using Content.Server.GameTicking.Rules;
using Content.Server.Shuttles.Events;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
using Content.Shared.GameTicking.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server._CP14.GameTicking.Rules;
public sealed class CP14CrashingShipRule : GameRuleSystem<CP14CrashingShipRuleComponent>
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly IRobustRandom _random = default!;
private ISawmill _sawmill = default!;
public override void Initialize()
{
base.Initialize();
_sawmill = _logManager.GetSawmill("cp14_crashing_ship_rule");
SubscribeLocalEvent<CP14CrashingShipComponent, FTLCompletedEvent>(OnFTLCompleted);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdateExplosions(frameTime);
}
protected override void Started(EntityUid uid,
CP14CrashingShipRuleComponent component,
GameRuleComponent gameRule,
GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
var station = _station.GetStations().First();
if (!TryComp<StationDataComponent>(station, out var stationData))
{
_sawmill.Error($"Station {station} does not have a StationDataComponent.");
return;
}
var largestStationGrid = _station.GetLargestGrid(stationData);
if (largestStationGrid is null)
{
_sawmill.Error($"Station {station} does not have a grid.");
return;
}
component.StartExplosionTime += _timing.CurTime;
component.Ship = largestStationGrid.Value;
}
private void OnFTLCompleted(Entity<CP14CrashingShipComponent> ent, ref FTLCompletedEvent args)
{
SpawnRandomExplosion(ent, ent.Comp.FinalExplosionProto, 10);
RemCompDeferred<CP14CrashingShipComponent>(ent);
}
private void UpdateExplosions(float frameTime)
{
var ruleQuery = EntityQueryEnumerator<CP14CrashingShipRuleComponent>();
while (ruleQuery.MoveNext(out var uid, out var rule))
{
if (!rule.PendingExplosions)
continue;
if (_timing.CurTime < rule.StartExplosionTime)
continue;
if (rule.Ship is null)
continue;
AddComp<CP14CrashingShipComponent>(rule.Ship.Value);
rule.PendingExplosions = false;
}
var query = EntityQueryEnumerator<CP14CrashingShipComponent>();
while (query.MoveNext(out var uid, out var ship))
{
if (_timing.CurTime < ship.NextExplosionTime)
continue;
ship.NextExplosionTime = _timing.CurTime + TimeSpan.FromSeconds(_random.Next(2, 10));
SpawnRandomExplosion((uid, ship), ship.ExplosionProto, 1);
}
}
private void SpawnRandomExplosion(Entity<CP14CrashingShipComponent> grid, EntProtoId explosionProto, int count)
{
var station = _station.GetOwningStation(grid);
if (station is null)
return;
TryFindRandomTileOnStation((station.Value, Comp<StationDataComponent>(station.Value)),
out var tile,
out var targetGrid,
out var targetCoords);
for (var i = 0; i < count; i++)
{
Spawn(explosionProto, targetCoords);
}
}
}

View File

@@ -0,0 +1,19 @@
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.GameTicking.Rules.Components;
/// <summary>
///When attached to shuttle, start firebombing it until FTL ends.
/// </summary>
[RegisterComponent, Access(typeof(CP14CrashingShipRule))]
public sealed partial class CP14CrashingShipComponent : Component
{
[DataField]
public TimeSpan NextExplosionTime = TimeSpan.Zero;
[DataField]
public EntProtoId ExplosionProto = "CP14ShipExplosion";
[DataField]
public EntProtoId FinalExplosionProto = "CP14ShipExplosionBig";
}

View File

@@ -0,0 +1,17 @@
namespace Content.Server._CP14.GameTicking.Rules.Components;
/// <summary>
/// A rule that assigns common goals to different roles. Common objectives are generated once at the beginning of a round and are shared between players.
/// </summary>
[RegisterComponent, Access(typeof(CP14CrashingShipRule))]
public sealed partial class CP14CrashingShipRuleComponent : Component
{
[DataField]
public EntityUid? Ship;
[DataField]
public bool PendingExplosions = true;
[DataField]
public TimeSpan StartExplosionTime = TimeSpan.FromMinutes(1);
}

View File

@@ -0,0 +1,20 @@
using Content.Shared._CP14.Procedural.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.GameTicking.Rules.Components;
/// <summary>
/// A rule that assigns common goals to different roles. Common objectives are generated once at the beginning of a round and are shared between players.
/// </summary>
[RegisterComponent, Access(typeof(CP14ExpeditionToWindlandsRule))]
public sealed partial class CP14ExpeditionToWindlandsRuleComponent : Component
{
[DataField]
public ProtoId<CP14ProceduralLocationPrototype> Location = "T1GrasslandIsland";
[DataField]
public List<ProtoId<CP14ProceduralModifierPrototype>> Modifiers = [];
[DataField]
public float FloatingTime = 120;
}

View File

@@ -0,0 +1,9 @@
namespace Content.Server._CP14.Procedural.GlobalWorld.Components;
/// <summary>
/// Added to cards on which procedural generation occurs, and removed when generation is successfully completed.
/// </summary>
[RegisterComponent]
public sealed partial class CP14ActiveJobGenerationComponent : Component
{
}

View File

@@ -0,0 +1,109 @@
using System.Threading;
using Content.Server._CP14.Procedural.GlobalWorld.Components;
using Content.Server.Procedural;
using Content.Server.Station.Systems;
using Content.Shared._CP14.Procedural.Prototypes;
using Content.Shared.Procedural;
using JetBrains.Annotations;
using Robust.Shared.CPUJob.JobQueues;
using Robust.Shared.CPUJob.JobQueues.Queues;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server._CP14.Procedural;
public sealed class CP14LocationGenerationSystem : EntitySystem
{
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly DungeonSystem _dungeon = default!;
[Dependency] private readonly IRobustRandom _random = default!;
private const double JobMaxTime = 0.002;
private readonly JobQueue _expeditionQueue = new();
private readonly List<(CP14SpawnProceduralLocationJob Job, CancellationTokenSource CancelToken)> _jobs = new();
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14ActiveJobGenerationComponent, ComponentShutdown>(OnGenerationShutdown);
}
public override void Update(float frameTime)
{
_expeditionQueue.Process();
foreach (var (job, cancelToken) in _jobs.ToArray())
{
switch (job.Status)
{
case JobStatus.Finished:
if (job.JobName is not null)
{
var ev = new CP14LocationGeneratedEvent(job.JobName);
RaiseLocalEvent(ev);
}
RemComp<CP14ActiveJobGenerationComponent>(job.MapUid);
_jobs.Remove((job, cancelToken));
break;
}
}
}
/// <summary>
/// Generates a new procedural location on the specified map and coordinates.
/// Essentially, this is a wrapper for _dungeon.GenerateDungeon, which collects the necessary settings for the
/// dungeon based on the location and modifiers.
/// </summary>
public void GenerateLocation(EntityUid mapUid, MapId mapId, ProtoId<CP14ProceduralLocationPrototype> location, List<ProtoId<CP14ProceduralModifierPrototype>> modifiers, Vector2i position = new(), int? seed = null, string? jobName = null)
{
var cancelToken = new CancellationTokenSource();
EnsureComp<CP14ActiveJobGenerationComponent>(mapUid);
var job = new CP14SpawnProceduralLocationJob(
JobMaxTime,
EntityManager,
_logManager,
_proto,
_dungeon,
_mapSystem,
mapUid,
mapId,
position,
seed ?? _random.Next(-10000, 10000),
location,
modifiers,
jobName,
cancelToken.Token);
_jobs.Add((job, cancelToken));
_expeditionQueue.EnqueueJob(job);
}
private void OnGenerationShutdown(Entity<CP14ActiveJobGenerationComponent> ent, ref ComponentShutdown args)
{
//We stop asynchronous generation of a location early if for some reason this location is deleted before generation is complete
foreach (var (job, cancelToken) in _jobs.ToArray())
{
if (job.MapUid == ent.Owner)
{
cancelToken.Cancel();
_jobs.Remove((job, cancelToken));
}
}
}
}
[PublicAPI]
public sealed class CP14LocationGeneratedEvent(string jobName) : EntityEventArgs
{
public string JobName = jobName;
}

View File

@@ -0,0 +1,99 @@
using System.Threading;
using System.Threading.Tasks;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Procedural;
using Content.Shared._CP14.Procedural.Prototypes;
using Content.Shared.Atmos;
using Content.Shared.Gravity;
using Content.Shared.Procedural;
using Content.Shared.Procedural.DungeonLayers;
using Robust.Shared.CPUJob.JobQueues;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Procedural;
public sealed class CP14SpawnProceduralLocationJob(
double maxTime,
IEntityManager entManager,
ILogManager logManager,
IPrototypeManager protoManager,
DungeonSystem dungeon,
SharedMapSystem map,
EntityUid mapUid,
MapId mapId,
Vector2i position,
int seed,
ProtoId<CP14ProceduralLocationPrototype> config,
List<ProtoId<CP14ProceduralModifierPrototype>> modifiers,
string? jobName = null,
CancellationToken cancellation = default)
: Job<bool>(maxTime, cancellation)
{
public readonly EntityUid MapUid = mapUid;
public string? JobName = jobName;
private readonly ISawmill _sawmill = logManager.GetSawmill("cp14_procedural_location_job");
protected override async Task<bool> Process()
{
_sawmill.Debug($"Spawning procedural location `{config.Id}` with seed {seed}");
var gridComp = entManager.EnsureComponent<MapGridComponent>(MapUid);
MetaDataComponent? metadata = null;
DungeonConfigPrototype dungeonConfig = new();
//Boilerplate: reserve all old grid tiles
dungeonConfig.Layers.Add(new CP14ReserveGrid());
//Setup location config
var locationConfig = protoManager.Index(config);
var indexedLocation = protoManager.Index(locationConfig.LocationConfig);
dungeonConfig.Layers.AddRange(indexedLocation.Layers);
dungeonConfig.ReserveTiles = indexedLocation.ReserveTiles;
//Add map components
entManager.AddComponents(MapUid, locationConfig.Components);
//Apply modifiers
foreach (var modifier in modifiers)
{
if (!protoManager.TryIndex(modifier, out var indexedModifier))
continue;
if (indexedModifier.Layers != null)
dungeonConfig.Layers.AddRange(indexedModifier.Layers);
if (indexedModifier.Components != null)
entManager.AddComponents(MapUid, indexedModifier.Components);
_sawmill.Debug($"Added modifier: {seed} - {modifier.Id}");
}
//Setup gravity
var gravity = entManager.EnsureComponent<GravityComponent>(MapUid);
gravity.Enabled = true;
entManager.Dirty(MapUid, gravity, metadata);
// Setup default atmos
var moles = new float[Atmospherics.AdjustedNumberOfGases];
moles[(int)Gas.Oxygen] = 21.824779f;
moles[(int)Gas.Nitrogen] = 82.10312f;
var mixture = new GasMixture(moles, Atmospherics.T20C);
entManager.System<AtmosphereSystem>().SetMapAtmosphere(MapUid, false, mixture);
if (!map.IsInitialized(mapId))
map.InitializeMap(mapId);
map.SetPaused(mapId, false);
//Spawn modified config
await WaitAsyncTask(dungeon.GenerateDungeonAsync(dungeonConfig,
MapUid,
gridComp,
position,
seed));
return true;
}
}

View File

@@ -0,0 +1,86 @@
using System.Threading.Tasks;
using Content.Server.Parallax;
using Content.Shared.Maps;
using Content.Shared.Parallax.Biomes;
using Content.Shared.Procedural;
using Content.Shared.Procedural.DungeonLayers;
using Content.Shared.Procedural.PostGeneration;
using Robust.Shared.Map;
using Robust.Shared.Utility;
namespace Content.Server.Procedural.DungeonJob;
public sealed partial class DungeonJob
{
/// <summary>
/// <see cref="BiomeDunGen"/>
/// </summary>
private async Task PostGen(CP14BiomeDunGen dunGen,
List<Dungeon> dungeons,
HashSet<Vector2i> reservedTiles,
Random random)
{
if (!_prototype.TryIndex(dunGen.BiomeTemplate, out var indexedBiome))
return;
var biomeSystem = _entManager.System<BiomeSystem>();
var seed = random.Next();
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
foreach (var dun in dungeons)
{
foreach (var node in dun.AllTiles)
{
var tileRef = _maps.GetTileRef(_gridUid, _grid, node);
if (reservedTiles.Contains(node))
continue;
if (dunGen.TileMask is not null)
{
if (!dunGen.TileMask.Contains(((ContentTileDefinition)_tileDefManager[tileRef.Tile.TypeId]).ID))
continue;
}
if (dunGen.TileMask is not null)
{
if (!dunGen.TileMask.Contains(((ContentTileDefinition) _tileDefManager[tileRef.Tile.TypeId]).ID))
continue;
}
// Need to set per-tile to override data.
if (biomeSystem.TryGetTile(node, indexedBiome.Layers, seed, (_gridUid, _grid), out var tile))
{
_maps.SetTile(_gridUid, _grid, node, tile.Value);
}
if (biomeSystem.TryGetDecals(node, indexedBiome.Layers, seed, (_gridUid, _grid), out var decals))
{
foreach (var decal in decals)
{
_decals.TryAddDecal(decal.ID, new EntityCoordinates(_gridUid, decal.Position), out _);
}
}
if (biomeSystem.TryGetEntity(node, indexedBiome.Layers, tile ?? tileRef.Tile, seed, (_gridUid, _grid), out var entityProto))
{
var ent = _entManager.SpawnEntity(entityProto, new EntityCoordinates(_gridUid, node + _grid.TileSizeHalfVector));
var xform = xformQuery.Get(ent);
if (!xform.Comp.Anchored)
{
_transform.AnchorEntity(ent, xform);
}
// TODO: Engine bug with SpawnAtPosition
DebugTools.Assert(xform.Comp.Anchored);
}
await SuspendDungeon();
if (!ValidateResume())
return;
}
}
}
}

View File

@@ -0,0 +1,20 @@
using System.Threading.Tasks;
using Content.Shared.Procedural;
using Content.Shared.Procedural.DungeonLayers;
namespace Content.Server.Procedural.DungeonJob;
public sealed partial class DungeonJob
{
private async Task PostGen(CP14ReserveGrid dunGen,
HashSet<Vector2i> reservedTiles)
{
var tiles = _maps.GetAllTilesEnumerator(_gridUid, _grid);
while (tiles.MoveNext(out var tileRef))
{
var node = tileRef.Value.GridIndices;
reservedTiles.Add(node);
}
}
}

View File

@@ -0,0 +1,307 @@
using System.Diagnostics;
using System.Linq;
using Content.Server._CP14.Procedural.GlobalWorld.Components;
using Content.Server.Station.Components;
using Content.Shared._CP14.Procedural.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server._CP14.Procedural.GlobalWorld;
public sealed partial class CP14GlobalWorldSystem
{
private void GenerateGlobalWorldMap(Entity<CP14StationGlobalWorldComponent> ent)
{
ent.Comp.Nodes.Clear();
ent.Comp.Edges.Clear();
//For first - check station integration, and put station into (0,0) global map position
if (TryComp<CP14StationGlobalWorldIntegrationComponent>(ent, out var integration) &&
TryComp<StationDataComponent>(ent, out var stationData))
{
var largestStationGrid = _station.GetLargestGrid(stationData);
Debug.Assert(largestStationGrid is not null);
var mapId = _transform.GetMapId(largestStationGrid.Value);
var zeroNode =
new CP14GlobalWorldNode
{
MapUid = mapId,
LocationConfig = integration.Location,
Modifiers = integration.Modifiers,
Level = 0,
};
GenerateNodeData(zeroNode);
ent.Comp.Nodes.Add(
Vector2i.Zero,
zeroNode
);
}
else
{
var zeroNode = new CP14GlobalWorldNode();
GenerateNodeData(zeroNode);
ent.Comp.Nodes.Add(Vector2i.Zero, zeroNode);
}
//Generate nodes with random data until limits
while (ent.Comp.Nodes.Count < ent.Comp.LocationCount + 1)
{
// Get a random existing node
var randomNode = _random.Pick(ent.Comp.Nodes);
var randomNodePosition = randomNode.Key;
// Find a random empty adjacent position
var directions = new[] { new Vector2i(1, 0), new Vector2i(-1, 0), new Vector2i(0, 1), new Vector2i(0, -1) };
var emptyPositions = directions
.Select(dir => randomNodePosition + dir)
.Where(pos => !ent.Comp.Nodes.ContainsKey(pos))
.ToList();
if (emptyPositions.Count == 0)
continue;
var newPosition = emptyPositions[Random.Shared.Next(emptyPositions.Count)];
// Add the new node and connect it with an edge
var newNode = new CP14GlobalWorldNode
{
Level = Math.Abs(newPosition.X) + Math.Abs(newPosition.Y),
};
GenerateNodeData(newNode);
ent.Comp.Nodes.Add(newPosition, newNode);
ent.Comp.Edges.Add((randomNodePosition, newPosition));
//Add connections to each other
if (_proto.TryIndex(newNode.LocationConfig, out var indexedNewNodeLocation) && _proto.TryIndex(randomNode.Value.LocationConfig, out var indexedRandomNodeLocation))
{
newNode.Modifiers.Add(indexedNewNodeLocation.Connection);
randomNode.Value.Modifiers.Add(indexedRandomNodeLocation.Connection);
}
}
}
private void GenerateNodeData(CP14GlobalWorldNode node,
bool overrideLocation = false,
bool clearOldModifiers = false)
{
if (node.LocationConfig is null || overrideLocation)
{
var location = SelectLocation(node.Level);
node.LocationConfig ??= location;
}
if (!_proto.TryIndex(node.LocationConfig, out var indexedLocation))
throw new Exception($"No location config found for node at level {node.Level}!");
var limits = new Dictionary<ProtoId<CP14ProceduralModifierCategoryPrototype>, float>
{
{ "Danger", Math.Max(node.Level * 0.2f, 0.5f) },
{ "GhostRoleDanger", 1f },
{ "Reward", Math.Max(node.Level * 0.3f, 0.5f) },
{ "Ore", Math.Max(node.Level * 0.5f, 1f) },
{ "Fun", 1f },
{ "Weather", 1f },
{ "MapLight", 1f },
};
var mods = SelectModifiers(node.Level, indexedLocation, limits);
if (clearOldModifiers)
node.Modifiers.Clear();
foreach (var mod in mods)
{
node.Modifiers.Add(mod);
}
}
/// <summary>
/// Returns a suitable location for the specified difficulty level.
/// </summary>
public CP14ProceduralLocationPrototype SelectLocation(int level)
{
CP14ProceduralLocationPrototype? selectedConfig = null;
HashSet<CP14ProceduralLocationPrototype> suitableConfigs = new();
foreach (var locationConfig in _proto.EnumeratePrototypes<CP14ProceduralLocationPrototype>())
{
suitableConfigs.Add(locationConfig);
}
while (suitableConfigs.Count > 0)
{
var randomConfig = _random.Pick(suitableConfigs);
var passed = true;
//Random prob filter
if (passed)
{
if (!_random.Prob(randomConfig.GenerationProb))
{
passed = false;
}
}
//Levels filter
if (passed)
{
if (level < randomConfig.Levels.Min || level > randomConfig.Levels.Max)
{
passed = false;
}
}
if (!passed)
{
suitableConfigs.Remove(randomConfig);
continue;
}
selectedConfig = randomConfig;
break;
}
if (selectedConfig is null)
throw new Exception($"No suitable procedural location config found for level {level}!");
return selectedConfig;
}
/// <summary>
/// Returns a set of modifiers under the specified difficulty level that are appropriate for the specified location
/// </summary>
public List<CP14ProceduralModifierPrototype> SelectModifiers(
int level,
CP14ProceduralLocationPrototype location,
Dictionary<ProtoId<CP14ProceduralModifierCategoryPrototype>, float> modifierLimits)
{
List<CP14ProceduralModifierPrototype> selectedModifiers = new();
//Modifier generation
Dictionary<CP14ProceduralModifierPrototype, float> suitableModifiersWeights = new();
foreach (var modifier in _proto.EnumeratePrototypes<CP14ProceduralModifierPrototype>())
{
var passed = true;
//Random prob filter
if (passed)
{
if (!_random.Prob(modifier.GenerationProb))
{
passed = false;
}
}
//Levels filter
if (passed)
{
if (level < modifier.Levels.Min || level > modifier.Levels.Max)
{
passed = false;
}
}
//Tag blacklist filter
foreach (var configTag in location.Tags)
{
if (modifier.BlacklistTags.Count != 0 && modifier.BlacklistTags.Contains(configTag))
{
passed = false;
break;
}
}
//Tag required filter
if (passed)
{
foreach (var reqTag in modifier.RequiredTags)
{
if (!location.Tags.Contains(reqTag))
{
passed = false;
break;
}
}
}
if (passed)
suitableModifiersWeights.Add(modifier, modifier.GenerationWeight);
}
//Limits calculation
Dictionary<ProtoId<CP14ProceduralModifierCategoryPrototype>, float> limits = new();
foreach (var limit in modifierLimits)
{
limits.Add(limit.Key, limit.Value);
}
while (suitableModifiersWeights.Count > 0)
{
var selectedModifier = ModifierPick(suitableModifiersWeights, _random);
//Fill location under limits
var passed = true;
foreach (var category in selectedModifier.Categories)
{
if (!limits.ContainsKey(category.Key))
{
suitableModifiersWeights.Remove(selectedModifier);
passed = false;
break;
}
if (limits[category.Key] - category.Value < 0)
{
suitableModifiersWeights.Remove(selectedModifier);
passed = false;
break;
}
}
if (!passed)
continue;
selectedModifiers.Add(selectedModifier);
foreach (var category in selectedModifier.Categories)
{
limits[category.Key] -= category.Value;
}
if (selectedModifier.Unique)
suitableModifiersWeights.Remove(selectedModifier);
}
return selectedModifiers;
}
/// <summary>
/// Optimization moment: avoid re-indexing for weight selection
/// </summary>
private static CP14ProceduralModifierPrototype ModifierPick(
Dictionary<CP14ProceduralModifierPrototype, float> weights,
IRobustRandom random)
{
var picks = weights;
var sum = picks.Values.Sum();
var accumulated = 0f;
var rand = random.NextFloat() * sum;
foreach (var (key, weight) in picks)
{
accumulated += weight;
if (accumulated >= rand)
{
return key;
}
}
// Shouldn't happen
throw new InvalidOperationException($"Invalid weighted pick in CP14DemiplanSystem.Generation!");
}
}

View File

@@ -0,0 +1,148 @@
using Content.Server._CP14.Procedural.GlobalWorld.Components;
using Content.Server.Shuttles.Systems;
using Content.Server.Station.Events;
using Content.Server.Station.Systems;
using Content.Shared.Teleportation.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server._CP14.Procedural.GlobalWorld;
public sealed partial class CP14GlobalWorldSystem : EntitySystem
{
[Dependency] private readonly ShuttleSystem _shuttles = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly CP14LocationGenerationSystem _generation = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly MetaDataSystem _meta = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly LinkedEntitySystem _link = default!;
private ISawmill _sawmill = default!;
public override void Initialize()
{
base.Initialize();
_sawmill = _logManager.GetSawmill("cp14_global_world");
SubscribeLocalEvent<CP14StationGlobalWorldComponent, StationPostInitEvent>(OnIntegratedPostInit);
SubscribeLocalEvent<CP14LocationGeneratedEvent>(OnLocationGenerated);
SubscribeLocalEvent<CP14StationGlobalWorldComponent, CP14GlobalWorldGeneratedEvent>(OnGlobalWorldGenerated);
}
private void OnLocationGenerated(CP14LocationGeneratedEvent args)
{
var query = EntityQueryEnumerator<CP14StationGlobalWorldComponent>();
while (query.MoveNext(out var ent, out var comp))
{
//Theres no support for multiple GlobalWorld
if (comp.LocationInGeneration.Contains(args.JobName))
{
comp.LocationInGeneration.Remove(args.JobName);
_sawmill.Debug($"Location {args.JobName} generated successfully. Remaining: {comp.LocationInGeneration.Count}");
}
if (comp.LocationInGeneration.Count == 0)
{
//All locations are generated, we can now spawn the global world map
_sawmill.Debug("All locations generated, spawning global world map.");
var ev = new CP14GlobalWorldGeneratedEvent();
RaiseLocalEvent(ent, ev);
}
}
}
private void OnIntegratedPostInit(Entity<CP14StationGlobalWorldComponent> ent, ref StationPostInitEvent args)
{
GenerateGlobalWorldMap(ent);
SpawnGlobalWorldMap(ent);
}
private void OnGlobalWorldGenerated(Entity<CP14StationGlobalWorldComponent> ent, ref CP14GlobalWorldGeneratedEvent ev)
{
ConnectGlobalWorldMap(ent);
}
private void SpawnGlobalWorldMap(Entity<CP14StationGlobalWorldComponent> ent)
{
foreach (var (position, node) in ent.Comp.Nodes)
{
if (node.LocationConfig is null)
continue;
if (node.MapUid is null)
{
_map.CreateMap(out var newMapId, runMapInit: false);
node.MapUid = newMapId;
}
var mapId = node.MapUid.Value;
var mapUid = _map.GetMap(mapId);
var jobName = $"job_GW_{position}";
ent.Comp.LocationInGeneration.Add(jobName);
_generation.GenerateLocation(
mapUid,
mapId,
node.LocationConfig.Value,
node.Modifiers,
jobName: jobName
);
// Avoid renaming the settlement
if (position != Vector2i.Zero)
{
var newName = $"{position} - {node.LocationConfig.Value}";
_meta.SetEntityName(mapUid, newName);
}
}
}
private void ConnectGlobalWorldMap(Entity<CP14StationGlobalWorldComponent> ent)
{
foreach (var edge in ent.Comp.Edges)
{
var firstNodeMapUid = ent.Comp.Nodes[edge.Item1].MapUid;
var secondNodeMapUid = ent.Comp.Nodes[edge.Item2].MapUid;
EntityUid? firstConnector = null;
EntityUid? secondConnector = null;
//Get random connector from map
var query = EntityQueryEnumerator<CP14GlobalWorldConnectorComponent>();
while (query.MoveNext(out var uid, out _))
{
if (_transform.GetMapId(uid) == firstNodeMapUid)
firstConnector = uid;
if (_transform.GetMapId(uid) == secondNodeMapUid)
secondConnector = uid;
}
if (firstConnector == null || secondConnector == null)
{
_sawmill.Error($"Failed to find connectors for edge {edge.Item1} - {edge.Item2}. " +
$"First: {firstConnector}, Second: {secondConnector}");
continue;
}
var firstPortal = Spawn("CP14LocationPassway", Transform(firstConnector.Value).Coordinates);
var secondPortal = Spawn("CP14LocationPassway", Transform(secondConnector.Value).Coordinates);
_link.TryLink(firstPortal, secondPortal, true);
Del(firstConnector);
Del(secondConnector);
}
}
}
public sealed class CP14GlobalWorldGeneratedEvent : EntityEventArgs
{
}

View File

@@ -0,0 +1,9 @@
namespace Content.Server._CP14.Procedural.GlobalWorld.Components;
/// <summary>
/// The GlobalWorld system connects worlds by creating connecting portals at the location of these markers.
/// </summary>
[RegisterComponent, Access(typeof(CP14GlobalWorldSystem))]
public sealed partial class CP14GlobalWorldConnectorComponent : Component
{
}

View File

@@ -0,0 +1,37 @@
using Content.Shared._CP14.Procedural.Prototypes;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Procedural.GlobalWorld.Components;
/// <summary>
/// Generates the surrounding procedural world on the game map, surrounding the mapped settlement.
/// </summary>
[RegisterComponent, Access(typeof(CP14GlobalWorldSystem))]
public sealed partial class CP14StationGlobalWorldComponent : Component
{
[DataField]
public Dictionary<Vector2i, CP14GlobalWorldNode> Nodes = new();
[DataField]
public HashSet<(Vector2i, Vector2i)> Edges = new();
[DataField]
public int LocationCount = 5;
/// <summary>
/// A list of jobName names that are waiting for generation to complete. This is a hack, but I don't know a better way to do it.
/// </summary>
[DataField]
public List<string> LocationInGeneration = new();
}
[Serializable]
public sealed class CP14GlobalWorldNode()
{
public MapId? MapUid;
public int Level = 0;
public ProtoId<CP14ProceduralLocationPrototype>? LocationConfig;
public List<ProtoId<CP14ProceduralModifierPrototype>> Modifiers = new();
}

View File

@@ -0,0 +1,20 @@
using Content.Shared._CP14.Procedural.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Procedural.GlobalWorld.Components;
/// <summary>
/// Generates the surrounding procedural world on the game map, surrounding the mapped settlement.
/// </summary>
[RegisterComponent, Access(typeof(CP14GlobalWorldSystem))]
public sealed partial class CP14StationGlobalWorldIntegrationComponent : Component
{
[DataField(required: true)]
public ProtoId<CP14ProceduralLocationPrototype> Location;
[DataField]
public List<ProtoId<CP14ProceduralModifierPrototype>> Modifiers = [];
[DataField]
public Vector2i GenerationOffset = Vector2i.Zero;
}

View File

@@ -1,11 +1,9 @@
using Content.Server._CP14.Demiplane;
namespace Content.Server._CP14.RoundEnd;
/// <summary>
///
/// </summary>
[RegisterComponent, Access(typeof(CP14DemiplaneSystem))]
[RegisterComponent]
public sealed partial class CP14MagicContainerRoundFinisherComponent : Component
{
}

View File

@@ -1,6 +1,4 @@
using Content.Server._CP14.Demiplane;
using Content.Server.Chat.Systems;
using Content.Server.GameTicking;
using Content.Server.RoundEnd;
using Content.Shared._CP14.MagicEnergy;
using Content.Shared.GameTicking;
@@ -15,7 +13,6 @@ public sealed partial class CP14RoundEndSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly CP14DemiplaneSystem _demiplane = default!;
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
@@ -49,7 +46,6 @@ public sealed partial class CP14RoundEndSystem : EntitySystem
if (_roundEndMoment > _timing.CurTime)
return;
_demiplane.DeleteAllDemiplanes();
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("cp14-round-end"),
announcementSound: new SoundPathSpecifier("/Audio/_CP14/Announce/event_boom.ogg"));
_roundEnd.EndRound();

View File

@@ -18,13 +18,13 @@ public sealed partial class FTLMapComponent : Component
/// What parallax to use for the background, immediately gets deffered to ParallaxComponent.
/// </summary>
[DataField]
public string Parallax = "CP14Ocean"; //CP14 parallax replacement
public string Parallax = "Sky"; //CP14 parallax replacement
/// <summary>
/// CP14 FTL map ambient color
/// </summary>
[DataField]
public Color AmbientColor = new(34, 90, 122);
public Color AmbientColor = new(47, 51, 54);
/// <summary>
/// Can FTL on this map only be done to beacons.

View File

@@ -1,74 +0,0 @@
using Content.Shared._CP14.Demiplane.Components;
using Content.Shared._CP14.DemiplaneTraveling;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.Demiplane;
public abstract partial class CP14SharedDemiplaneSystem : EntitySystem
{
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14DemiplaneRiftOpenedComponent, InteractHandEvent>(OnDemiplanePasswayInteract);
SubscribeLocalEvent<CP14DemiplaneHintComponent, MapInitEvent>(OnDemiplaneHintMapInit);
}
private void OnDemiplaneHintMapInit(Entity<CP14DemiplaneHintComponent> ent, ref MapInitEvent args)
{
var query = EntityQueryEnumerator<CP14DemiplaneRiftOpenedComponent, TransformComponent>();
var xformHint = Transform(ent);
var hintPos = _transform.GetWorldPosition(xformHint);
while (query.MoveNext(out _, out _, out var xformRift))
{
if (xformRift.MapUid != xformHint.MapUid)
continue;
var riftPos = _transform.GetWorldPosition(xformRift);
//Calculate the rotation
Angle angle = new(riftPos - hintPos);
_transform.SetWorldRotation(ent, angle + Angle.FromDegrees(90));
break;
}
}
private void OnDemiplanePasswayInteract(Entity<CP14DemiplaneRiftOpenedComponent> passway, ref InteractHandEvent args)
{
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager,
args.User,
passway.Comp.DoAfter,
new CP14DemiplanPasswayUseDoAfter(),
args.Target,
args.Target)
{
BreakOnDamage = true,
BreakOnMove = true,
BreakOnHandChange = true,
NeedHand = true,
MovementThreshold = 0.2f,
});
}
public virtual bool TryTeleportIntoDemiplane(Entity<CP14DemiplaneComponent> demiplane, EntityUid? entity)
{
return true;
}
public virtual bool TryTeleportOutDemiplane(Entity<CP14DemiplaneComponent> demiplane, EntityUid? entity)
{
return true;
}
}
[Serializable, NetSerializable]
public sealed partial class CP14DemiplanPasswayUseDoAfter : SimpleDoAfterEvent
{
}

View File

@@ -1,38 +0,0 @@
using Robust.Shared.Audio;
namespace Content.Shared._CP14.Demiplane.Components;
/// <summary>
/// Designates this entity as holding a demiplane.
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedDemiplaneSystem))]
public sealed partial class CP14DemiplaneComponent : Component
{
/// <summary>
/// All entities in the real world that are connected to this demiplane
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
[DataField]
public HashSet<EntityUid> ExitPoints = new();
/// <summary>
/// All entities in the demiplane in which the objects entered in the demiplane appear
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
[DataField]
public HashSet<EntityUid> EntryPoints = new();
/// <summary>
/// The sound of entering a demiplane, played locally to the player who entered it.
/// Consider more as an intro sound “You have entered the demiplane. Good luck.”
/// </summary>
[DataField("arrivalSound")]
public SoundSpecifier ArrivalSound = new SoundCollectionSpecifier("CP14DemiplaneIntro");
/// <summary>
/// The sound of exiting the demiplane, played locally to the player who exited the demiplane.
/// Consider it more as an ending sound
/// </summary>
[DataField("departureSound")]
public SoundSpecifier DepartureSound = new SoundCollectionSpecifier("CP14DemiplaneIntro");
}

View File

@@ -1,18 +0,0 @@
namespace Content.Shared._CP14.Demiplane.Components;
/// <summary>
/// is automatically delete over time if there are no active stabilizers inside this demiplane.
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedDemiplaneSystem)), AutoGenerateComponentPause]
public sealed partial class CP14DemiplaneDestroyWithoutStabilizationComponent : Component
{
/// <summary>
/// how many time after generation the demiplane cannot be destroyed.
/// </summary>
[DataField]
public TimeSpan ProtectedSpawnTime = TimeSpan.FromMinutes(1);
[DataField]
[AutoPausedField]
public TimeSpan EndProtectionTime = TimeSpan.Zero;
}

View File

@@ -1,22 +0,0 @@
using Content.Shared.Damage;
namespace Content.Shared._CP14.Demiplane.Components;
/// <summary>
/// When closing the demiplane, all entities with this component will be thrown out instead of being deleted.
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedDemiplaneSystem))]
public sealed partial class CP14DemiplaneForceExtractComponent : Component
{
[DataField]
public bool Enabled = true;
[DataField]
public DamageSpecifier ExtractDamage = new()
{
DamageDict = new()
{
{ "Blunt", 111 },
}
};
}

View File

@@ -1,11 +0,0 @@
namespace Content.Shared._CP14.Demiplane.Components;
using Demiplane;
/// <summary>
/// A very small and silly component that simply turns the entity toward the nearest demiplane rift
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedDemiplaneSystem))]
public sealed partial class CP14DemiplaneHintComponent : Component
{
}

View File

@@ -1,29 +0,0 @@
namespace Content.Shared._CP14.Demiplane.Components;
/// <summary>
/// An entity that is the link between the demiplane and the real world. Depending on whether it is in the real world or in the demiplane
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedDemiplaneSystem))]
public sealed partial class CP14DemiplaneRiftComponent : Component
{
[ViewVariables(VVAccess.ReadOnly)]
[DataField]
public EntityUid? Demiplane;
/// <summary>
/// Checks if the map on which this rift is initialized is a demiplane to automatically bind to it. QoL thing.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
[DataField]
public bool TryAutoLinkToMap = true;
/// <summary>
/// will this rift become one of the random entry or exit points of the demiplane
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
[DataField]
public bool ActiveTeleport = true;
[DataField]
public bool DeleteAfterDisconnect = true;
}

View File

@@ -1,17 +0,0 @@
namespace Content.Shared._CP14.Demiplane.Components;
/// <summary>
/// Keeps the demiplanes from being destroyed while they're in it.
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedDemiplaneSystem))]
public sealed partial class CP14DemiplaneStabilizerComponent : Component
{
/// <summary>
/// must be a being and be alive to work as a stabilizer
/// </summary>
[DataField]
public bool RequireAlive = false;
[DataField]
public bool Enabled = true;
}

View File

@@ -1,38 +0,0 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
namespace Content.Shared._CP14.Demiplane.Components;
/// <summary>
/// Destroy demiplane after time
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause, Access(typeof(CP14SharedDemiplaneSystem))]
public sealed partial class CP14DemiplaneTimedDestructionComponent : Component
{
[DataField]
public TimeSpan TimeToDestruction = TimeSpan.FromSeconds(240f);
[DataField, AutoPausedField]
public TimeSpan EndTime = TimeSpan.Zero;
/// <summary>
/// Countdown audio stream.
/// </summary>
[DataField, AutoNetworkedField]
public EntityUid? Stream = null;
/// <summary>
/// Sound that plays when the demiplane start to collapse.
/// </summary>
[DataField]
public SoundSpecifier Sound = new SoundCollectionSpecifier("ExpeditionEnd")
{
Params = AudioParams.Default.WithVolume(-5),
};
/// <summary>
/// Song selected on MapInit so we can predict the audio countdown properly.
/// </summary>
[DataField]
public SoundPathSpecifier? SelectedSong;
}

View File

@@ -1,12 +0,0 @@
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Demiplane.Prototypes;
/// <summary>
///
/// </summary>
[Prototype("cp14DemiplaneModifierCategory")]
public sealed partial class CP14DemiplaneModifierCategoryPrototype : IPrototype
{
[IdDataField] public string ID { get; } = default!;
}

View File

@@ -1,33 +0,0 @@
using Content.Shared.Destructible.Thresholds;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Demiplane.Prototypes;
/// <summary>
/// A prototype “Special Demiplane” that can appear on the demiplane map as the final room of a chain of demiplanes.
/// It is supposed to be used as a special demiplane with special modifiers, and mining resources and
/// equipment from here is the main task of adventurers
/// </summary>
[Prototype("cp14SpecialDemiplane")]
public sealed partial class CP14SpecialDemiplanePrototype : IPrototype
{
[IdDataField] public string ID { get; } = default!;
/// <summary>
/// The difficulty levels at which this location can be generated.
/// </summary>
[DataField(required: true)]
public MinMax Levels = new(1, 10);
/// <summary>
/// The location config that will be used to generate the demiplane. May be null, and if so, the location will be generated using the default way.
/// </summary>
[DataField]
public ProtoId<CP14DemiplaneLocationPrototype>? Location;
/// <summary>
/// Modifiers that will be automatically added to the demiplane when it is generated.
/// </summary>
[DataField]
public List<ProtoId<CP14DemiplaneModifierPrototype>> Modifiers = new();
}

View File

@@ -1,36 +0,0 @@
using System.Numerics;
using Content.Shared._CP14.Demiplane.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.DemiplaneTraveling;
[Serializable, NetSerializable]
public enum CP14DemiplaneMapUiKey
{
Key,
}
[Serializable, NetSerializable]
public sealed class CP14DemiplaneMapUiState(Dictionary<Vector2i, CP14DemiplaneMapNode> nodes, HashSet<(Vector2i, Vector2i)>? edges = null) : BoundUserInterfaceState
{
public Dictionary<Vector2i, CP14DemiplaneMapNode> Nodes = nodes;
public HashSet<(Vector2i, Vector2i)> Edges = edges ?? new();
}
[Serializable, NetSerializable]
public sealed class CP14DemiplaneMapNode(int level, Vector2 uiPosition, bool start, ProtoId<CP14DemiplaneLocationPrototype>? locationConfig = null, List<ProtoId<CP14DemiplaneModifierPrototype>>? modifiers = null)
{
public int Level = level;
public int AdditionalLevel = 0;
public Vector2 UiPosition = uiPosition;
public bool Start = start;
public bool InFrontierZone = false;
public bool InUsing = false;
public bool Completed = start;
public ProtoId<CP14DemiplaneLocationPrototype>? LocationConfig = locationConfig;
public List<ProtoId<CP14DemiplaneModifierPrototype>> Modifiers = modifiers ?? new();
}

View File

@@ -1,30 +0,0 @@
using Content.Shared.Damage;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.DemiplaneTraveling;
/// <summary>
/// Component for opening demiplane map UI and interacting with StationDemiplaneMapComponent
/// </summary>
[RegisterComponent]
public sealed partial class CP14DemiplaneNavigationMapComponent : Component
{
/// <summary>
/// Extracting coordinates from the demiplane node will create this entity and synchronize its modifiers, location and other parameters.
/// </summary>
[DataField]
public EntProtoId KeyProto = "CP14BaseDemiplaneKey";
[DataField]
public SoundSpecifier EjectSound = new SoundPathSpecifier("/Audio/Magic/ethereal_exit.ogg");
[DataField]
public DamageSpecifier RevokeDamage = new()
{
DamageDict = new()
{
{ "Blunt", 100 },
},
};
}

View File

@@ -1,29 +0,0 @@
using Robust.Shared.Audio;
namespace Content.Shared._CP14.DemiplaneTraveling;
/// <summary>
/// teleports a certain number of entities between demiplanes with a delay
/// </summary>
[RegisterComponent, AutoGenerateComponentPause]
public sealed partial class CP14DemiplaneRadiusTimedPasswayComponent : Component
{
[DataField]
public int MaxEntities = 3;
[DataField]
public TimeSpan Delay = TimeSpan.FromSeconds(10f);
[DataField]
public float Radius = 3f;
[DataField]
[AutoPausedField]
public TimeSpan NextTimeTeleport = TimeSpan.Zero;
[DataField("arrivalSound")]
public SoundSpecifier ArrivalSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg");
[DataField("departureSound")]
public SoundSpecifier DepartureSound = new SoundPathSpecifier("/Audio/Effects/teleport_departure.ogg");
}

View File

@@ -1,25 +0,0 @@
using Robust.Shared.Audio;
namespace Content.Shared._CP14.DemiplaneTraveling;
/// <summary>
/// if located on an entity with a CP14DemiplanRiftComponent, allows users to move through that rift via an interaction with doAfter
/// </summary>
[RegisterComponent]
public sealed partial class CP14DemiplaneRiftOpenedComponent : Component
{
/// <summary>
/// The number of teleportations this teleporter can make before disappearing. Use the negative number to make infinite.
/// </summary>
[DataField]
public int MaxUse = -1;
[DataField]
public float DoAfter = 4f;
[DataField("arrivalSound")]
public SoundSpecifier ArrivalSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg");
[DataField("departureSound")]
public SoundSpecifier DepartureSound = new SoundPathSpecifier("/Audio/Effects/teleport_departure.ogg");
}

View File

@@ -1,29 +0,0 @@
using Robust.Shared.Audio;
namespace Content.Shared._CP14.DemiplaneTraveling;
/// <summary>
/// teleports a certain number of entities to coordinate with a delay
/// </summary>
[RegisterComponent, AutoGenerateComponentPause]
public sealed partial class CP14MonolithTimedPasswayComponent : Component
{
[DataField]
public int MaxEntities = 3;
[DataField]
public TimeSpan Delay = TimeSpan.FromSeconds(10f);
[DataField]
public float Radius = 3f;
[DataField]
[AutoPausedField]
public TimeSpan NextTimeTeleport = TimeSpan.Zero;
[DataField("arrivalSound")]
public SoundSpecifier ArrivalSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg");
[DataField("departureSound")]
public SoundSpecifier DepartureSound = new SoundPathSpecifier("/Audio/Effects/teleport_departure.ogg");
}

View File

@@ -1,47 +0,0 @@
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.DemiplaneTraveling;
public abstract partial class CP14SharedStationDemiplaneMapSystem : EntitySystem
{
public static bool NodeInFronrierZone(Dictionary<Vector2i,CP14DemiplaneMapNode> nodes, HashSet<(Vector2i, Vector2i)> edges, Vector2i key)
{
if (!nodes.TryGetValue(key, out var node))
return false;
if (node.Completed || node.Start)
return false;
//return false if no finished or start nodes near
var near = new List<Vector2i>();
foreach (var edge in edges)
{
var node1 = nodes[edge.Item1];
var node2 = nodes[edge.Item2];
if (node1 != node && node2 != node)
continue;
if (node1.Completed || node1.Start || node2.Completed || node2.Start)
{
return true;
}
}
return false;
}
}
[Serializable, NetSerializable]
public sealed class CP14DemiplaneMapEjectMessage(Vector2i position) : BoundUserInterfaceMessage
{
public readonly Vector2i Position = position;
}
[Serializable, NetSerializable]
public sealed class CP14DemiplaneMapRevokeMessage(Vector2i position) : BoundUserInterfaceMessage
{
public readonly Vector2i Position = position;
}

View File

@@ -1,22 +0,0 @@
using Content.Shared.Destructible.Thresholds;
namespace Content.Shared._CP14.DemiplaneTraveling;
/// <summary>
/// A station component that stores information about the current map of demiplanes, their research status and relationships.
/// </summary>
[RegisterComponent]
public sealed partial class CP14StationDemiplaneMapComponent : Component
{
[DataField]
public Dictionary<Vector2i, CP14DemiplaneMapNode> Nodes = new();
[DataField]
public HashSet<(Vector2i, Vector2i)> Edges = new();
/// <summary>
/// Count of special rooms that can be generated in the demiplane map.
/// </summary>
[DataField]
public MinMax Specials = new(30, 30);
}

View File

@@ -19,6 +19,9 @@ public sealed partial class CP14SpellConsumeManaEffect : CP14SpellEffect
var targetEntity = args.Target.Value;
if (!entManager.HasComponent<CP14MagicEnergyContainerComponent>(targetEntity))
return;
var magicEnergy = entManager.System<SharedCP14MagicEnergySystem>();
//First - used object

View File

@@ -1,26 +0,0 @@
using Content.Shared._CP14.Demiplane;
using Content.Shared._CP14.Demiplane.Components;
namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellDemiplaneInfiltration : CP14SpellEffect
{
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
if (args.User is null)
return;
if (!entManager.TryGetComponent<CP14DemiplaneRiftComponent>(args.Target, out var rift))
return;
if (rift.Demiplane is null)
return;
if (!entManager.TryGetComponent<CP14DemiplaneComponent>(rift.Demiplane.Value, out var demiplane))
return;
var demiplaneSystem = entManager.System<CP14SharedDemiplaneSystem>();
demiplaneSystem.TryTeleportIntoDemiplane((rift.Demiplane.Value, demiplane), args.User.Value);
}
}

View File

@@ -4,7 +4,7 @@ using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.ModularCraft.Components;
/// <summary>
/// Adds all details to the item when initializing. This is useful for spawning modular items directly when mapping or as loot in demiplanes.
/// Adds all details to the item when initializing. This is useful for spawning modular items directly when mapping or as loot in dungeons
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedModularCraftSystem))]
public sealed partial class CP14ModularCraftAutoAssembleComponent : Component

View File

@@ -0,0 +1,19 @@
using Content.Shared.Maps;
using Content.Shared.Parallax.Biomes;
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.DungeonLayers;
[Virtual]
public partial class CP14BiomeDunGen : IDunGenLayer
{
[DataField(required: true)]
public ProtoId<BiomeTemplatePrototype> BiomeTemplate;
/// <summary>
/// creates a biome only on the specified tiles
/// </summary>
[DataField]
public HashSet<ProtoId<ContentTileDefinition>>? TileMask;
}

View File

@@ -0,0 +1,11 @@
using Content.Shared.Maps;
using Content.Shared.Parallax.Biomes;
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.DungeonLayers;
[Virtual]
public partial class CP14ReserveGrid : IDunGenLayer
{
}

View File

@@ -4,13 +4,13 @@ using Content.Shared.Tag;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared._CP14.Demiplane.Prototypes;
namespace Content.Shared._CP14.Procedural.Prototypes;
/// <summary>
/// procedural location template. The answer to the question “Where” as far as the combinatorics of the expedition is concerned.
/// procedural location template. The answer to the question “Where” as far as the combinatorics of the generation is concerned.
/// </summary>
[Prototype("cp14DemiplaneLocation")]
public sealed partial class CP14DemiplaneLocationPrototype : IPrototype
[Prototype("cp14Location")]
public sealed partial class CP14ProceduralLocationPrototype : IPrototype
{
[IdDataField] public string ID { get; } = default!;
@@ -18,13 +18,22 @@ public sealed partial class CP14DemiplaneLocationPrototype : IPrototype
/// The difficulty levels at which this location can be generated.
/// </summary>
[DataField]
public MinMax Levels = new(1, 10);
public MinMax Levels = new(0, 10);
[DataField(required: true)]
public ProtoId<DungeonConfigPrototype> LocationConfig;
/// <summary>
/// Components that will be automatically added to the demiplane when it is generated
/// This modifier will be added to all adjacent connected locations leading to this location.
/// </summary>
[DataField(required: true)]
public ProtoId<CP14ProceduralModifierPrototype> Connection;
[DataField]
public float GenerationProb = 1f;
/// <summary>
/// Components that will be automatically added to the location map when it is generated
/// </summary>
[DataField]
public ComponentRegistry Components = new();
@@ -34,13 +43,4 @@ public sealed partial class CP14DemiplaneLocationPrototype : IPrototype
/// </summary>
[DataField]
public List<ProtoId<TagPrototype>> Tags = new();
[DataField]
public LocId? Name;
[DataField]
public float ExamineProb = 0.75f;
[DataField]
public SpriteSpecifier? Icon = null;
}

View File

@@ -0,0 +1,12 @@
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Procedural.Prototypes;
/// <summary>
///
/// </summary>
[Prototype("cp14LocationModifierCategory")]
public sealed partial class CP14ProceduralModifierCategoryPrototype : IPrototype
{
[IdDataField] public string ID { get; } = default!;
}

View File

@@ -3,13 +3,13 @@ using Content.Shared.Procedural;
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Demiplane.Prototypes;
namespace Content.Shared._CP14.Procedural.Prototypes;
/// <summary>
/// Demiplane modifier prototype. The answer to the question “Which” in terms of the combinatorics of demiplane generation is
/// Location modifier prototype. The answer to the question “What's inside" from the point of view of the combinatorics of creating locations
/// </summary>
[Prototype("cp14DemiplaneModifier")]
public sealed partial class CP14DemiplaneModifierPrototype : IPrototype
[Prototype("cp14LocationModifier")]
public sealed partial class CP14ProceduralModifierPrototype : IPrototype
{
[IdDataField] public string ID { get; } = default!;
@@ -23,7 +23,7 @@ public sealed partial class CP14DemiplaneModifierPrototype : IPrototype
/// Each modifier belongs to specific categories. Used by the generator to determine what to generate
/// </summary>
[DataField]
public Dictionary<ProtoId<CP14DemiplaneModifierCategoryPrototype>, float> Categories = new();
public Dictionary<ProtoId<CP14ProceduralModifierCategoryPrototype>, float> Categories = new();
/// <summary>
/// How often can this modifier be generated? Determined by weight from all modifiers available for the location
@@ -40,10 +40,10 @@ public sealed partial class CP14DemiplaneModifierPrototype : IPrototype
public float GenerationProb = 1f;
/// <summary>
/// Can this modifier be generated multiple times within a single demiplane?
/// Can this modifier be generated multiple times within a single location?
/// </summary>
[DataField]
public bool Unique = true;
public bool Unique = false;
/// <summary>
/// Generation layers that will be added to the location generation after the main layers.
@@ -71,7 +71,4 @@ public sealed partial class CP14DemiplaneModifierPrototype : IPrototype
[DataField]
public LocId? Name;
[DataField]
public float ExamineProb = 0.75f;
}

View File

@@ -1,5 +1,2 @@
cp14-gamepreset-resource-harvest = Resource Collection
cp14-gamepreset-resource-harvest-desc = Your expedition has been sent to a dangerous place to collect valuable natural resources. Get the necessary materials and return.
cp14-judge-night-title = Judgment Night
cp14-judge-night-desc = Once a decade, a blood moon rises in the heavens, twisting minds and blackening hearts. On this night, brother goes against brother, blood is shed and terrible crimes are committed. Will the curse of the moon consume you tonight? Will you survive this night of judgment?
cp14-gamemode-survival-title = Survival
cp14-gamemode-survival-description = Your ship is in distress and crashing into wild, dangerous lands. What will you do to survive? Which of you will find the way back to civilization? And who... or what is watching you from the darkness...

View File

@@ -1,5 +1,2 @@
cp14-gamepreset-resource-harvest = Сбор ресурсов
cp14-gamepreset-resource-harvest-desc = Ваша экспедиция была направлена в опасное место для сбора ценных природных ресурсов. Добудьте необходимые материалы, и возвращайтесь.
cp14-judge-night-title = Судная ночь
cp14-judge-night-desc = Раз в десятилетие на небеса восходит кровавая луна, извращая умы и очерняя сердца. В эту ночь брат идет на брата, льется кровь и совершаются ужасные преступления. Поглотит ли вас сегодня проклятье луны? Переживете ли вы эту судную ночь?
cp14-gamemode-survival-title = Выживание
cp14-gamemode-survival-description = Ваш корабль терпит бедствие и падает в дикие, опасные земли. На что вы пойдете, чтобы выжить? Кто из вас найдет путь обратно в цивилизацию? И кто... или что это наблюдает за вами из темноты...

View File

@@ -0,0 +1,911 @@
meta:
format: 7
category: Map
engineVersion: 264.0.0
forkId: ""
forkVersion: ""
time: 08/02/2025 22:54:12
entityCount: 150
maps:
- 1
grids:
- 1
orphans: []
nullspace: []
tilemap:
0: Space
21: CP14FloorBase
20: CP14FloorCobblestone
25: CP14FloorDirt
9: CP14FloorFoundation
13: CP14FloorGrass
14: CP14FloorGrassLight
15: CP14FloorGrassTall
26: CP14FloorMycelium
27: CP14FloorMyceliumLight
10: CP14FloorOakWoodPlanksBig
12: CP14FloorOakWoodPlanksBroken
11: CP14FloorOakWoodPlanksCruciform
22: CP14FloorSnow
23: CP14FloorSnowDeep
24: CP14FloorSnowDeepDeep
17: CP14FloorStonebricks
16: CP14FloorStonebricksSmallCarved1
19: CP14FloorStonebricksSmallCarved2
18: CP14FloorStonebricksSquareCarved
2: FloorAsteroidSand
6: FloorAsteroidSandUnvariantized
5: FloorAsteroidTile
8: FloorBrokenWood
82: FloorShuttleOrange
1: FloorShuttlePurple
89: FloorSteel
7: FloorWood
3: Plating
4: PlatingAsteroid
entities:
- proto: ""
entities:
- uid: 1
components:
- type: MetaData
- type: Transform
- type: Map
mapPaused: True
- type: GridTree
- type: Broadphase
- type: OccluderTree
- type: MapGrid
chunks:
-1,-1:
ind: -1,-1
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAACAA==
version: 7
0,0:
ind: 0,0
tiles: AQAAAAABAAEAAAAAAAABAAAAAAMAFAAAAAACABUAAAAABAAVAAAAAAcAAQAAAAABAAEAAAAAAQABAAAAAAEAFQAAAAAEABUAAAAACwAVAAAAAAUAFQAAAAACAAEAAAAAAQABAAAAAAMAAQAAAAABAAEAAAAAAAAVAAAAAAoAFQAAAAAEABUAAAAABgAVAAAAAAQAFQAAAAACABUAAAAAAQABAAAAAAEAFQAAAAALABUAAAAABQAVAAAAAAQAFQAAAAANABUAAAAACwAVAAAAAAYAAQAAAAABAAEAAAAAAQAVAAAAAAoAFQAAAAAMABUAAAAABgAUAAAAAAQAFAAAAAAEABUAAAAABwAVAAAAAAsAAQAAAAACABUAAAAACAAVAAAAAAsAFAAAAAADABQAAAAABAAVAAAAAAUAFAAAAAACABUAAAAAAgABAAAAAAEAFAAAAAAAABUAAAAADAAVAAAAAAsAFAAAAAABABUAAAAACAAVAAAAAAcAFQAAAAAFAAEAAAAAAAAVAAAAAAIAFQAAAAACABUAAAAABwAUAAAAAAEAFQAAAAALABUAAAAAAQAVAAAAAAoAAQAAAAAAABUAAAAAAgAVAAAAAAQAFQAAAAADABUAAAAAAAAVAAAAAAkAFQAAAAAFABUAAAAAAQABAAAAAAEAFQAAAAAIABUAAAAABQAVAAAAAAEAFQAAAAANABUAAAAACQAVAAAAAAcAFQAAAAADAAEAAAAAAgAVAAAAAAwAFQAAAAANABUAAAAAAQAVAAAAAAgAFQAAAAAGABUAAAAABQABAAAAAAAAAQAAAAACABUAAAAACwAVAAAAAAYAFAAAAAAEABUAAAAADAAVAAAAAAsAFQAAAAAIAAEAAAAAAAABAAAAAAIAAQAAAAABABUAAAAAAgAVAAAAAA0AFQAAAAAHABUAAAAABgABAAAAAAAAAQAAAAABAAEAAAAAAgABAAAAAAIAFQAAAAAHABUAAAAADAAVAAAAAAUAFQAAAAACAAEAAAAAAAABAAAAAAMAAQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAAAAAEAAAAAAgABAAAAAAEAAQAAAAADAAEAAAAAAgABAAAAAAAAAQAAAAACAAEAAAAAAwABAAAAAAMAAQAAAAAAAAEAAAAAAwABAAAAAAMAAQAAAAAAAAEAAAAAAgABAAAAAAIAAQAAAAAAAA0AAAAAAwANAAAAAAMAAQAAAAADAAEAAAAAAgANAAAAAAIAAQAAAAACAAEAAAAAAQANAAAAAAEADQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAACAAEAAAAAAAABAAAAAAEAAQAAAAAAAAEAAAAAAgABAAAAAAMADQAAAAACAA0AAAAAAgABAAAAAAAAAQAAAAADAAEAAAAAAwANAAAAAAUADwAAAAACAA0AAAAAAAANAAAAAAEADQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAAAAA0AAAAABQAOAAAAAAMADwAAAAAAAA8AAAAAAQAOAAAAAAAADQAAAAAFAAEAAAAAAQABAAAAAAEADQAAAAABAA8AAAAAAAAOAAAAAAIADQAAAAACAA0AAAAAAgANAAAAAAQAAQAAAAAAAAEAAAAAAAABAAAAAAMADgAAAAADAA4AAAAAAAAPAAAAAAMADwAAAAACAA8AAAAAAwANAAAAAAIAAQAAAAABAA0AAAAABAAOAAAAAAMADgAAAAAEAA4AAAAAAgAOAAAAAAEADQAAAAACAAEAAAAAAwABAAAAAAEADQAAAAAEAAEAAAAAAAANAAAAAAEADQAAAAADAA4AAAAABAAOAAAAAAIADQAAAAAAAAEAAAAAAQABAAAAAAMADQAAAAABAA4AAAAAAwAPAAAAAAIADwAAAAACAA8AAAAABQANAAAAAAQAAQAAAAACAAEAAAAAAQABAAAAAAEADQAAAAABAA0AAAAAAwABAAAAAAEADQAAAAAFAAEAAAAAAgABAAAAAAIAAQAAAAABAA0AAAAABQAOAAAAAAEADwAAAAAFAA8AAAAAAQAPAAAAAAAADQAAAAAFAAEAAAAAAgABAAAAAAAAAQAAAAADAAEAAAAAAAABAAAAAAAAAQAAAAABAAEAAAAAAQABAAAAAAEAAQAAAAADAAEAAAAAAAABAAAAAAMADQAAAAABAA0AAAAAAQANAAAAAAEADQAAAAABAAEAAAAAAAABAAAAAAMAAQAAAAACAAEAAAAAAgABAAAAAAEAAQAAAAACAAEAAAAAAAABAAAAAAEAAQAAAAAAAAEAAAAAAAABAAAAAAEAAQAAAAACAAEAAAAAAgABAAAAAAAAAQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAACAA==
version: 7
0,1:
ind: 0,1
tiles: AQAAAAAAAAEAAAAAAwABAAAAAAIAAQAAAAABAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAwABAAAAAAMAAQAAAAADAAEAAAAAAgABAAAAAAEAAQAAAAABAAEAAAAAAwABAAAAAAAAAQAAAAADAAEAAAAAAQABAAAAAAAAAQAAAAAAAAEAAAAAAwABAAAAAAIAAQAAAAACAAEAAAAAAgABAAAAAAMAAQAAAAADAAEAAAAAAwABAAAAAAAAAQAAAAACAAEAAAAAAwABAAAAAAIAAQAAAAABAAEAAAAAAAABAAAAAAEAAQAAAAAAAAEAAAAAAQABAAAAAAIAAQAAAAACAAEAAAAAAQABAAAAAAMAAQAAAAACAAEAAAAAAAABAAAAAAEAAQAAAAAAAAEAAAAAAwABAAAAAAMAAQAAAAAAAAEAAAAAAQABAAAAAAEAAQAAAAABAAEAAAAAAwABAAAAAAIAAQAAAAABAAEAAAAAAgABAAAAAAEAAQAAAAACAAEAAAAAAwABAAAAAAAAAQAAAAACAAEAAAAAAQABAAAAAAIAAQAAAAADAAEAAAAAAAABAAAAAAAAAQAAAAACAAEAAAAAAQABAAAAAAIAAQAAAAADAAEAAAAAAQABAAAAAAIAAQAAAAAAAAEAAAAAAQABAAAAAAIAAQAAAAABAAEAAAAAAQABAAAAAAIAAQAAAAAAAAEAAAAAAQABAAAAAAIAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAADAAEAAAAAAAABAAAAAAEAAQAAAAADAAEAAAAAAgABAAAAAAIAAQAAAAABAAEAAAAAAAABAAAAAAMAAQAAAAACAAEAAAAAAwABAAAAAAIAAQAAAAABAAEAAAAAAwABAAAAAAEAAQAAAAABAAEAAAAAAwABAAAAAAEAAQAAAAAAAAEAAAAAAwABAAAAAAIAAQAAAAAAAAEAAAAAAwABAAAAAAEAAQAAAAABAAEAAAAAAQABAAAAAAMAAQAAAAABAAEAAAAAAwABAAAAAAMAAQAAAAAAAAEAAAAAAwABAAAAAAAAAQAAAAACAAEAAAAAAgABAAAAAAAAAQAAAAACAAEAAAAAAwABAAAAAAIAAQAAAAAAAAEAAAAAAwABAAAAAAIAAQAAAAABAAEAAAAAAwABAAAAAAIAAQAAAAABAAEAAAAAAwABAAAAAAIAAQAAAAABAAEAAAAAAgABAAAAAAAAAQAAAAACAAEAAAAAAAABAAAAAAAAAQAAAAABAAEAAAAAAwABAAAAAAIAAQAAAAAAAAEAAAAAAQABAAAAAAAAAQAAAAAAAAEAAAAAAwABAAAAAAEAAQAAAAADAAEAAAAAAAABAAAAAAIAAQAAAAACAAEAAAAAAQABAAAAAAAAAQAAAAACAAEAAAAAAgABAAAAAAEAAQAAAAADAAEAAAAAAQABAAAAAAMAAQAAAAAAAAEAAAAAAwABAAAAAAIAAQAAAAAAAAEAAAAAAAABAAAAAAEAAQAAAAACAAEAAAAAAgABAAAAAAMAAQAAAAADAAEAAAAAAAABAAAAAAAAAQAAAAACAAEAAAAAAgABAAAAAAMAAQAAAAAAAAEAAAAAAQABAAAAAAAAAQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAABAAEAAAAAAwABAAAAAAMAAQAAAAAAAAEAAAAAAAABAAAAAAEAAQAAAAADAAEAAAAAAAABAAAAAAAAAQAAAAACAAEAAAAAAQABAAAAAAEAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAABAAEAAAAAAQABAAAAAAEAAQAAAAACAAEAAAAAAAABAAAAAAMAAQAAAAABAAEAAAAAAAABAAAAAAIAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAACAAEAAAAAAgABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAEAAQAAAAAAAAEAAAAAAgABAAAAAAEAAQAAAAADAAEAAAAAAwABAAAAAAEAAQAAAAAAAAEAAAAAAAABAAAAAAMAAQAAAAABAAEAAAAAAQABAAAAAAEAAQAAAAAAAAEAAAAAAwABAAAAAAMAAQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAACAAEAAAAAAgABAAAAAAEAAQAAAAABAAEAAAAAAgABAAAAAAAAAQAAAAABAAEAAAAAAwABAAAAAAMAAQAAAAAAAAEAAAAAAwABAAAAAAMAAQAAAAACAAEAAAAAAwABAAAAAAMAAQAAAAABAAEAAAAAAgABAAAAAAIAAQAAAAAAAAEAAAAAAwABAAAAAAMAAQAAAAADAAEAAAAAAQABAAAAAAMAAQAAAAABAAEAAAAAAAABAAAAAAAAAQAAAAACAA==
version: 7
0,-1:
ind: 0,-1
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAABAAEAAAAAAgABAAAAAAMAAQAAAAADAAEAAAAAAQABAAAAAAEAAQAAAAABAAEAAAAAAAABAAAAAAIAAQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAADAAEAAAAAAAABAAAAAAAAAQAAAAAAAA==
version: 7
-1,0:
ind: -1,0
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAA==
version: 7
-1,1:
ind: -1,1
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAABAA==
version: 7
1,-1:
ind: 1,-1
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAADAAEAAAAAAQABAAAAAAAAAQAAAAABAAEAAAAAAQABAAAAAAEAAQAAAAADAAEAAAAAAwABAAAAAAEAAQAAAAADAAEAAAAAAAABAAAAAAMAAQAAAAACAAEAAAAAAAABAAAAAAIAAQAAAAACAA==
version: 7
1,0:
ind: 1,0
tiles: AQAAAAACAAEAAAAAAgAWAAAAAAQAFgAAAAABABYAAAAAAAABAAAAAAAAAQAAAAADAAEAAAAAAgABAAAAAAEAFgAAAAACABYAAAAABAABAAAAAAEAAQAAAAAAAAEAAAAAAgABAAAAAAEAAQAAAAAAAAEAAAAAAAAWAAAAAAAAFwAAAAABABcAAAAAAwAXAAAAAAEAFgAAAAAEAAEAAAAAAAABAAAAAAEAGAAAAAAFABcAAAAAAwAXAAAAAAAAFgAAAAAAABYAAAAABAABAAAAAAMAAQAAAAACAAEAAAAAAwAWAAAAAAIAFwAAAAABABcAAAAAAQAYAAAAAAAAFwAAAAABABcAAAAAAwAWAAAAAAMAAQAAAAADABgAAAAABQAYAAAAAAAAGAAAAAAAABgAAAAAAgAWAAAAAAUAFgAAAAAFAAEAAAAAAwABAAAAAAAAFgAAAAACABcAAAAAAwAYAAAAAAMAGAAAAAABABgAAAAAAQAXAAAAAAUAFgAAAAACAAEAAAAAAQAWAAAAAAUAGAAAAAADABgAAAAAAgAYAAAAAAMAGAAAAAAFABYAAAAABQAWAAAAAAUAAQAAAAADABYAAAAAAwAXAAAAAAUAGAAAAAAFABgAAAAAAQAXAAAAAAQAFwAAAAAFABYAAAAABAABAAAAAAAAFgAAAAACABYAAAAAAgAYAAAAAAMAGAAAAAACABgAAAAABAAXAAAAAAUAFgAAAAADAAEAAAAAAgABAAAAAAEAFgAAAAACABcAAAAABAAXAAAAAAQAFgAAAAAFABYAAAAABQAWAAAAAAMAAQAAAAACAAEAAAAAAQAWAAAAAAQAFgAAAAABABcAAAAAAwAXAAAAAAMAFwAAAAABABcAAAAAAAABAAAAAAAAAQAAAAADABYAAAAABAAWAAAAAAAAFgAAAAABABYAAAAABQABAAAAAAMAAQAAAAABAAEAAAAAAAABAAAAAAIAAQAAAAACAAEAAAAAAQAWAAAAAAUAFgAAAAABABYAAAAAAAABAAAAAAAAAQAAAAACAAEAAAAAAQABAAAAAAAAAQAAAAABAAEAAAAAAgABAAAAAAEAAQAAAAACAAEAAAAAAQABAAAAAAMAAQAAAAACAAEAAAAAAAABAAAAAAIAAQAAAAABAAEAAAAAAgABAAAAAAMAAQAAAAAAAAEAAAAAAAABAAAAAAMAAQAAAAAAAAEAAAAAAwABAAAAAAMAAQAAAAABAAEAAAAAAwABAAAAAAAAAQAAAAAAAAEAAAAAAQABAAAAAAEAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAABAAEAAAAAAwABAAAAAAMAAQAAAAAAAAEAAAAAAAABAAAAAAIAAQAAAAAAAAEAAAAAAgABAAAAAAEAAQAAAAABAAEAAAAAAQABAAAAAAAAAQAAAAACAAEAAAAAAQABAAAAAAMAAQAAAAAAAAEAAAAAAwABAAAAAAMAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAADAAEAAAAAAgABAAAAAAAAAQAAAAAAAAEAAAAAAgABAAAAAAMAAQAAAAACAAEAAAAAAAABAAAAAAMAAQAAAAAAAAEAAAAAAgABAAAAAAEAAQAAAAABAAEAAAAAAAABAAAAAAEAAQAAAAAAAAEAAAAAAwABAAAAAAMAAQAAAAAAAAEAAAAAAQABAAAAAAEAAQAAAAABAAEAAAAAAwABAAAAAAAAAQAAAAABAAEAAAAAAAABAAAAAAAAAQAAAAACAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAQABAAAAAAEAAQAAAAADAAEAAAAAAwABAAAAAAAAAQAAAAADAAEAAAAAAgABAAAAAAIAAQAAAAACAAEAAAAAAwABAAAAAAAAAQAAAAADAAEAAAAAAgABAAAAAAMAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAABAAEAAAAAAQABAAAAAAMAAQAAAAADAAEAAAAAAgABAAAAAAEAAQAAAAAAAAEAAAAAAAABAAAAAAMAAQAAAAADAAEAAAAAAAABAAAAAAMAAQAAAAADAAEAAAAAAQABAAAAAAMAAQAAAAADAAEAAAAAAQABAAAAAAMAAQAAAAACAAEAAAAAAgABAAAAAAAAAQAAAAACAAEAAAAAAAABAAAAAAAAAQAAAAACAAEAAAAAAAABAAAAAAAAAQAAAAADAAEAAAAAAQABAAAAAAAAAQAAAAAAAAEAAAAAAwABAAAAAAIAAQAAAAADAAEAAAAAAAABAAAAAAAAAQAAAAABAAEAAAAAAQABAAAAAAIAAQAAAAADAAEAAAAAAwABAAAAAAEAAQAAAAADAAEAAAAAAgABAAAAAAEAAQAAAAABAA==
version: 7
1,1:
ind: 1,1
tiles: AQAAAAAAAAEAAAAAAgABAAAAAAIAAQAAAAACAAEAAAAAAwABAAAAAAAAAQAAAAADAAEAAAAAAgABAAAAAAMAAQAAAAADAAEAAAAAAgABAAAAAAAAAQAAAAABAAEAAAAAAgABAAAAAAIAAQAAAAADAAEAAAAAAgABAAAAAAAAAQAAAAAAAAEAAAAAAQABAAAAAAAAAQAAAAADAAEAAAAAAgABAAAAAAMAAQAAAAACAAEAAAAAAAABAAAAAAMAAQAAAAAAAAEAAAAAAgABAAAAAAIAAQAAAAABAAEAAAAAAQABAAAAAAEAAQAAAAACAAEAAAAAAwABAAAAAAIAAQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAADAAEAAAAAAgABAAAAAAEAAQAAAAADAAEAAAAAAwABAAAAAAIAAQAAAAACAAEAAAAAAgABAAAAAAAAAQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAACAAEAAAAAAAABAAAAAAIAAQAAAAADAAEAAAAAAgABAAAAAAMAAQAAAAADAAEAAAAAAAABAAAAAAAAAQAAAAADAAEAAAAAAAABAAAAAAMAAQAAAAAAAAEAAAAAAQABAAAAAAAAAQAAAAABAAEAAAAAAAABAAAAAAAAAQAAAAABAAEAAAAAAQABAAAAAAAAAQAAAAADAAEAAAAAAgABAAAAAAIAAQAAAAAAAAEAAAAAAQABAAAAAAAAAQAAAAABAAEAAAAAAwABAAAAAAEAAQAAAAACAAEAAAAAAgABAAAAAAMAAQAAAAADAAEAAAAAAAABAAAAAAEAAQAAAAADAAEAAAAAAgABAAAAAAIAAQAAAAAAAAEAAAAAAgABAAAAAAAAAQAAAAABAAEAAAAAAAABAAAAAAEAAQAAAAACAAEAAAAAAQABAAAAAAIAAQAAAAAAAAEAAAAAAQABAAAAAAEAAQAAAAACAAEAAAAAAwABAAAAAAMAAQAAAAAAAAEAAAAAAQABAAAAAAMAAQAAAAAAAAEAAAAAAgABAAAAAAIAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAABAAEAAAAAAQABAAAAAAEAAQAAAAADAAEAAAAAAAABAAAAAAMAAQAAAAABAAEAAAAAAwABAAAAAAMAAQAAAAAAAAEAAAAAAwABAAAAAAEAAQAAAAABAAEAAAAAAwABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAMAAQAAAAABAAEAAAAAAgABAAAAAAIAAQAAAAADAAEAAAAAAgABAAAAAAAAAQAAAAAAAAEAAAAAAgABAAAAAAMAAQAAAAABAAEAAAAAAwABAAAAAAIAAQAAAAABAAEAAAAAAgABAAAAAAIAAQAAAAABAAEAAAAAAQABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAEAAQAAAAACAAEAAAAAAgABAAAAAAMAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAACAAEAAAAAAwABAAAAAAMAAQAAAAABAAEAAAAAAAABAAAAAAIAAQAAAAADAAEAAAAAAQABAAAAAAEAAQAAAAAAAAEAAAAAAwABAAAAAAEAAQAAAAABAAEAAAAAAAABAAAAAAMAAQAAAAAAAAEAAAAAAAABAAAAAAMAAQAAAAABAAEAAAAAAwABAAAAAAIAAQAAAAACAAEAAAAAAwABAAAAAAIAAQAAAAAAAAEAAAAAAQABAAAAAAEAAQAAAAACAAEAAAAAAQABAAAAAAIAAQAAAAACAAEAAAAAAgABAAAAAAMAAQAAAAABAAEAAAAAAwABAAAAAAMAAQAAAAADAAEAAAAAAQABAAAAAAMAAQAAAAADAAEAAAAAAgABAAAAAAIAAQAAAAADAAEAAAAAAgABAAAAAAMAAQAAAAABAAEAAAAAAAABAAAAAAIAAQAAAAADAAEAAAAAAgABAAAAAAMAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAACAAEAAAAAAwABAAAAAAIAAQAAAAABAAEAAAAAAAABAAAAAAEAAQAAAAAAAAEAAAAAAgABAAAAAAIAAQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAABAAEAAAAAAQABAAAAAAMAAQAAAAAAAAEAAAAAAgABAAAAAAIAAQAAAAACAAEAAAAAAgABAAAAAAMAAQAAAAADAAEAAAAAAAABAAAAAAMAAQAAAAABAAEAAAAAAAABAAAAAAMAAQAAAAADAAEAAAAAAgABAAAAAAAAAQAAAAADAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAMAAQAAAAAAAAEAAAAAAAABAAAAAAIAAQAAAAABAA==
version: 7
-1,2:
ind: -1,2
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
version: 7
0,2:
ind: 0,2
tiles: AQAAAAACAAEAAAAAAgABAAAAAAMAAQAAAAADAAEAAAAAAAABAAAAAAAAAQAAAAABAAEAAAAAAAABAAAAAAIAAQAAAAACAAEAAAAAAgABAAAAAAMAAQAAAAABAAEAAAAAAgABAAAAAAMAAQAAAAABAAEAAAAAAAABAAAAAAAAAQAAAAADAAEAAAAAAgABAAAAAAIAAQAAAAADAAEAAAAAAgABAAAAAAEAAQAAAAABAAEAAAAAAwABAAAAAAMAAQAAAAACAAEAAAAAAwABAAAAAAMAAQAAAAADAAEAAAAAAAABAAAAAAEAAQAAAAADAAEAAAAAAAABAAAAAAMAAQAAAAAAAAEAAAAAAwABAAAAAAEAAQAAAAAAAAEAAAAAAwABAAAAAAMAAQAAAAACAAEAAAAAAQABAAAAAAIAAQAAAAACAAEAAAAAAQABAAAAAAMAAQAAAAAAAAEAAAAAAQABAAAAAAEAAQAAAAABAAEAAAAAAAABAAAAAAMAAQAAAAABAAEAAAAAAQABAAAAAAEAAQAAAAABAAEAAAAAAAABAAAAAAIAAQAAAAABAAEAAAAAAwABAAAAAAMAAQAAAAABAAEAAAAAAQABAAAAAAIAAQAAAAADAAEAAAAAAAABAAAAAAIAAQAAAAABAAEAAAAAAgABAAAAAAEAAQAAAAAAAAEAAAAAAQABAAAAAAEAAQAAAAADAAEAAAAAAwABAAAAAAEAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAAAAAEAAAAAAgABAAAAAAMAAQAAAAADAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAgABAAAAAAMAAQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAABAAEAAAAAAAABAAAAAAEAAQAAAAADAAEAAAAAAQABAAAAAAEAAQAAAAADAAEAAAAAAgABAAAAAAIAAQAAAAAAAAEAAAAAAQABAAAAAAAAAQAAAAADAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAQABAAAAAAMAAQAAAAACAAEAAAAAAwABAAAAAAMAAQAAAAAAAAEAAAAAAwABAAAAAAAAAQAAAAADAAEAAAAAAwABAAAAAAEAAQAAAAACAAEAAAAAAgABAAAAAAAAAQAAAAAAAAEAAAAAAwABAAAAAAAAAQAAAAACAAEAAAAAAAABAAAAAAMAAQAAAAABAAEAAAAAAgABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAIAAQAAAAAAAAEAAAAAAAABAAAAAAIAAQAAAAAAAAEAAAAAAwABAAAAAAAAAQAAAAAAAAEAAAAAAgABAAAAAAEAAQAAAAACAAEAAAAAAQABAAAAAAAAAQAAAAADAAEAAAAAAQABAAAAAAIAAQAAAAABAAEAAAAAAQABAAAAAAAAAQAAAAAAAAEAAAAAAwABAAAAAAEAAQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
version: 7
1,2:
ind: 1,2
tiles: AQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAADAAEAAAAAAwABAAAAAAIAAQAAAAABAAEAAAAAAAABAAAAAAAAAQAAAAADAAEAAAAAAwABAAAAAAEAAQAAAAABAAEAAAAAAQABAAAAAAAAAQAAAAADAAEAAAAAAAABAAAAAAAAAQAAAAACAAEAAAAAAQABAAAAAAMAAQAAAAABAAEAAAAAAAABAAAAAAIAAQAAAAADAAEAAAAAAgABAAAAAAEAAQAAAAAAAAEAAAAAAgABAAAAAAMAAQAAAAADAAEAAAAAAAABAAAAAAIAAQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAABAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAgABAAAAAAMAAQAAAAABAAEAAAAAAAABAAAAAAEAAQAAAAADAAEAAAAAAwABAAAAAAIAAQAAAAAAAAEAAAAAAQABAAAAAAMAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAAAAAEAAAAAAwABAAAAAAEAAQAAAAACAAEAAAAAAwABAAAAAAMAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAADAAEAAAAAAQABAAAAAAMAAQAAAAABAAEAAAAAAwABAAAAAAEAAQAAAAAAAAEAAAAAAgABAAAAAAIAAQAAAAADAAEAAAAAAwABAAAAAAIAAQAAAAACAAEAAAAAAwABAAAAAAEAAQAAAAAAAAEAAAAAAgABAAAAAAEAAQAAAAACAAEAAAAAAgABAAAAAAEAAQAAAAAAAAEAAAAAAwABAAAAAAMAAQAAAAADAAEAAAAAAwABAAAAAAIAAQAAAAABAAEAAAAAAwABAAAAAAMAAQAAAAABAAEAAAAAAwABAAAAAAEAAQAAAAABAAEAAAAAAAABAAAAAAAAAQAAAAABAAEAAAAAAwABAAAAAAEAAQAAAAAAAAEAAAAAAAABAAAAAAEAAQAAAAABAAEAAAAAAAABAAAAAAMAAQAAAAACAAEAAAAAAgABAAAAAAAAAQAAAAABAAEAAAAAAAABAAAAAAEAAQAAAAADAAEAAAAAAQABAAAAAAEAAQAAAAACAAEAAAAAAwABAAAAAAAAAQAAAAADAAEAAAAAAwABAAAAAAIAAQAAAAABAAEAAAAAAAABAAAAAAMAAQAAAAAAAAEAAAAAAgABAAAAAAEAAQAAAAABAAEAAAAAAwABAAAAAAAAAQAAAAADAAEAAAAAAAABAAAAAAIAAQAAAAAAAAEAAAAAAwABAAAAAAMAAQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAADAAEAAAAAAwABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAMAAQAAAAACAAEAAAAAAQABAAAAAAMAAQAAAAACAAEAAAAAAQABAAAAAAMAAQAAAAADAAEAAAAAAwABAAAAAAIAAQAAAAAAAAEAAAAAAgABAAAAAAMAAQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
version: 7
2,-1:
ind: 2,-1
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAADAAEAAAAAAgABAAAAAAMAAQAAAAABAAEAAAAAAAABAAAAAAAAAQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAAAAAEAAAAAAgABAAAAAAIAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAADAA==
version: 7
2,0:
ind: 2,0
tiles: AQAAAAADABkAAAAAAQAaAAAAAAYAGgAAAAABABoAAAAABgAZAAAAAAoAAQAAAAAAAAEAAAAAAQABAAAAAAAAAQAAAAACABoAAAAABwAZAAAAAAYAGQAAAAACABoAAAAABAABAAAAAAMAAQAAAAAAAAEAAAAAAgAaAAAAAAIAGgAAAAABABoAAAAABAAaAAAAAAEAGgAAAAAIABkAAAAABgABAAAAAAAAAQAAAAADABkAAAAACQAaAAAAAAsAGgAAAAADABoAAAAACwAZAAAAAAUAGQAAAAADAAEAAAAAAwAaAAAAAAoAGgAAAAALABsAAAAABAAbAAAAAAkAGwAAAAAHABoAAAAACAAaAAAAAAkAAQAAAAADAAEAAAAAAQAaAAAAAAsAGwAAAAAGABsAAAAABgAaAAAAAAkAGgAAAAAGABkAAAAACAABAAAAAAAAGgAAAAAKABsAAAAABAAbAAAAAAEAGwAAAAABABsAAAAAAwAaAAAAAAIAGgAAAAAKAAEAAAAAAgABAAAAAAEAGgAAAAACABoAAAAACgAbAAAAAAsAGwAAAAAJABoAAAAABAABAAAAAAAAAQAAAAAAABkAAAAADwAaAAAAAAYAGwAAAAAGABsAAAAABgAbAAAAAAUAGgAAAAAJAAEAAAAAAwABAAAAAAAAGQAAAAAPABoAAAAACAAbAAAAAAcAGwAAAAAKABsAAAAABQAaAAAAAAsAAQAAAAABAAEAAAAAAQABAAAAAAIAAQAAAAAAABoAAAAACwAZAAAAAAcAGQAAAAAHABoAAAAABQABAAAAAAMAAQAAAAABABkAAAAAAAAaAAAAAAoAGwAAAAALABoAAAAABgAaAAAAAAgAGgAAAAAHAAEAAAAAAQABAAAAAAMAAQAAAAABAAEAAAAAAgAZAAAAAAYAGQAAAAAAABoAAAAABwABAAAAAAEAAQAAAAADAAEAAAAAAwABAAAAAAAAGgAAAAAIABkAAAAADgAZAAAAAAMAGQAAAAAMAAEAAAAAAAABAAAAAAAAAQAAAAABAAEAAAAAAgABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAEAAQAAAAABAAEAAAAAAwABAAAAAAMAAQAAAAAAAAEAAAAAAwABAAAAAAEAAQAAAAACAAEAAAAAAAABAAAAAAMAAQAAAAABAAEAAAAAAQABAAAAAAIAAQAAAAADAAEAAAAAAAABAAAAAAMAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAADAAEAAAAAAQABAAAAAAEAAQAAAAABAAEAAAAAAwABAAAAAAMAAQAAAAADAAEAAAAAAAABAAAAAAEAAQAAAAABAAEAAAAAAAABAAAAAAMAAQAAAAAAAAEAAAAAAwABAAAAAAMAAQAAAAABAAEAAAAAAwABAAAAAAMAAQAAAAABAAEAAAAAAQABAAAAAAMAAQAAAAACAAEAAAAAAwABAAAAAAAAAQAAAAADAAEAAAAAAAABAAAAAAAAAQAAAAADAAEAAAAAAgABAAAAAAIAAQAAAAACAAEAAAAAAgABAAAAAAAAAQAAAAADAAEAAAAAAQABAAAAAAMAAQAAAAACAAEAAAAAAgABAAAAAAEAAQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAACAAEAAAAAAAABAAAAAAIAAQAAAAACAAEAAAAAAQABAAAAAAMAAQAAAAAAAAEAAAAAAgABAAAAAAEAAQAAAAACAAEAAAAAAwABAAAAAAMAAQAAAAAAAAEAAAAAAwABAAAAAAAAAQAAAAAAAAEAAAAAAgABAAAAAAMAAQAAAAACAAEAAAAAAgABAAAAAAEAAQAAAAABAAEAAAAAAgABAAAAAAAAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAABAAEAAAAAAAABAAAAAAEAAQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAABAAEAAAAAAQABAAAAAAMAAQAAAAABAAEAAAAAAwABAAAAAAMAAQAAAAADAAEAAAAAAgABAAAAAAAAAQAAAAADAAEAAAAAAgABAAAAAAAAAQAAAAABAAEAAAAAAwABAAAAAAIAAQAAAAAAAAEAAAAAAQABAAAAAAAAAQAAAAACAAEAAAAAAQABAAAAAAAAAQAAAAABAAEAAAAAAQABAAAAAAEAAQAAAAAAAAEAAAAAAgABAAAAAAEAAQAAAAADAAEAAAAAAwABAAAAAAAAAQAAAAADAAEAAAAAAAABAAAAAAEAAQAAAAADAAEAAAAAAQABAAAAAAAAAQAAAAABAAEAAAAAAQABAAAAAAMAAQAAAAACAAEAAAAAAAABAAAAAAAAAQAAAAACAAEAAAAAAgABAAAAAAAAAQAAAAABAA==
version: 7
2,1:
ind: 2,1
tiles: AQAAAAAAAAEAAAAAAwABAAAAAAEAAQAAAAADAAEAAAAAAwABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAADAAEAAAAAAgABAAAAAAAAAQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAABAAEAAAAAAgABAAAAAAIAAQAAAAABAAEAAAAAAQABAAAAAAMAAQAAAAAAAAEAAAAAAAABAAAAAAMAAQAAAAABAAEAAAAAAQABAAAAAAMAAQAAAAAAAAEAAAAAAAABAAAAAAMAAQAAAAAAAAEAAAAAAAABAAAAAAIAAQAAAAAAAAEAAAAAAwABAAAAAAIAAQAAAAACAAEAAAAAAAABAAAAAAIAAQAAAAACAAEAAAAAAwABAAAAAAAAAQAAAAADAAEAAAAAAAABAAAAAAIAAQAAAAABAAEAAAAAAAABAAAAAAAAAQAAAAABAAEAAAAAAAABAAAAAAEAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAACAAEAAAAAAwABAAAAAAMAAQAAAAACAAEAAAAAAAABAAAAAAEAAQAAAAACAAEAAAAAAQABAAAAAAMAAQAAAAABAAEAAAAAAQABAAAAAAAAAQAAAAAAAAEAAAAAAQABAAAAAAAAAQAAAAAAAAEAAAAAAwABAAAAAAIAAQAAAAABAAEAAAAAAwABAAAAAAMAAQAAAAABAAEAAAAAAAABAAAAAAEAAQAAAAABAAEAAAAAAgABAAAAAAIAAQAAAAABAAEAAAAAAQABAAAAAAIAAQAAAAAAAAEAAAAAAQABAAAAAAAAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAAAAAEAAAAAAwABAAAAAAAAAQAAAAAAAAEAAAAAAQABAAAAAAEAAQAAAAADAAEAAAAAAAABAAAAAAEAAQAAAAADAAEAAAAAAAABAAAAAAAAAQAAAAACAAEAAAAAAAABAAAAAAIAAQAAAAACAAEAAAAAAgABAAAAAAEAAQAAAAACAAEAAAAAAwABAAAAAAMAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAACAAEAAAAAAQABAAAAAAMAAQAAAAAAAAEAAAAAAgABAAAAAAAAAQAAAAACAAEAAAAAAAABAAAAAAEAAQAAAAADAAEAAAAAAgABAAAAAAIAAQAAAAABAAEAAAAAAAABAAAAAAAAAQAAAAADAAEAAAAAAAABAAAAAAMAAQAAAAADAAEAAAAAAgABAAAAAAMAAQAAAAABAAEAAAAAAAABAAAAAAAAAQAAAAADAAEAAAAAAwABAAAAAAEAAQAAAAAAAAEAAAAAAQABAAAAAAMAAQAAAAAAAAEAAAAAAwABAAAAAAIAAQAAAAACAAEAAAAAAgABAAAAAAEAAQAAAAADAAEAAAAAAgABAAAAAAAAAQAAAAADAAEAAAAAAAABAAAAAAMAAQAAAAABAAEAAAAAAgABAAAAAAIAAQAAAAADAAEAAAAAAgABAAAAAAAAAQAAAAABAAEAAAAAAAABAAAAAAIAAQAAAAABAAEAAAAAAAABAAAAAAIAAQAAAAABAAEAAAAAAQABAAAAAAEAAQAAAAADAAEAAAAAAgABAAAAAAIAAQAAAAABAAEAAAAAAgABAAAAAAIAAQAAAAACAAEAAAAAAwABAAAAAAEAAQAAAAACAAEAAAAAAQABAAAAAAMAAQAAAAACAAEAAAAAAwABAAAAAAMAAQAAAAABAAEAAAAAAQABAAAAAAIAAQAAAAADAAEAAAAAAwABAAAAAAEAAQAAAAAAAAEAAAAAAwABAAAAAAIAAQAAAAAAAAEAAAAAAwABAAAAAAEAAQAAAAADAAEAAAAAAwABAAAAAAEAAQAAAAACAAEAAAAAAwABAAAAAAEAAQAAAAAAAAEAAAAAAgABAAAAAAAAAQAAAAAAAAEAAAAAAQABAAAAAAEAAQAAAAAAAAEAAAAAAgABAAAAAAMAAQAAAAADAAEAAAAAAAABAAAAAAMAAQAAAAADAAEAAAAAAAABAAAAAAEAAQAAAAACAAEAAAAAAwABAAAAAAEAAQAAAAACAAEAAAAAAQABAAAAAAMAAQAAAAABAAEAAAAAAgABAAAAAAAAAQAAAAACAAEAAAAAAQABAAAAAAEAAQAAAAABAAEAAAAAAgABAAAAAAEAAQAAAAAAAAEAAAAAAQABAAAAAAEAAQAAAAAAAAEAAAAAAwABAAAAAAIAAQAAAAAAAAEAAAAAAwABAAAAAAEAAQAAAAACAAEAAAAAAQABAAAAAAAAAQAAAAADAAEAAAAAAgABAAAAAAAAAQAAAAABAAEAAAAAAgABAAAAAAIAAQAAAAABAAEAAAAAAwABAAAAAAAAAQAAAAACAA==
version: 7
2,2:
ind: 2,2
tiles: AQAAAAADAAEAAAAAAwABAAAAAAIAAQAAAAABAAEAAAAAAAABAAAAAAEAAQAAAAAAAAEAAAAAAgABAAAAAAAAAQAAAAADAAEAAAAAAwABAAAAAAMAAQAAAAAAAAEAAAAAAwABAAAAAAMAAQAAAAACAAEAAAAAAgABAAAAAAIAAQAAAAACAAEAAAAAAwABAAAAAAIAAQAAAAABAAEAAAAAAQABAAAAAAMAAQAAAAABAAEAAAAAAgABAAAAAAIAAQAAAAABAAEAAAAAAgABAAAAAAIAAQAAAAACAAEAAAAAAQABAAAAAAAAAQAAAAABAAEAAAAAAAABAAAAAAIAAQAAAAABAAEAAAAAAQABAAAAAAAAAQAAAAABAAEAAAAAAgABAAAAAAMAAQAAAAADAAEAAAAAAQABAAAAAAEAAQAAAAADAAEAAAAAAQABAAAAAAMAAQAAAAADAAEAAAAAAgABAAAAAAEAAQAAAAABAAEAAAAAAgABAAAAAAAAAQAAAAACAAEAAAAAAgABAAAAAAEAAQAAAAAAAAEAAAAAAQABAAAAAAAAAQAAAAABAAEAAAAAAAABAAAAAAAAAQAAAAACAAEAAAAAAAABAAAAAAIAAQAAAAACAAEAAAAAAAABAAAAAAAAAQAAAAABAAEAAAAAAAABAAAAAAIAAQAAAAACAAEAAAAAAQABAAAAAAIAAQAAAAABAAEAAAAAAQABAAAAAAAAAQAAAAAAAAEAAAAAAwABAAAAAAEAAQAAAAAAAAEAAAAAAgABAAAAAAAAAQAAAAADAAEAAAAAAQABAAAAAAMAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAgABAAAAAAIAAQAAAAABAAEAAAAAAwABAAAAAAAAAQAAAAABAAEAAAAAAwABAAAAAAIAAQAAAAAAAAEAAAAAAgABAAAAAAEAAQAAAAADAAEAAAAAAgABAAAAAAEAAQAAAAADAAEAAAAAAwABAAAAAAEAAQAAAAAAAAEAAAAAAAABAAAAAAEAAQAAAAABAAEAAAAAAwABAAAAAAEAAQAAAAABAAEAAAAAAQABAAAAAAMAAQAAAAABAAEAAAAAAgABAAAAAAEAAQAAAAAAAAEAAAAAAAABAAAAAAMAAQAAAAACAAEAAAAAAQABAAAAAAIAAQAAAAAAAAEAAAAAAgABAAAAAAIAAQAAAAAAAAEAAAAAAAABAAAAAAIAAQAAAAACAAEAAAAAAgABAAAAAAMAAQAAAAACAAEAAAAAAwABAAAAAAAAAQAAAAABAAEAAAAAAgABAAAAAAIAAQAAAAADAAEAAAAAAgABAAAAAAMAAQAAAAAAAAEAAAAAAgABAAAAAAIAAQAAAAACAAEAAAAAAQABAAAAAAEAAQAAAAAAAAEAAAAAAgABAAAAAAMAAQAAAAABAAEAAAAAAQABAAAAAAAAAQAAAAAAAAEAAAAAAgABAAAAAAAAAQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
version: 7
- type: Gravity
gravityShakeSound: !type:SoundPathSpecifier
path: /Audio/Effects/alert.ogg
- type: DecalGrid
chunkCollection:
version: 2
nodes: []
- type: SpreaderGrid
- type: GridPathfinding
- type: RadiationGridResistance
- proto: CP14CrystalQuartzWater
entities:
- uid: 123
components:
- type: Transform
pos: 26.508047,4.3724494
parent: 1
- uid: 142
components:
- type: Transform
pos: 20.48077,2.4310699
parent: 1
- proto: CP14FenceBigWooden
entities:
- uid: 20
components:
- type: Transform
pos: 5.5,11.5
parent: 1
- uid: 94
components:
- type: Transform
pos: 12.5,10.5
parent: 1
- uid: 96
components:
- type: Transform
pos: 9.5,10.5
parent: 1
- uid: 118
components:
- type: Transform
pos: 11.5,10.5
parent: 1
- uid: 131
components:
- type: Transform
pos: 2.5,8.5
parent: 1
- proto: CP14GatherableLumiMushroom
entities:
- uid: 18
components:
- type: Transform
pos: 10.5,2.5
parent: 1
- uid: 122
components:
- type: Transform
pos: 2.5,2.5
parent: 1
- proto: CP14MushroomTree1Glowing
entities:
- uid: 126
components:
- type: Transform
pos: 34.5,0.5
parent: 1
- proto: CP14MushroomTree2
entities:
- uid: 128
components:
- type: Transform
pos: 40.5,5.5
parent: 1
- proto: CP14MushroomTree3
entities:
- uid: 87
components:
- type: Transform
pos: 33.5,4.5
parent: 1
- proto: CP14MushroomTree3Glowing
entities:
- uid: 147
components:
- type: Transform
pos: 42.5,2.5
parent: 1
- proto: CP14MushroomTree4
entities:
- uid: 2
components:
- type: Transform
pos: 37.5,2.5
parent: 1
- uid: 121
components:
- type: Transform
pos: 45.5,0.5
parent: 1
- proto: CP14RockBig
entities:
- uid: 6
components:
- type: Transform
pos: 5.5,1.5
parent: 1
- uid: 46
components:
- type: Transform
pos: 9.5,0.5
parent: 1
- proto: CP14RockSmall
entities:
- uid: 38
components:
- type: Transform
pos: 9.5,4.5
parent: 1
- uid: 72
components:
- type: Transform
pos: 1.5,3.5
parent: 1
- proto: CP14Snowdrift
entities:
- uid: 11
components:
- type: Transform
pos: 19.5,2.5
parent: 1
- uid: 15
components:
- type: Transform
pos: 21.5,2.5
parent: 1
- uid: 16
components:
- type: Transform
pos: 18.5,5.5
parent: 1
- uid: 23
components:
- type: Transform
pos: 22.5,4.5
parent: 1
- uid: 24
components:
- type: Transform
pos: 16.5,2.5
parent: 1
- uid: 25
components:
- type: Transform
pos: 19.5,5.5
parent: 1
- uid: 90
components:
- type: Transform
pos: 20.5,1.5
parent: 1
- proto: CP14SpawnerLocationConnection
entities:
- uid: 8
components:
- type: Transform
pos: 11.5,3.5
parent: 1
- uid: 22
components:
- type: Transform
pos: 3.5,11.5
parent: 1
- uid: 39
components:
- type: Transform
pos: 19.5,3.5
parent: 1
- uid: 61
components:
- type: Transform
pos: 12.5,13.5
parent: 1
- uid: 74
components:
- type: Transform
pos: 3.5,3.5
parent: 1
- uid: 99
components:
- type: Transform
pos: 28.5,5.5
parent: 1
- uid: 103
components:
- type: Transform
pos: 35.5,3.5
parent: 1
- uid: 107
components:
- type: Transform
pos: 44.5,5.5
parent: 1
- proto: CP14WallDirt
entities:
- uid: 12
components:
- type: Transform
pos: 1.5,10.5
parent: 1
- uid: 13
components:
- type: Transform
pos: 2.5,13.5
parent: 1
- uid: 14
components:
- type: Transform
pos: 3.5,9.5
parent: 1
- uid: 17
components:
- type: Transform
pos: 4.5,9.5
parent: 1
- uid: 19
components:
- type: Transform
pos: 3.5,8.5
parent: 1
- uid: 21
components:
- type: Transform
pos: 5.5,12.5
parent: 1
- uid: 75
components:
- type: Transform
pos: 3.5,12.5
parent: 1
- uid: 76
components:
- type: Transform
pos: 3.5,13.5
parent: 1
- uid: 85
components:
- type: Transform
pos: 33.5,3.5
parent: 1
- uid: 86
components:
- type: Transform
pos: 33.5,2.5
parent: 1
- uid: 91
components:
- type: Transform
pos: 42.5,1.5
parent: 1
- uid: 92
components:
- type: Transform
pos: 10.5,8.5
parent: 1
- uid: 93
components:
- type: Transform
pos: 43.5,5.5
parent: 1
- uid: 95
components:
- type: Transform
pos: 10.5,10.5
parent: 1
- uid: 97
components:
- type: Transform
pos: 12.5,14.5
parent: 1
- uid: 101
components:
- type: Transform
pos: 44.5,6.5
parent: 1
- uid: 102
components:
- type: Transform
pos: 36.5,3.5
parent: 1
- uid: 104
components:
- type: Transform
pos: 36.5,2.5
parent: 1
- uid: 105
components:
- type: Transform
pos: 34.5,2.5
parent: 1
- uid: 106
components:
- type: Transform
pos: 43.5,0.5
parent: 1
- uid: 108
components:
- type: Transform
pos: 46.5,1.5
parent: 1
- uid: 109
components:
- type: Transform
pos: 36.5,4.5
parent: 1
- uid: 110
components:
- type: Transform
pos: 37.5,4.5
parent: 1
- uid: 112
components:
- type: Transform
pos: 37.5,3.5
parent: 1
- uid: 114
components:
- type: Transform
pos: 46.5,2.5
parent: 1
- uid: 116
components:
- type: Transform
pos: 43.5,6.5
parent: 1
- uid: 117
components:
- type: Transform
pos: 13.5,14.5
parent: 1
- uid: 124
components:
- type: Transform
pos: 36.5,5.5
parent: 1
- uid: 125
components:
- type: Transform
pos: 34.5,1.5
parent: 1
- uid: 127
components:
- type: Transform
pos: 11.5,14.5
parent: 1
- uid: 134
components:
- type: Transform
pos: 13.5,13.5
parent: 1
- uid: 135
components:
- type: Transform
pos: 45.5,5.5
parent: 1
- uid: 136
components:
- type: Transform
pos: 14.5,13.5
parent: 1
- uid: 137
components:
- type: Transform
pos: 41.5,2.5
parent: 1
- uid: 141
components:
- type: Transform
pos: 4.5,12.5
parent: 1
- uid: 143
components:
- type: Transform
pos: 35.5,4.5
parent: 1
- uid: 145
components:
- type: Transform
pos: 40.5,4.5
parent: 1
- uid: 148
components:
- type: Transform
pos: 41.5,1.5
parent: 1
- uid: 149
components:
- type: Transform
pos: 9.5,8.5
parent: 1
- uid: 150
components:
- type: Transform
pos: 8.5,9.5
parent: 1
- proto: CP14WallSnow
entities:
- uid: 5
components:
- type: Transform
pos: 22.5,2.5
parent: 1
- uid: 28
components:
- type: Transform
pos: 17.5,3.5
parent: 1
- uid: 29
components:
- type: Transform
pos: 20.5,0.5
parent: 1
- uid: 30
components:
- type: Transform
pos: 17.5,1.5
parent: 1
- uid: 31
components:
- type: Transform
pos: 20.5,4.5
parent: 1
- uid: 33
components:
- type: Transform
pos: 18.5,0.5
parent: 1
- uid: 34
components:
- type: Transform
pos: 20.5,3.5
parent: 1
- uid: 37
components:
- type: Transform
pos: 19.5,6.5
parent: 1
- uid: 41
components:
- type: Transform
pos: 18.5,4.5
parent: 1
- uid: 42
components:
- type: Transform
pos: 18.5,3.5
parent: 1
- uid: 43
components:
- type: Transform
pos: 19.5,4.5
parent: 1
- uid: 60
components:
- type: Transform
pos: 27.5,5.5
parent: 1
- uid: 98
components:
- type: Transform
pos: 25.5,2.5
parent: 1
- uid: 100
components:
- type: Transform
pos: 28.5,3.5
parent: 1
- uid: 139
components:
- type: Transform
pos: 24.5,4.5
parent: 1
- proto: CP14WallStone
entities:
- uid: 7
components:
- type: Transform
pos: 1.5,4.5
parent: 1
- uid: 10
components:
- type: Transform
pos: 3.5,5.5
parent: 1
- uid: 35
components:
- type: Transform
pos: 8.5,1.5
parent: 1
- uid: 47
components:
- type: Transform
pos: 8.5,5.5
parent: 1
- uid: 48
components:
- type: Transform
pos: 12.5,0.5
parent: 1
- uid: 49
components:
- type: Transform
pos: 14.5,2.5
parent: 1
- uid: 50
components:
- type: Transform
pos: 11.5,0.5
parent: 1
- uid: 51
components:
- type: Transform
pos: 11.5,1.5
parent: 1
- uid: 52
components:
- type: Transform
pos: 12.5,4.5
parent: 1
- uid: 53
components:
- type: Transform
pos: 10.5,4.5
parent: 1
- uid: 54
components:
- type: Transform
pos: 11.5,4.5
parent: 1
- uid: 55
components:
- type: Transform
pos: 14.5,4.5
parent: 1
- uid: 56
components:
- type: Transform
pos: 8.5,4.5
parent: 1
- uid: 57
components:
- type: Transform
pos: 10.5,0.5
parent: 1
- uid: 58
components:
- type: Transform
pos: 10.5,3.5
parent: 1
- uid: 59
components:
- type: Transform
pos: 10.5,6.5
parent: 1
- uid: 65
components:
- type: Transform
pos: 1.5,6.5
parent: 1
- uid: 66
components:
- type: Transform
pos: 9.5,3.5
parent: 1
- uid: 67
components:
- type: Transform
pos: 12.5,1.5
parent: 1
- uid: 68
components:
- type: Transform
pos: 5.5,5.5
parent: 1
- uid: 69
components:
- type: Transform
pos: 5.5,4.5
parent: 1
- uid: 70
components:
- type: Transform
pos: 6.5,4.5
parent: 1
- uid: 71
components:
- type: Transform
pos: 12.5,3.5
parent: 1
- uid: 73
components:
- type: Transform
pos: 5.5,3.5
parent: 1
- uid: 77
components:
- type: Transform
pos: 4.5,4.5
parent: 1
- uid: 78
components:
- type: Transform
pos: 4.5,3.5
parent: 1
- uid: 79
components:
- type: Transform
pos: 2.5,3.5
parent: 1
- uid: 80
components:
- type: Transform
pos: 3.5,4.5
parent: 1
- uid: 81
components:
- type: Transform
pos: 1.5,1.5
parent: 1
- uid: 82
components:
- type: Transform
pos: 4.5,0.5
parent: 1
- uid: 83
components:
- type: Transform
pos: 6.5,1.5
parent: 1
- uid: 84
components:
- type: Transform
pos: 2.5,4.5
parent: 1
- proto: CP14WindowIceBlock
entities:
- uid: 3
components:
- type: Transform
pos: 28.5,2.5
parent: 1
- uid: 4
components:
- type: Transform
pos: 30.5,3.5
parent: 1
- uid: 9
components:
- type: Transform
pos: 20.5,5.5
parent: 1
- uid: 26
components:
- type: Transform
pos: 28.5,6.5
parent: 1
- uid: 27
components:
- type: Transform
pos: 29.5,5.5
parent: 1
- uid: 32
components:
- type: Transform
pos: 18.5,1.5
parent: 1
- uid: 36
components:
- type: Transform
pos: 21.5,4.5
parent: 1
- uid: 40
components:
- type: Transform
pos: 21.5,3.5
parent: 1
- uid: 44
components:
- type: Transform
pos: 19.5,1.5
parent: 1
- uid: 45
components:
- type: Transform
pos: 17.5,2.5
parent: 1
- uid: 62
components:
- type: Transform
pos: 16.5,4.5
parent: 1
- uid: 63
components:
- type: Transform
pos: 16.5,3.5
parent: 1
- uid: 64
components:
- type: Transform
pos: 22.5,3.5
parent: 1
- uid: 88
components:
- type: Transform
pos: 19.5,0.5
parent: 1
- uid: 89
components:
- type: Transform
pos: 17.5,6.5
parent: 1
- uid: 111
components:
- type: Transform
pos: 25.5,1.5
parent: 1
- uid: 113
components:
- type: Transform
pos: 28.5,1.5
parent: 1
- uid: 115
components:
- type: Transform
pos: 27.5,6.5
parent: 1
- uid: 119
components:
- type: Transform
pos: 30.5,5.5
parent: 1
- uid: 120
components:
- type: Transform
pos: 25.5,0.5
parent: 1
- uid: 129
components:
- type: Transform
pos: 26.5,0.5
parent: 1
- uid: 130
components:
- type: Transform
pos: 29.5,3.5
parent: 1
- uid: 132
components:
- type: Transform
pos: 24.5,2.5
parent: 1
- uid: 133
components:
- type: Transform
pos: 29.5,2.5
parent: 1
- uid: 138
components:
- type: Transform
pos: 29.5,6.5
parent: 1
- uid: 140
components:
- type: Transform
pos: 27.5,1.5
parent: 1
- uid: 144
components:
- type: Transform
pos: 26.5,5.5
parent: 1
- uid: 146
components:
- type: Transform
pos: 24.5,1.5
parent: 1
...

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -32,6 +32,7 @@
id: AdminInstantEffectFlash
suffix: Flash
parent: AdminInstantEffectBase
categories: [ ForkFiltered ] #Allow in for CE
components:
- type: FlashOnTrigger
range: 7
@@ -42,6 +43,7 @@
id: AdminInstantEffectSmoke3
suffix: Smoke (03 sec)
parent: AdminInstantEffectBase
categories: [ ForkFiltered ] #Allow in for CE
components:
- type: SmokeOnTrigger
duration: 3
@@ -56,6 +58,7 @@
id: AdminInstantEffectSmoke10
suffix: Smoke (10 sec)
parent: AdminInstantEffectBase
categories: [ ForkFiltered ] #Allow in for CE
components:
- type: SmokeOnTrigger
duration: 10
@@ -70,6 +73,7 @@
id: AdminInstantEffectSmoke30
suffix: Smoke (30 sec)
parent: AdminInstantEffectBase
categories: [ ForkFiltered ] #Allow in for CE
components:
- type: SmokeOnTrigger
duration: 30
@@ -97,6 +101,7 @@
id: AdminInstantEffectGravityWell
suffix: Gravity Well
parent: AdminInstantEffectBase
categories: [ ForkFiltered ] #Allow in for CE
components:
- type: SoundOnTrigger
removeOnTrigger: true

View File

@@ -125,7 +125,6 @@
components:
- type: StorageFill
contents:
- id: CP14GuidebookDemiplanes
- id: CP14ClothingCloakGuildmasterCape
prob: 1
- id: CP14ClothingShirtCottonWhite

View File

@@ -1,63 +0,0 @@
- type: entity
id: CP14ActionSpellDemiplaneInfiltration
parent: CP14ActionSpellBase
name: Demiplane infiltration
description: You get inside the demiplane of your choice.
components:
- type: Sprite
sprite: _CP14/Actions/Spells/dimension.rsi
state: rift_arrow
- type: CP14MagicEffectManaCost
manaCost: 25
- type: CP14MagicEffect
telegraphyEffects:
- !type:CP14SpellSpawnEntityOnTarget
spawns:
- CP14ImpactEffectShadowStep
effects:
- !type:CP14SpellDemiplaneInfiltration
- type: CP14MagicEffectVerbalAspect
startSpeech: "Ego intrabo et..."
endSpeech: "salvabo socios meos"
- type: CP14MagicEffectCastingVisual
proto: CP14RuneDemiplaneInfiltration
- type: Action
icon:
sprite: _CP14/Actions/Spells/dimension.rsi
state: rift_arrow
- type: TargetAction
checkCanAccess: true
range: 3
- type: EntityTargetAction
whitelist:
components:
- CP14DemiplaneRift
canTargetSelf: false
event: !type:CP14DelayedEntityTargetActionEvent
cooldown: 50
castDelay: 5
distanceThreshold: 3
breakOnMove: true
- type: entity
id: CP14RuneDemiplaneInfiltration
parent: CP14BaseMagicRune
categories: [ HideSpawnMenu ]
save: false
components:
- type: PointLight
color: "#5e427e"
- type: Sprite
layers:
- state: sun
color: "#5e427e"
shader: unshaded
- type: entity
parent: CP14BaseSpellScrollDimension
id: CP14SpellScrollDemiplaneInfiltration
name: demiplane infiltration spell scroll
components:
- type: CP14SpellStorage
spells:
- CP14ActionSpellDemiplaneInfiltration

View File

@@ -1,76 +0,0 @@
- type: entity
id: CP14ActionSpellMonolithWarp
parent: CP14ActionSpellBase
name: Monolith warp
description: You open a dimensional rift that transports the creatures around it to the demiplane link crystal
components:
- type: Sprite
sprite: _CP14/Actions/Spells/dimension.rsi
state: demi_arrow
- type: CP14MagicEffectManaCost
manaCost: 25
- type: CP14MagicEffect
telegraphyEffects:
- !type:CP14SpellSpawnEntityOnTarget
spawns:
- CP14ImpactEffectShadowStep
effects:
- !type:CP14SpellSpawnEntityOnTarget
spawns:
- CP14GuildmaterTimedTeleport
- type: CP14MagicEffectVerbalAspect
startSpeech: "Crystal, ego..."
endSpeech: "vado ad vos"
- type: CP14MagicEffectCastingVisual
proto: CP14RuneDemiplaneInfiltration
- type: Action
icon:
sprite: _CP14/Actions/Spells/dimension.rsi
state: demi_arrow
- type: TargetAction
range: 7
- type: WorldTargetAction
event: !type:CP14DelayedWorldTargetActionEvent
cooldown: 50
- type: entity
id: CP14GuildmaterTimedTeleport
categories: [ ForkFiltered ]
suffix: Teleport to demiplane crystal
name: pulsating demiplane rift
description: This rift is gaining strength, and will trap all nearby creatures in a demiplane in a second!
save: false
components:
- type: Transform
anchored: True
- type: Clickable
- type: Physics
canCollide: false
bodyType: Static
- type: Sprite
drawdepth: Effects
sprite: /Textures/_CP14/Structures/Dungeon/demiplan_rift.rsi
layers:
- state: pulse
shader: unshaded
- type: PointLight
radius: 8
color: "#5e427e"
- type: SingularityDistortion
falloffPower: 1.5
intensity: 50
- type: AmbientSound
volume: -3
range: 7
sound:
path: /Audio/Ambience/Objects/gravity_gen_hum.ogg
- type: CP14MonolithTimedPassway
- type: entity
parent: CP14BaseSpellScrollDimension
id: CP14SpellScrollMonolithWarp
name: demiplane link spell scroll
components:
- type: CP14SpellStorage
spells:
- CP14ActionSpellMonolithWarp

View File

@@ -43,5 +43,4 @@
drawdepth: Effects
sprite: _CP14/Effects/Magic/cast_impact.rsi
noRot: true
- type: CP14SpawnOutOfDemiplane

View File

@@ -0,0 +1,29 @@
- type: entity
id: CP14ShipExplosion
parent: AdminInstantEffectBase
categories: [ ForkFiltered ]
suffix: Fire Explosion
components:
- type: Explosive
totalIntensity: 75
intensitySlope: 1
maxIntensity: 400
explosionType: FireBomb
- type: ExplodeOnTrigger
- type: entity
id: CP14ShipExplosionBig
parent: AdminInstantEffectBase
categories: [ ForkFiltered ]
suffix: Big Fire Explosion
components:
- type: Explosive
totalIntensity: 90
intensitySlope: 0.1
maxIntensity: 400
explosionType: Default
- type: ExplodeOnTrigger
- type: FlashOnTrigger
range: 30
- type: SpawnOnTrigger
proto: GrenadeFlashEffect

View File

@@ -0,0 +1,36 @@
- type: entity
parent: MarkerBase
id: CP14SpawnerLocationConnection
name: Location connection
categories: [ HideSpawnMenu ]
components:
- type: Sprite
layers:
- state: green
- sprite: _CP14/Structures/Flora/demiplane_cracks.rsi
state: crack
- type: CP14GlobalWorldConnector
- type: entity
id: CP14LocationPassway
parent: BasePortal
categories: [ HideSpawnMenu ]
name: location passway
description: A gap in space that allows you to travel between worlds.
components:
- type: Sprite
drawdepth: Effects
sprite: /Textures/_CP14/Structures/Dungeon/demiplan_rift.rsi
layers:
- state: anom
shader: unshaded
- type: SingularityDistortion
falloffPower: 1.5
intensity: 50
- type: AmbientSound
volume: -3
range: 7
sound:
path: /Audio/Ambience/Objects/gravity_gen_hum.ogg
- type: Portal
canTeleportToOtherMaps: true

View File

@@ -1,12 +0,0 @@
- type: entity
parent: MarkerBase
id: CP14DemiplaneEntryPointMarker
name: demiplane entry point
categories: [ ForkFiltered ]
components:
- type: Sprite
layers:
- state: green
- sprite: /Textures/_CP14/Structures/Dungeon/demiplan_rift.rsi
state: pulse
- type: CP14DemiplaneRift

View File

@@ -67,7 +67,6 @@
types:
CP14ManaDepletion: 1
- type: CP14ZLevelMover
- type: CP14DemiplaneForceExtract
- type: UserInterface
interfaces:
enum.CP14ReligionEntityUiKey.Key:

View File

@@ -213,9 +213,6 @@
- type: Tag
tags:
- FootstepSound
- type: CP14DemiplaneForceExtract
- type: CP14DemiplaneStabilizer
requireAlive: true
- type: CP14IgnitionModifier
ignitionTimeModifier: 2.5
- type: MovementAlwaysTouching

View File

@@ -112,10 +112,6 @@
- type: Inventory
templateId: CP14Human
- type: TransferMindOnGib
- type: CP14DemiplaneForceExtract
enabled: false
- type: CP14DemiplaneStabilizer
enabled: false
- type: GhostTakeoverAvailable
- type: entity

View File

@@ -39,25 +39,3 @@
guides:
- CP14_RU_Alchemy
- CP14_EN_Alchemy
- type: entity
id: CP14GuidebookDemiplanes
parent: CP14GuidebookBase
name: demiplane research guide
description: "A 20-minute adventure: in and out."
components:
- type: Sprite
layers:
- state: paper
- state: cover_base
color: "#5f1280"
- state: decor_frame
color: "#ffe269"
- state: icon_magic
shader: unshaded
- state: bookmark_short
- type: GuideHelp
guides:
- CP14_RU_Demiplanes
- CP14_EN_Demiplanes

View File

@@ -1,54 +0,0 @@
- type: entity
parent: BaseItem
id: CP14BaseDemiplaneKey
categories: [ ForkFiltered ]
name: demiplane coordinate key
description: A temporary blob of energy linking the real world and the demiplane. Use it before it dissipates.
components:
- type: Item
size: Ginormous
- type: Sprite
noRot: true
drawdepth: Effects
sprite: _CP14/Structures/Dungeon/demiplan_rift_core.rsi
layers:
- state: coord
- type: GuideHelp
guides:
- CP14_RU_Demiplanes
- CP14_EN_Demiplanes
- type: CP14DemiplaneUsingOpen
- type: CP14DemiplaneData
selectedModifiers:
- DemiplaneDecor
- Core
- EntryRoom
- Exit
- type: TimedDespawn
lifetime: 120
- type: CanMoveInAir
- type: RandomWalk
minSpeed: 0.25
maxSpeed: 0.75
- type: Physics
bodyType: Dynamic
bodyStatus: InAir
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.25,-0.25,0.25,0.25"
density: 20
mask:
#- ItemMask # CP14 swap ItemMask to Impassable only
- Impassable
restitution: 0.3 # fite me
friction: 0.2
- type: PointLight
enabled: true
color: "#8f42ff"
energy: 1
radius: 2
netsync: false

View File

@@ -79,7 +79,6 @@
- CP14ActionSpellShadowStep
- CP14ActionSpellShadowGrab
- CP14ActionSpellShadowSwap
- CP14ActionSpellDemiplaneInfiltration
- type: Sprite
sprite: _CP14/Objects/Weapons/Magic/TwoHandedStaff/shadow_staff.rsi
- type: Clothing

View File

@@ -8,7 +8,6 @@
- BaseStationAlertLevels
- BaseStationRecords # Required for lobby manifest + cryo leave
- CP14BaseStationCommonObjectives
- CP14BaseStationDemiplaneMap
- CP14BaseStationEconomy
#- CP14BaseStationGods
@@ -18,12 +17,6 @@
components:
- type: CP14StationCommonObjectives
- type: entity
id: CP14BaseStationDemiplaneMap
abstract: true
components:
- type: CP14StationDemiplaneMap
- type: entity
id: CP14BaseStationEconomy
abstract: true

View File

@@ -1,31 +0,0 @@
- type: entity
id: CP14DemiplaneHint
name: astral crack
description: Those cracks always lead to a way out of the damn place
categories: [ ForkFiltered ]
placement:
mode: SnapgridCenter
components:
- type: Clickable
- type: Sprite
sprite: /Textures/_CP14/Structures/Flora/demiplane_cracks.rsi
layers:
- state: crack3
shader: unshaded
drawdepth: LowFloors
- type: SyncSprite
- type: RequiresTile
- type: CP14DemiplaneHint
- type: RandomSprite
available:
- 0:
crack: ""
crack2: ""
crack3: ""
crack4: ""
- type: PointLight
netSync: false
radius: 1.3
color: "#c6529f"
energy: 1

View File

@@ -91,7 +91,6 @@
children:
- id: CP14GuidebookImperialLaws
- id: CP14GuidebookAlchemy
- id: CP14GuidebookDemiplanes
#Lore books
- !type:GroupSelector
children:

View File

@@ -1,107 +0,0 @@
- type: entity
id: CP14DemiplanRiftCore
categories: [ ForkFiltered ]
name: demiplan rift core
description: A subtle connection between the real world and the demiplane where the adventurers went. Sooner or later they will return from there.
components:
- type: CP14DemiplaneRift
- type: Transform
anchored: True
- type: Clickable
- type: Physics
canCollide: false
bodyType: Static
- type: Sprite
noRot: true
drawdepth: Effects
sprite: /Textures/_CP14/Structures/Dungeon/demiplan_rift_core.rsi
layers:
- state: connective
shader: unshaded
- type: PointLight
radius: 2
energy: 2
color: "#371c5c"
netsync: false
- type: GuideHelp
guides:
- CP14_RU_Demiplanes
- CP14_EN_Demiplanes
- type: Speech
speechVerb: Ghost
- type: entity
id: CP14DemiplaneTimedRadiusPassway
parent: CP14DemiplanRiftCore
name: pulsating demiplane rift
description: This rift is gaining strength, and will trap all nearby creatures in a demiplane in a second!
components:
- type: CP14DemiplaneRift
activeTeleport: false
- type: CP14DemiplaneRadiusTimedPassway
maxEntities: 4
delay: 4
- type: Sprite
drawdepth: Effects
sprite: /Textures/_CP14/Structures/Dungeon/demiplan_rift.rsi
layers:
- state: pulse
shader: unshaded
- type: PointLight
radius: 8
- type: SingularityDistortion
falloffPower: 1.5
intensity: 50
- type: AmbientSound
volume: -3
range: 7
sound:
path: /Audio/Ambience/Objects/gravity_gen_hum.ogg
- type: GuideHelp
guides:
- CP14_RU_Demiplanes
- CP14_EN_Demiplanes
- type: entity
id: CP14DemiplanePassway
parent: CP14DemiplanRiftCore
name: demiplane rift
description: A gap in space that allows you to travel between world and demiplanes.
components:
- type: CP14DemiplaneRiftOpened
- type: CP14DemiplaneRift
activeTeleport: false
- type: Sprite
drawdepth: Effects
sprite: /Textures/_CP14/Structures/Dungeon/demiplan_rift.rsi
layers:
- state: anom
shader: unshaded
- type: SingularityDistortion
falloffPower: 1.5
intensity: 50
- type: AmbientSound
volume: -3
range: 7
sound:
path: /Audio/Ambience/Objects/gravity_gen_hum.ogg
- type: GuideHelp
guides:
- CP14_RU_Demiplanes
- CP14_EN_Demiplanes
- type: entity
id: CP14DemiplanePasswayRed
parent: CP14DemiplanePassway
components:
- type: Sprite
drawdepth: Effects
sprite: /Textures/_CP14/Structures/Dungeon/demiplan_rift.rsi
layers:
- state: anom
shader: unshaded
color: red
- type: PointLight
color: red

View File

@@ -34,8 +34,8 @@
- !type:PulseBehaviour
interpolate: Cubic
maxDuration: 5
startValue: 1.0
endValue: 40.0
startValue: 0.1
endValue: 2.0
property: Energy
isLooped: true
enabled: true
@@ -52,15 +52,6 @@
range: 10
sound:
path: /Audio/_CP14/Effects/demiplane_heartbeat.ogg
- type: ActivatableUI
key: enum.CP14DemiplaneMapUiKey.Key
requiresComplex: true
singleUser: true
- type: CP14DemiplaneNavigationMap
- type: UserInterface
interfaces:
enum.CP14DemiplaneMapUiKey.Key:
type: CP14DemiplaneMapBoundUserInterface
- type: entity
id: CP14PortalFrameCrystal
@@ -112,89 +103,4 @@
- type: PointLight
color: "#9c34e6"
energy: 2.5
radius: 8
- type: entity
id: CP14DemiplaneCore
categories: [ ForkFiltered ]
parent: BaseStructure
name: demiplane core
description: The heart of the demiplane. Your task is to break it and escape from this place before it collapses.
components:
- type: CP14DemiplaneCore
- type: Transform
anchored: true
- type: Clickable
- type: Physics
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.25,-0.25,0.25,0.25"
density: 600
mask:
- MachineMask
layer:
- MidImpassable
- LowImpassable
- type: Tag
tags:
- Structure
- type: Sprite
noRot: true
sprite: _CP14/Structures/Specific/Thaumaturgy/energy_monolith_big.rsi
layers:
- state: dimension_core
drawdepth: Mobs
offset: 0,0.4
- type: PointLight
enabled: true
color: "#8f42ff"
castShadows: false
energy: 1
radius: 5
netsync: false
- type: LightBehaviour
behaviours:
- !type:PulseBehaviour
interpolate: Cubic
maxDuration: 5
startValue: 1.0
endValue: 40.0
property: Energy
isLooped: true
enabled: true
- type: AmbientSound
volume: 5
range: 10
sound:
path: /Audio/_CP14/Effects/demiplane_heartbeat.ogg
- type: Damageable
damageContainer: Inorganic
damageModifierSet: CP14Rock
- type: MeleeSound
soundGroups:
Brute:
collection: GlassSmash
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 50
behaviors:
- !type:PlaySoundBehavior
sound:
collection: GlassBreak
- !type:DoActsBehavior
acts: ["Destruction"]
- !type:SpawnEntitiesBehavior
spawn:
CP14DimensitCrystal:
min: 2
max: 5
- !type:SpawnEntitiesBehavior
spawn:
CP14SkyLightningPurple:
min: 1
max: 1
radius: 8

View File

@@ -32,6 +32,20 @@
CP14Innkeeper:
- CP14PersonalCurrencyCollectObjectiveGroup
# crashing
#- type: entity
# id: CP14CrashToWindlandsRule
# parent: CP14BaseGameRule
# components:
# - type: CP14CrashingShipRule
# - type: CP14ExpeditionToWindlandsRule
# modifiers:
# - WeatherInfinityStorm
# - MapLightCycleDefault
# - Ruins
# - Geodes
# event schedulers
- type: entity

Some files were not shown because too many files have changed in this diff Show More