Demiplane exploration map (#1251)

* dehardcode nodeTreeControl part 1

* finish

* demiplane map system setup

* link map entity with station data!

* random demiplane map generation

* redesign demiplane map node tree

* center

* map generate v2

* location generation

* modifier generation

* missing location icons

* ui polish

* data refactor

* fix line color

* ejectabling

* split demiplane component into two

* blocker attempt

* revoking

* fix frontier calculation

* dimensit

* demiplane core room spawning

* demiplane cool destruction

* Update round_end.yml

* progression works now!

* Update CP14NodeTree.cs

* Update CP14NodeTree.cs

* documentation pass

* fix abusing, some balance pass

* demiplane naming fix

* см

* location names

* delete old keys

* Update buy.yml

* Update migration.yml

* fix guidebook

* Update misc.yml

* Update misc.yml
This commit is contained in:
Ed
2025-05-05 15:19:44 +03:00
committed by GitHub
parent 63a2248201
commit aeedff5caa
108 changed files with 2936 additions and 617 deletions

View File

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

View File

@@ -0,0 +1,34 @@
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

@@ -0,0 +1,72 @@
<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="1200 850"
SetSize="1200 850">
<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="CP14Astral" 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>
</PanelContainer>
</BoxContainer>
</BoxContainer>
</demiplaneTraveling:CP14DemiplaneMapWindow>

View File

@@ -0,0 +1,246 @@
using System.Numerics;
using System.Text;
using Content.Client._CP14.UserInterface.Systems.NodeTree;
using Content.Shared._CP14.DemiplaneTraveling;
using Robust.Client.AutoGenerated;
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!;
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.Scanned,
active: node.Value.InFrontierZone || node.Value.Scanned,
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;
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.Scanned)
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.Scanned)
sb.Append(Loc.GetString("cp14-demiplane-map-status-scanned"));
sb.Append("\n \n");
if (node.AdditionalLevel > 0 && !node.Scanned)
{
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;
}
private void DeselectNode()
{
Name.Text = string.Empty;
Description.Text = string.Empty;
LocationView.Texture = null;
EjectButton.Disabled = true;
RevokeButton.Disabled = true;
}
}

View File

@@ -1,7 +0,0 @@
<ui:CP14SkillTreeGraphControl
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client._CP14.Skill.Ui"
xmlns:ui="clr-namespace:Content.Client._CP14.Skill.Ui"
HorizontalExpand="True"
VerticalExpand="True"
MouseFilter="Stop"/>

View File

@@ -1,227 +0,0 @@
using System.Linq;
using System.Numerics;
using Content.Shared._CP14.Skill;
using Content.Shared._CP14.Skill.Components;
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared._CP14.Skill.Restrictions;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Utility;
using Robust.Shared.Input;
using Robust.Shared.Prototypes;
namespace Content.Client._CP14.Skill.Ui;
[GenerateTypedNameReferences]
public sealed partial class CP14SkillTreeGraphControl : BoxContainer
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
private readonly CP14SharedSkillSystem _skillSystem;
private Entity<CP14SkillStorageComponent>? _player;
private IEnumerable<CP14SkillPrototype> _allSkills;
private CP14SkillPrototype? _hoveredNode;
private CP14SkillPrototype? _selectedNode;
public CP14SkillTreePrototype? Tree;
private bool dragging = false;
private Vector2 _previousMousePosition = Vector2.Zero;
private Vector2 _globalOffset = new (60,60);
private const float GridSize = 25f;
public event Action<CP14SkillPrototype?>? OnNodeSelected;
public event Action<Vector2>? OnOffsetChanged;
public CP14SkillTreeGraphControl()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
_skillSystem = _entManager.System<CP14SharedSkillSystem>();
_allSkills = _proto.EnumeratePrototypes<CP14SkillPrototype>();
_proto.PrototypesReloaded += _ => _allSkills = _proto.EnumeratePrototypes<CP14SkillPrototype>().OrderBy(skill => _skillSystem.GetSkillName(skill)).ToList();
OnOffsetChanged?.Invoke(_globalOffset);
}
public void UpdateState(Entity<CP14SkillStorageComponent>? player)
{
_player = player;
OnOffsetChanged?.Invoke(_globalOffset);
}
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
{
base.KeyBindDown(args);
if (args.Handled)
return;
if (args.Function == EngineKeyFunctions.UIClick)
{
dragging = true;
if (_hoveredNode == null)
return;
OnNodeSelected?.Invoke(_hoveredNode);
UserInterfaceManager.ClickSound();
_selectedNode = _hoveredNode;
}
if (args.Function == EngineKeyFunctions.UIRightClick)
{
_globalOffset = new Vector2(60, 60); // Reset offset on right click
OnOffsetChanged?.Invoke(_globalOffset);
}
}
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
{
base.KeyBindUp(args);
if (args.Handled || args.Function != EngineKeyFunctions.UIClick)
return;
dragging = false;
}
protected override void ExitedTree()
{
base.ExitedTree();
OnNodeSelected?.Invoke(null);
}
protected override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
_hoveredNode = null;
if (_player == null || Tree == null)
{
return;
}
var cursor = (UserInterfaceManager.MousePositionScaled.Position * UIScale) - GlobalPixelPosition;
if (dragging)
{
var delta = cursor - _previousMousePosition;
_globalOffset += delta;
OnOffsetChanged?.Invoke(_globalOffset);
}
_previousMousePosition = cursor;
var playerSkills = _player.Value.Comp.LearnedSkills;
//Draw connection lines
foreach (var skill in _allSkills)
{
if (skill.Tree != Tree)
continue;
var fromPos = skill.SkillUiPosition * GridSize * UIScale + _globalOffset;
foreach (var req in skill.Restrictions)
{
switch (req)
{
case NeedPrerequisite prerequisite:
if (!_proto.TryIndex(prerequisite.Prerequisite, out var prerequisiteSkill))
continue;
if (prerequisiteSkill.Tree != Tree)
continue;
var learned = playerSkills.Contains(skill.ID);
var toPos = prerequisiteSkill.SkillUiPosition * GridSize * UIScale + _globalOffset;
var color = learned ? Color.White : Color.FromSrgb(new Color(0.7f, 0.7f, 0.7f));
handle.DrawLine(fromPos, toPos, color);
break;
}
}
}
//Draw skill icons over lines
foreach (var skill in _allSkills)
{
if (skill.Tree != Tree)
continue;
//TODO: Not optimized, recalculates every frame. Needs to be calculated on state update and cached.
var canBeLearned = _skillSystem.CanLearnSkill(_player.Value, skill, _player.Value.Comp);
var pos = skill.SkillUiPosition * GridSize * UIScale + _globalOffset;
// Base skill icon
var baseTexture = skill.Icon.Frame0();
var baseSize = new Vector2(baseTexture.Width, baseTexture.Height) * 1.5f * UIScale;
var baseQuad = new UIBox2(pos - baseSize / 2, pos + baseSize / 2);
var hovered = (cursor - pos).LengthSquared() <= (baseSize.X / 2) * (baseSize.X / 2);
// Frame
var frameTexture = Tree.FrameIcon.Frame0();
var frameSize = new Vector2(frameTexture.Width, frameTexture.Height) * 1.5f * UIScale;
var frameQuad = new UIBox2(pos - frameSize / 2, pos + frameSize / 2);
handle.DrawTextureRect(frameTexture, frameQuad, canBeLearned ? Color.White : Color.FromSrgb(new Color(0.7f, 0.7f, 0.7f)));
// Selected Skill
if (_selectedNode == skill)
{
var selectedTexture = Tree.SelectedIcon.Frame0();
var selectedSize = new Vector2(selectedTexture.Width, selectedTexture.Height) * 1.5f * UIScale;
var selectedQuad = new UIBox2(pos - selectedSize / 2, pos + selectedSize / 2);
handle.DrawTextureRect(selectedTexture, selectedQuad, Color.White);
}
// Hovered Skill
if (hovered)
{
_hoveredNode = skill;
var hoveredTexture = Tree.HoveredIcon.Frame0();
var hoveredSize = new Vector2(hoveredTexture.Width, hoveredTexture.Height) * 1.5f * UIScale;
var hoveredQuad = new UIBox2(pos - hoveredSize / 2, pos + hoveredSize / 2);
handle.DrawTextureRect(hoveredTexture, hoveredQuad, Color.White);
}
var learned = playerSkills.Contains(skill.ID);
var allowedToLearn = _skillSystem.AllowedToLearn(_player.Value, skill, _player.Value.Comp);
// Learned Skill
if (learned)
{
var learnedTexture = Tree.LearnedIcon.Frame0();
var learnedSize = new Vector2(learnedTexture.Width, learnedTexture.Height) * 1.5f * UIScale;
var learnedQuad = new UIBox2(pos - learnedSize / 2, pos + learnedSize / 2);
handle.DrawTextureRect(learnedTexture, learnedQuad, Color.White);
}
else if (canBeLearned)
{
var availableTexture = Tree.AvailableIcon.Frame0();
var availableSize = new Vector2(availableTexture.Width, availableTexture.Height) * 1.5f * UIScale;
var availableQuad = new UIBox2(pos - availableSize / 2, pos + availableSize / 2);
handle.DrawTextureRect(availableTexture, availableQuad, Color.White);
}
var iconColor = allowedToLearn switch
{
true when !learned => Color.FromSrgb(new Color(0.7f, 0.7f, 0.7f)),
false when !learned => Color.FromSrgb(new Color(0f, 0f, 0f)),
_ => Color.White
};
handle.DrawTextureRect(baseTexture, baseQuad, iconColor);
}
}
}

View File

@@ -0,0 +1,38 @@
using System.Numerics;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Client._CP14.UserInterface.Systems.NodeTree;
public sealed class CP14NodeTreeElement(string nodeKey, bool gained ,bool active, Vector2 uiPosition, SpriteSpecifier? icon = null)
{
public string NodeKey = nodeKey;
public bool Gained = gained;
public bool Active = active;
public Vector2 UiPosition = uiPosition;
public SpriteSpecifier? Icon = icon;
}
public sealed class CP14NodeTreeUiState(
HashSet<CP14NodeTreeElement> nodes,
HashSet<(string, string)>? edges = null,
SpriteSpecifier? frameIcon = null,
SpriteSpecifier? hoveredIcon = null,
SpriteSpecifier? selectedIcon = null,
SpriteSpecifier? learnedIcon = null,
Color? lineColor = null,
Color? activeLineColor = null
) : BoundUserInterfaceState
{
public HashSet<CP14NodeTreeElement> Nodes = nodes;
public HashSet<(string, string)> Edges = edges ?? new HashSet<(string, string)>();
public SpriteSpecifier FrameIcon = frameIcon ?? new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Interface/NodeTree/default.rsi"), "frame");
public SpriteSpecifier HoveredIcon = hoveredIcon ?? new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Interface/NodeTree/default.rsi"), "hovered");
public SpriteSpecifier SelectedIcon = selectedIcon?? new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Interface/NodeTree/default.rsi"), "selected");
public SpriteSpecifier LearnedIcon = learnedIcon?? new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Interface/NodeTree/default.rsi"), "learned");
public Color LineColor = lineColor ?? Color.Gray;
public Color ActiveLineColor = activeLineColor ?? Color.White;
}

View File

@@ -0,0 +1,6 @@
<ui:CP14NodeTreeGraphControl
xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client._CP14.UserInterface.Systems.NodeTree"
HorizontalExpand="True"
VerticalExpand="True"
MouseFilter="Stop"/>

View File

@@ -0,0 +1,179 @@
using System.Numerics;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Utility;
using Robust.Shared.Input;
using Serilog;
namespace Content.Client._CP14.UserInterface.Systems.NodeTree;
[GenerateTypedNameReferences]
public sealed partial class CP14NodeTreeGraphControl : BoxContainer
{
[Dependency] private readonly IEntityManager _entManager = default!;
private CP14NodeTreeUiState? _state;
private CP14NodeTreeElement? _hoveredNode;
private CP14NodeTreeElement? _selectedNode;
private Dictionary<string, CP14NodeTreeElement> _nodeDict = new();
private bool _dragging = false;
private Vector2 _previousMousePosition = Vector2.Zero;
private Vector2 _globalOffset = new (60,60);
public event Action<CP14NodeTreeElement?>? OnNodeSelected;
public event Action<Vector2>? OnOffsetChanged;
public CP14NodeTreeGraphControl()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
OnOffsetChanged?.Invoke(_globalOffset);
}
public void UpdateState(CP14NodeTreeUiState state)
{
_state = state;
_nodeDict.Clear();
foreach (var node in state.Nodes)
{
_nodeDict[node.NodeKey] = node;
}
OnOffsetChanged?.Invoke(_globalOffset);
}
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
{
base.KeyBindDown(args);
if (args.Handled)
return;
if (args.Function == EngineKeyFunctions.UIClick)
{
_dragging = true;
if (_hoveredNode == null)
return;
OnNodeSelected?.Invoke(_hoveredNode);
UserInterfaceManager.ClickSound();
_selectedNode = _hoveredNode;
}
if (args.Function == EngineKeyFunctions.UIRightClick)
{
_globalOffset = new Vector2(60, 60); // Reset offset on right click
OnOffsetChanged?.Invoke(_globalOffset);
}
}
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
{
base.KeyBindUp(args);
if (args.Handled || args.Function != EngineKeyFunctions.UIClick)
return;
_dragging = false;
}
protected override void ExitedTree()
{
base.ExitedTree();
OnNodeSelected?.Invoke(null);
}
protected override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
if (_state is null)
return;
_hoveredNode = null;
var cursor = (UserInterfaceManager.MousePositionScaled.Position * UIScale) - GlobalPixelPosition;
if (_dragging)
{
var delta = cursor - _previousMousePosition;
_globalOffset += delta;
OnOffsetChanged?.Invoke(_globalOffset);
}
_previousMousePosition = cursor;
//Draw connection lines
foreach (var edge in _state.Edges)
{
var node1 = _nodeDict[edge.Item1];
var node2 = _nodeDict[edge.Item2];
var fromPos = node1.UiPosition * UIScale + _globalOffset;
var toPos = node2.UiPosition * UIScale + _globalOffset;
var lineColor = node1.Gained || node2.Gained ? _state.ActiveLineColor : _state.LineColor;
handle.DrawLine(fromPos, toPos, lineColor);
}
//Draw Node icons over lines
foreach (var node in _state.Nodes)
{
var pos = node.UiPosition * UIScale + _globalOffset;
// Frame
var frameTexture = _state.FrameIcon.Frame0();
var frameSize = new Vector2(frameTexture.Width, frameTexture.Height) * 1.5f * UIScale;
var frameQuad = new UIBox2(pos - frameSize / 2, pos + frameSize / 2);
handle.DrawTextureRect(frameTexture, frameQuad);
// Selected Node
if (_selectedNode == node)
{
var selectedTexture = _state.SelectedIcon.Frame0();
var selectedSize = new Vector2(selectedTexture.Width, selectedTexture.Height) * 1.5f * UIScale;
var selectedQuad = new UIBox2(pos - selectedSize / 2, pos + selectedSize / 2);
handle.DrawTextureRect(selectedTexture, selectedQuad);
}
// Hovered Node
var hovered = (cursor - pos).LengthSquared() <= (frameSize.X / 2) * (frameSize.X / 2);
if (hovered)
{
_hoveredNode = node;
var hoveredTexture = _state.HoveredIcon.Frame0();
var hoveredSize = new Vector2(hoveredTexture.Width, hoveredTexture.Height) * 1.5f * UIScale;
var hoveredQuad = new UIBox2(pos - hoveredSize / 2, pos + hoveredSize / 2);
handle.DrawTextureRect(hoveredTexture, hoveredQuad);
}
// Learned Node
if (node.Gained)
{
var learnedTexture = _state.LearnedIcon.Frame0();
var learnedSize = new Vector2(learnedTexture.Width, learnedTexture.Height) * 1.5f * UIScale;
var learnedQuad = new UIBox2(pos - learnedSize / 2, pos + learnedSize / 2);
handle.DrawTextureRect(learnedTexture, learnedQuad);
}
// Base Node icon
if (node.Icon is not null)
{
var baseTexture = node.Icon.Frame0();
var baseSize = new Vector2(baseTexture.Width, baseTexture.Height) * 1.5f * UIScale;
var baseQuad = new UIBox2(pos - baseSize / 2, pos + baseSize / 2);
var tint = node.Gained || node.Active ? Color.White : Color.FromSrgb(new Color(0.6f, 0.6f, 0.6f));
handle.DrawTextureRect(baseTexture, baseQuad, tint);
}
}
}
}

View File

@@ -3,11 +3,13 @@ using System.Numerics;
using System.Text;
using Content.Client._CP14.Skill;
using Content.Client._CP14.Skill.Ui;
using Content.Client._CP14.UserInterface.Systems.NodeTree;
using Content.Client._CP14.UserInterface.Systems.Skill.Window;
using Content.Client.Gameplay;
using Content.Client.UserInterface.Controls;
using Content.Shared._CP14.Skill.Components;
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared._CP14.Skill.Restrictions;
using Content.Shared.Input;
using JetBrains.Annotations;
using Robust.Client.Player;
@@ -25,15 +27,22 @@ namespace Content.Client._CP14.UserInterface.Systems.Skill;
public sealed class CP14SkillUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>,
IOnSystemChanged<CP14ClientSkillSystem>
{
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
[UISystemDependency] private readonly CP14ClientSkillSystem _skill = default!;
private CP14SkillWindow? _window;
private CP14SkillPrototype? _selectedSkill;
private EntityUid? _targetPlayer;
private MenuButton? SkillButton => UIManager.GetActiveUIWidgetOrNull<Client.UserInterface.Systems.MenuBar.Widgets.GameTopMenuBar>()?.CP14SkillButton;
private IEnumerable<CP14SkillPrototype> _allSkills = [];
private CP14SkillPrototype? _selectedSkill;
private CP14SkillTreePrototype? _selectedSkillTree;
private MenuButton? SkillButton => UIManager
.GetActiveUIWidgetOrNull<Client.UserInterface.Systems.MenuBar.Widgets.GameTopMenuBar>()
?.CP14SkillButton;
public void OnStateEntered(GameplayState state)
{
@@ -47,14 +56,22 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
InputCmdHandler.FromDelegate(_ => ToggleWindow()))
.Register<CP14SkillUIController>();
_window.LearnButton.OnPressed += _ => _skill.RequestLearnSkill(_player.LocalEntity, _selectedSkill);
CacheSkillProto();
_proto.PrototypesReloaded += _ => CacheSkillProto();
_window.LearnButton.OnPressed += _ => _skill.RequestLearnSkill(_playerManager.LocalEntity, _selectedSkill);
_window.GraphControl.OnNodeSelected += SelectNode;
_window.GraphControl.OnOffsetChanged += offset =>
{
_window.ParallaxBackground.Offset = -offset * 0.25f + new Vector2(1000,1000); //hardcoding is bad
_window.ParallaxBackground.Offset = -offset * 0.25f + new Vector2(1000, 1000); //hardcoding is bad
};
}
private void CacheSkillProto()
{
_allSkills = _proto.EnumeratePrototypes<CP14SkillPrototype>();
}
public void OnStateExited(GameplayState state)
{
@@ -72,13 +89,13 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
public void OnSystemLoaded(CP14ClientSkillSystem system)
{
system.OnSkillUpdate += UpdateState;
_player.LocalPlayerDetached += CharacterDetached;
_playerManager.LocalPlayerDetached += CharacterDetached;
}
public void OnSystemUnloaded(CP14ClientSkillSystem system)
{
system.OnSkillUpdate -= UpdateState;
_player.LocalPlayerDetached -= CharacterDetached;
_playerManager.LocalPlayerDetached -= CharacterDetached;
}
public void UnloadButton()
@@ -113,38 +130,71 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
SkillButton!.Pressed = true;
}
private void SelectNode(CP14NodeTreeElement? node)
{
if (_window is null)
return;
if (_playerManager.LocalEntity == null)
return;
if (node == null)
{
DeselectNode();
return;
}
if (!_proto.TryIndex<CP14SkillPrototype>(node.NodeKey, out var skill))
{
DeselectNode();
return;
}
SelectNode(skill);
}
private void SelectNode(CP14SkillPrototype? skill)
{
if (_window is null)
return;
if (_player.LocalEntity == null)
if (_playerManager.LocalEntity == null)
return;
_selectedSkill = skill;
if (skill == null)
{
_window.SkillName.Text = string.Empty;
_window.SkillDescription.Text = string.Empty;
_window.SkillView.Texture = null;
_window.LearnButton.Disabled = true;
DeselectNode();
}
else
{
_window.SkillName.Text = _skill.GetSkillName(skill);
_window.SkillDescription.SetMessage(GetSkillDescription(skill));
_window.SkillView.Texture = skill.Icon.Frame0();
_window.LearnButton.Disabled = !_skill.CanLearnSkill(_player.LocalEntity.Value, skill);
_window.LearnButton.Disabled = !_skill.CanLearnSkill(_playerManager.LocalEntity.Value, skill);
_window.SkillCost.Text = skill.LearnCost.ToString();
}
UpdateGraphControl();
}
private void DeselectNode()
{
if (_window is null)
return;
_window.SkillName.Text = string.Empty;
_window.SkillDescription.Text = string.Empty;
_window.SkillView.Texture = null;
_window.LearnButton.Disabled = true;
}
private FormattedMessage GetSkillDescription(CP14SkillPrototype skill)
{
var msg = new FormattedMessage();
if (_player.LocalEntity == null)
if (_playerManager.LocalEntity == null)
return msg;
var sb = new StringBuilder();
@@ -155,7 +205,7 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
//Restrictions
foreach (var req in skill.Restrictions)
{
var color = req.Check(_entManager, _player.LocalEntity.Value) ? "green" : "red";
var color = req.Check(_entManager, _playerManager.LocalEntity.Value) ? "green" : "red";
sb.Append($"- [color={color}]{req.GetDescription(_entManager, _proto)}[/color]\n");
}
@@ -165,21 +215,76 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
return msg;
}
private void UpdateState(EntityUid player)
private void UpdateGraphControl()
{
if (_window is null)
return;
if (!EntityManager.TryGetComponent<CP14SkillStorageComponent>(player, out var storage))
if (_selectedSkillTree == null)
return;
_window.GraphControl.UpdateState((player, storage));
if (!EntityManager.TryGetComponent<CP14SkillStorageComponent>(_targetPlayer, out var storage))
return;
// Reselect for update state
SelectNode(_selectedSkill);
HashSet<CP14NodeTreeElement> nodeTreeElements = new();
HashSet<(string, string)> nodeTreeEdges = new();
var learned = storage.LearnedSkills;
foreach (var skill in _allSkills)
{
if (skill.Tree != _selectedSkillTree)
continue;
foreach (var req in skill.Restrictions)
{
switch (req)
{
case NeedPrerequisite prerequisite:
if (!_proto.TryIndex(prerequisite.Prerequisite, out var prerequisiteSkill))
continue;
if (prerequisiteSkill.Tree != _selectedSkillTree)
continue;
nodeTreeEdges.Add((skill.ID, prerequisiteSkill.ID));
break;
}
}
var nodeTreeElement = new CP14NodeTreeElement(
skill.ID,
gained: learned.Contains(skill),
active: _skill.CanLearnSkill(_targetPlayer.Value, skill),
skill.SkillUiPosition * 25f,
skill.Icon);
nodeTreeElements.Add(nodeTreeElement);
}
_window.GraphControl.UpdateState(
new CP14NodeTreeUiState(
nodes: nodeTreeElements,
edges: nodeTreeEdges,
frameIcon: _selectedSkillTree.FrameIcon,
hoveredIcon: _selectedSkillTree.HoveredIcon,
selectedIcon: _selectedSkillTree.SelectedIcon,
learnedIcon: _selectedSkillTree.LearnedIcon
)
);
}
private void UpdateState(EntityUid player)
{
_targetPlayer = player;
if (_window is null)
return;
if (!EntityManager.TryGetComponent<CP14SkillStorageComponent>(_targetPlayer, out var storage))
return;
//If tree not selected, select the first one
if (_window.GraphControl.Tree == null && storage.Progress.Count > 0)
if (_selectedSkillTree == null && storage.Progress.Count > 0)
{
var firstTree = storage.Progress.First().Key;
@@ -189,17 +294,24 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
}
}
if (_selectedSkillTree == null)
return;
// Reselect for update state
SelectNode(_selectedSkill);
UpdateGraphControl();
// Update the experience points for the selected tree
var playerProgress = storage.Progress;
if (_window.GraphControl.Tree is not null && playerProgress.TryGetValue(_window.GraphControl.Tree, out var skillpoint))
if (playerProgress.TryGetValue(_selectedSkillTree, out var skillPoint))
{
_window.ExpPointLabel.Text = skillpoint.ToString();
_window.ExpPointLabel.Text = skillPoint.ToString();
}
_window.LevelLabel.Text = $"{storage.SkillsSumExperience}/{storage.ExperienceMaxCap}";
_window.TreeTabsContainer.RemoveAllChildren();
foreach (var (tree, progress) in storage.Progress)
foreach (var (tree, _) in storage.Progress)
{
if (!_proto.TryIndex(tree, out var indexedTree))
continue;
@@ -220,12 +332,14 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
if (_window == null)
return;
_window.GraphControl.Tree = tree;
_selectedSkillTree = tree;
_window.ParallaxBackground.ParallaxPrototype = tree.Parallax;
_window.TreeName.Text = Loc.GetString(tree.Name);
var playerProgress = storage.Progress;
_window.ExpPointLabel.Text = playerProgress.TryGetValue(tree, out var skillpoint) ? skillpoint.ToString() : "0";
UpdateGraphControl();
}
private void CharacterDetached(EntityUid uid)

View File

@@ -5,6 +5,7 @@
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:parallax="clr-namespace:Content.Client.Parallax"
xmlns:ui1="clr-namespace:Content.Client._CP14.Skill.Ui"
xmlns:nodeTree="clr-namespace:Content.Client._CP14.UserInterface.Systems.NodeTree"
Title="{Loc 'cp14-skill-info-title'}"
MinSize="700 350"
SetSize="980 550">
@@ -38,7 +39,7 @@
<!-- Skill Cost -->
<BoxContainer HorizontalExpand="True">
<RichTextLabel HorizontalExpand="True" Access="Public" Text="{Loc 'cp14-skill-menu-learncost'}" Margin="0 10 0 10" />
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Left" TextureScale="2, 2" TexturePath="/Textures/_CP14/Interface/Skills/skillpoint.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Left" TextureScale="2, 2" TexturePath="/Textures/_CP14/Interface/Misc/skillpoint.png"/>
<RichTextLabel Name="SkillCost" Access="Public" Text="0"/>
</BoxContainer>
<!-- Skill Description -->
@@ -67,7 +68,7 @@
<PanelContainer Margin="10 10 10 10" HorizontalExpand="True" VerticalExpand="True" RectClipContent="True">
<parallax:ParallaxControl Name="ParallaxBackground" ScaleX="4" ScaleY="4" Access="Public" SpeedX="10" SpeedY="5"/>
<BoxContainer Margin="10 10 10 10" Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
<ui1:CP14SkillTreeGraphControl Name="GraphControl" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Access="Public"/>
<nodeTree:CP14NodeTreeGraphControl Name="GraphControl" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Access="Public"/>
</BoxContainer>
<!-- Tree Tabs -->
<BoxContainer Margin="10 10 10 10" VerticalAlignment="Top" HorizontalAlignment="Right" Orientation="Vertical" VerticalExpand="True">
@@ -76,11 +77,11 @@
<!-- Experience Data -->
<BoxContainer Margin="10 10 10 10" VerticalAlignment="Bottom" HorizontalAlignment="Center" Orientation="Horizontal" VerticalExpand="True">
<RichTextLabel VerticalAlignment="Center" HorizontalAlignment="Left" Text="{Loc 'cp14-skill-menu-skillpoints'}" StyleClasses="LabelKeyText"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2, 2" TexturePath="/Textures/_CP14/Interface/Skills/skillpoint.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2, 2" TexturePath="/Textures/_CP14/Interface/Misc/skillpoint.png"/>
<RichTextLabel Margin="0 0 20 0" Name="ExpPointLabel" VerticalAlignment="Center" HorizontalAlignment="Left" Text="0" StyleClasses="LabelKeyText" Access="Public"/>
<RichTextLabel VerticalAlignment="Center" HorizontalAlignment="Left" Text="{Loc 'cp14-skill-menu-level'}" StyleClasses="LabelKeyText"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2, 2" TexturePath="/Textures/_CP14/Interface/Skills/skillpoint.png"/>
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2, 2" TexturePath="/Textures/_CP14/Interface/Misc/skillpoint.png"/>
<RichTextLabel Margin="0 0 0 0" Name="LevelLabel" VerticalAlignment="Center" HorizontalAlignment="Left" Text="0" StyleClasses="LabelKeyText" Access="Public"/>
</BoxContainer>
</PanelContainer>

View File

@@ -41,7 +41,7 @@ public sealed partial class CP14DemiplaneSystem
return;
RemoveDemiplaneRandomEntryPoint((rift.Comp.Demiplane.Value, riftDemiplane), rift);
RemoveDemiplanRandomExitPoint((rift.Comp.Demiplane.Value, riftDemiplane), rift);
RemoveDemiplaneRandomExitPoint((rift.Comp.Demiplane.Value, riftDemiplane), rift);
}
/// <summary>
@@ -57,7 +57,7 @@ public sealed partial class CP14DemiplaneSystem
/// <summary>
/// Removing the demiplane exit point, one of which the player can exit to
/// </summary>
private void RemoveDemiplanRandomExitPoint(Entity<CP14DemiplaneComponent>? demiplane,
private void RemoveDemiplaneRandomExitPoint(Entity<CP14DemiplaneComponent>? demiplane,
EntityUid exitPoint)
{
if (!TryComp<CP14DemiplaneRiftComponent>(exitPoint, out var riftComp))
@@ -69,7 +69,7 @@ public sealed partial class CP14DemiplaneSystem
riftComp.Demiplane = null;
}
if (riftComp.DeleteAfterDisconnect)
if (riftComp.DeleteAfterDisconnect && exitPoint.Valid)
QueueDel(exitPoint);
}
@@ -95,7 +95,7 @@ public sealed partial class CP14DemiplaneSystem
riftComp.Demiplane = null;
}
if (riftComp.DeleteAfterDisconnect)
if (riftComp.DeleteAfterDisconnect && entryPoint.Valid)
QueueDel(entryPoint);
}

View File

@@ -1,15 +1,42 @@
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;

View File

@@ -30,13 +30,115 @@ public sealed partial class CP14DemiplaneSystem
private void InitGeneration()
{
SubscribeLocalEvent<CP14DemiplaneGeneratorDataComponent, MapInitEvent>(GeneratorMapInit);
SubscribeLocalEvent<CP14DemiplaneRandomGeneratorComponent, MapInitEvent>(GeneratorMapInit);
SubscribeLocalEvent<CP14DemiplaneUsingOpenComponent, UseInHandEvent>(GeneratorUsedInHand);
SubscribeLocalEvent<CP14DemiplaneGeneratorDataComponent, GetVerbsEvent<ExamineVerb>>(OnVerbExamine);
SubscribeLocalEvent<CP14DemiplaneDataComponent, GetVerbsEvent<ExamineVerb>>(OnVerbExamine);
}
private void OnVerbExamine(Entity<CP14DemiplaneGeneratorDataComponent> ent, ref GetVerbsEvent<ExamineVerb> args)
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 (!TryComp<CP14DemiplaneDataComponent>(ent, out var generator))
return;
UseGenerator((ent, generator), args.User);
}
//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;
}
_statistic.TrackAdd(generator.Comp.Statistic, 1);
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));
}
#if !DEBUG
QueueDel(generator); //wtf its crash debug build!
#endif
}
private void OnVerbExamine(Entity<CP14DemiplaneDataComponent> ent, ref GetVerbsEvent<ExamineVerb> args)
{
if (!args.CanInteract || !args.CanAccess)
return;
@@ -50,7 +152,7 @@ public sealed partial class CP14DemiplaneSystem
"/Textures/Interface/VerbIcons/dot.svg.192dpi.png"); //TODO custom icon
}
private FormattedMessage GetDemiplanExamine(CP14DemiplaneGeneratorDataComponent comp)
private FormattedMessage GetDemiplanExamine(CP14DemiplaneDataComponent comp)
{
var msg = new FormattedMessage();
@@ -136,114 +238,52 @@ public sealed partial class CP14DemiplaneSystem
_expeditionQueue.EnqueueJob(job);
}
private void UseGenerator(Entity<CP14DemiplaneGeneratorDataComponent> 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);
}
else
{
demiplane = ev.Demiplane;
}
_statistic.TrackAdd(generator.Comp.Statistic, 1);
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));
}
#if !DEBUG
QueueDel(generator); //wtf its crash debug build!
#endif
}
private void GeneratorUsedInHand(Entity<CP14DemiplaneUsingOpenComponent> ent, ref UseInHandEvent args)
{
if (!TryComp<CP14DemiplaneGeneratorDataComponent>(ent, out var generator))
return;
UseGenerator((ent, generator), args.User);
}
private void GeneratorMapInit(Entity<CP14DemiplaneGeneratorDataComponent> generator, ref MapInitEvent args)
/// <summary>
/// Returns a suitable demiplane location for the specified difficulty level.
/// </summary>
public CP14DemiplaneLocationPrototype GenerateDemiplaneLocation(int level)
{
CP14DemiplaneLocationPrototype? selectedConfig = null;
//Location generation
if (generator.Comp.Location is null)
HashSet<CP14DemiplaneLocationPrototype> suitableConfigs = new();
foreach (var locationConfig in _proto.EnumeratePrototypes<CP14DemiplaneLocationPrototype>())
{
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 (generator.Comp.Level < randomConfig.Levels.Min || generator.Comp.Level > randomConfig.Levels.Max)
{
suitableConfigs.Remove(randomConfig);
continue;
}
selectedConfig = randomConfig;
break;
}
if (selectedConfig is null)
{
// We dont should be here
Log.Warning("Expedition mission generation failed: No suitable location configs.");
QueueDel(generator);
return;
}
generator.Comp.Location = selectedConfig;
suitableConfigs.Add(locationConfig);
}
else
while (suitableConfigs.Count > 0)
{
if (!_proto.TryIndex(generator.Comp.Location, out selectedConfig))
return;
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>())
@@ -262,14 +302,14 @@ public sealed partial class CP14DemiplaneSystem
//Levels filter
if (passed)
{
if (generator.Comp.Level < modifier.Levels.Min || generator.Comp.Level > modifier.Levels.Max)
if (level < modifier.Levels.Min || level > modifier.Levels.Max)
{
passed = false;
}
}
//Tag blacklist filter
foreach (var configTag in selectedConfig.Tags)
foreach (var configTag in location.Tags)
{
if (modifier.BlacklistTags.Count != 0 && modifier.BlacklistTags.Contains(configTag))
{
@@ -283,7 +323,7 @@ public sealed partial class CP14DemiplaneSystem
{
foreach (var reqTag in modifier.RequiredTags)
{
if (!selectedConfig.Tags.Contains(reqTag))
if (!location.Tags.Contains(reqTag))
{
passed = false;
break;
@@ -298,11 +338,12 @@ public sealed partial class CP14DemiplaneSystem
//Limits calculation
Dictionary<ProtoId<CP14DemiplaneModifierCategoryPrototype>, float> limits = new();
foreach (var limit in generator.Comp.Limits)
foreach (var limit in modifierLimits)
{
limits.Add(limit.Key, limit.Value);
}
while (suitableModifiersWeights.Count > 0)
{
var selectedModifier = ModifierPick(suitableModifiersWeights, _random);
@@ -329,7 +370,7 @@ public sealed partial class CP14DemiplaneSystem
if (!passed)
continue;
generator.Comp.SelectedModifiers.Add(selectedModifier);
selectedModifiers.Add(selectedModifier);
foreach (var category in selectedModifier.Categories)
{
@@ -340,15 +381,9 @@ public sealed partial class CP14DemiplaneSystem
suitableModifiersWeights.Remove(selectedModifier);
}
//Scenario generation
//ETC generation
if (HasComp<CP14DemiplaneAutoOpenComponent>(generator))
UseGenerator(generator);
return selectedModifiers;
}
/// <summary>
/// Optimization moment: avoid re-indexing for weight selection
/// </summary>

View File

@@ -75,7 +75,7 @@ public sealed partial class CP14DemiplaneSystem
}
}
private void DeleteDemiplane(Entity<CP14DemiplaneComponent> demiplane, bool safe = false)
public void DeleteDemiplane(Entity<CP14DemiplaneComponent> demiplane, bool safe = false)
{
var query = EntityQueryEnumerator<CP14DemiplaneStabilizerComponent, TransformComponent>();

View File

@@ -162,7 +162,7 @@ public sealed partial class CP14DemiplaneSystem : CP14SharedDemiplaneSystem
foreach (var exit in demiplane.Comp.ExitPoints)
{
RemoveDemiplanRandomExitPoint(demiplane, exit);
RemoveDemiplaneRandomExitPoint(demiplane, exit);
}
foreach (var entry in demiplane.Comp.EntryPoints)

View File

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

View File

@@ -0,0 +1,19 @@
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,3 +1,4 @@
using Content.Server._CP14.DemiplaneTraveling;
using Content.Shared._CP14.Demiplane.Prototypes;
using Content.Shared._CP14.RoundStatistic;
using Robust.Shared.Prototypes;
@@ -5,10 +6,10 @@ using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Demiplane.Components;
/// <summary>
/// Stores the data needed to generate a new demiplane
/// Stores all the information needed to generate a new demiplane
/// </summary>
[RegisterComponent, Access(typeof(CP14DemiplaneSystem))]
public sealed partial class CP14DemiplaneGeneratorDataComponent : Component
[RegisterComponent, Access(typeof(CP14DemiplaneSystem), typeof(CP14StationDemiplaneMapSystem))]
public sealed partial class CP14DemiplaneDataComponent : Component
{
[DataField]
public ProtoId<CP14DemiplaneLocationPrototype>? Location;
@@ -16,15 +17,6 @@ public sealed partial class CP14DemiplaneGeneratorDataComponent : Component
[DataField]
public List<ProtoId<CP14DemiplaneModifierPrototype>> SelectedModifiers = new();
/// <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();
[DataField]
public ProtoId<CP14RoundStatTrackerPrototype> Statistic = "DemiplaneOpen";

View File

@@ -0,0 +1,19 @@
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

@@ -0,0 +1,23 @@
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

@@ -34,7 +34,7 @@ public sealed partial class CP14DemiplaneTravelingSystem : EntitySystem
}
// !!!SHITCODE WARNING!!!
// This whole module is saturated with shieldcode, code duplication and other delights. Why? Because.
// This whole module is saturated with shitcode, code duplication and other delights. Why? Because.
//TODO: Refactor this shitcode
public override void Update(float frameTime)
{

View File

@@ -0,0 +1,350 @@
using System.Linq;
using System.Numerics;
using Content.Server._CP14.Demiplane;
using Content.Server._CP14.Demiplane.Components;
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!;
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>(OnCoreDestroyed);
}
private void OnCoreDestroyed(Entity<CP14DemiplaneCoreComponent> ent, ref DestructionEventArgs args)
{
if (TryComp<CP14DemiplaneComponent>(ent.Comp.Demiplane, out var demiplane))
{
_demiplane.StartDestructDemiplane((ent.Comp.Demiplane.Value, demiplane));
}
var station = _station.GetOwningStation(ent, Transform(ent));
if (TryComp<CP14StationDemiplaneMapComponent>(station, out var stationMap) &&
stationMap.Nodes.TryGetValue(ent.Comp.Position, out var node) && ent.Comp.Station == station)
{
node.Scanned = 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);
QueueDel(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", node.Level * 0.2f },
{ "Reward", 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

@@ -30,6 +30,9 @@ public sealed class CP14WeatherControllerSystem : EntitySystem
var weatherData = PickWeatherDataByWeight(uid, weather.Entries);
if (weatherData is null)
continue;
if (!_proto.TryIndex(weatherData.Visuals, out var weatherVisualsIndexed))
{
var weatherDuration = TimeSpan.FromSeconds(weatherData.Duration.Next(_random));
@@ -45,7 +48,7 @@ public sealed class CP14WeatherControllerSystem : EntitySystem
}
}
private CP14WeatherData PickWeatherDataByWeight(EntityUid map, HashSet<CP14WeatherData> entries)
private CP14WeatherData? PickWeatherDataByWeight(EntityUid map, HashSet<CP14WeatherData> entries)
{
var filteredEntries = new HashSet<CP14WeatherData>(entries);
@@ -60,6 +63,9 @@ public sealed class CP14WeatherControllerSystem : EntitySystem
}
}
if (filteredEntries.Count == 0)
return null;
var totalWeight = filteredEntries.Sum(entry => entry.Weight);
var randomWeight = _random.NextFloat() * totalWeight;
var currentWeight = 0f;

View File

@@ -9,7 +9,7 @@ namespace Content.Shared._CP14.Demiplane.Components;
public sealed partial class CP14DemiplaneTimedDestructionComponent : Component
{
[DataField]
public TimeSpan TimeToDestruction = TimeSpan.FromSeconds(150f);
public TimeSpan TimeToDestruction = TimeSpan.FromSeconds(130f);
[DataField, AutoPausedField]
public TimeSpan EndTime = TimeSpan.Zero;

View File

@@ -2,6 +2,7 @@ using Content.Shared.Destructible.Thresholds;
using Content.Shared.Procedural;
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared._CP14.Demiplane.Prototypes;
@@ -39,4 +40,7 @@ public sealed partial class CP14DemiplaneLocationPrototype : IPrototype
[DataField]
public float ExamineProb = 0.75f;
[DataField]
public SpriteSpecifier? Icon = null;
}

View File

@@ -0,0 +1,33 @@
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

@@ -0,0 +1,36 @@
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 Scanned = start;
public ProtoId<CP14DemiplaneLocationPrototype>? LocationConfig = locationConfig;
public List<ProtoId<CP14DemiplaneModifierPrototype>> Modifiers = modifiers ?? new();
}

View File

@@ -0,0 +1,20 @@
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");
}

View File

@@ -0,0 +1,47 @@
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.Scanned || 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.Scanned || node1.Start || node2.Scanned || 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

@@ -0,0 +1,22 @@
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

@@ -16,19 +16,19 @@ public sealed partial class CP14SkillTreePrototype : IPrototype
public LocId Name;
[DataField]
public SpriteSpecifier FrameIcon = new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Interface/Skills/default.rsi"), "frame");
public SpriteSpecifier? FrameIcon;
[DataField]
public SpriteSpecifier HoveredIcon = new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Interface/Skills/default.rsi"), "hovered");
public SpriteSpecifier? HoveredIcon;
[DataField]
public SpriteSpecifier SelectedIcon = new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Interface/Skills/default.rsi"), "selected");
public SpriteSpecifier? SelectedIcon;
[DataField]
public SpriteSpecifier LearnedIcon = new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Interface/Skills/default.rsi"), "learned");
public SpriteSpecifier? LearnedIcon;
[DataField]
public SpriteSpecifier AvailableIcon = new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Interface/Skills/default.rsi"), "available");
public SpriteSpecifier? AvailableIcon;
[DataField]
public string Parallax = "AspidParallax";

View File

@@ -11,4 +11,7 @@ cp14-round-end = The demiplane link crystal was completely discharged. The conne
cp14-demiplane-echoes = Echoes of voices
cp14-demiplane-countdown = The Demiplane is beginning to collapse! You have {$duration} minutes to escape!
cp14-demiplane-countdown = The Demiplane is beginning to collapse! You have {$duration} minutes to escape!
cp14-demiplane-revoke-item = The bound key has been destroyed!
cp14-demiplane-revoke-map = The process of destroying the bound demiplane has been initiated!

View File

@@ -1,4 +1,4 @@
cp14-demiplane-location-cave = Dark caves
cp14-demiplane-location-cave = Caves
cp14-demiplane-location-cave-grass = Overgrown caves
cp14-demiplane-location-cave-magma = Flaming caves
cp14-demiplane-location-grassland-island = Green Island

View File

@@ -0,0 +1,15 @@
cp14-demiplane-map-title = Demiplane navigation map
cp14-demiplane-map-eject = Extract coordinates
cp14-demiplane-map-eject-tooltip = Extract the coordinates of the demiplane into physical form, allowing you to open a passageway into it at any time.
cp14-demiplane-map-revoke = Cut the link
cp14-demiplane-map-revoke-tooltip = You cut the connection to the selected demiplane. This either instantly removes the demiplane key or starts a 2-minute process to destroy the demiplane.
cp14-demiplane-map-add-level = [color=red]Difficulty +{$count}[/color]
cp14-demiplane-map-add-level-tooltip = This demiplane has been opened several times already, and this point in space is becoming more difficult and unstable.
cp14-demiplane-map-status-blocked = [color=red]This demiplane cannot be opened: You must explore any nearby demiplane to unlock the coordinates.[/color]
cp14-demiplane-map-status-allowed = [color=green]The demiplane is available for exploration.[/color]
cp14-demiplane-map-status-used = [color=yellow]The coordinates of this demiplane are already in use. It is not possible to create a copy.[/color]
cp14-demiplane-map-status-scanned = [color=purple]The demiplane core has been destroyed and successfully scanned. Demiplane reopening is impossible, coordinates of neighboring demiplanes are available for research.[/color]

View File

@@ -10,11 +10,10 @@ cp14-modifier-wild-sage = wild Sage
cp14-modifier-lumisroom = lumishrooms
cp14-modifier-explosive = explosive mines
cp14-modifier-ruins = ancient ruins
cp14-modifier-xeno = xenomorphs
cp14-modifier-zombie = swarms of undead
cp14-modifier-zombie = zombies
cp14-modifier-slime = slimes
cp14-modifier-skeleton = sentient skeletons
cp14-modifier-dyno = prehistoric fauna
cp14-modifier-dyno = dynos
cp14-modifier-mole = predatory moles
cp14-modifier-watcher = watchers
cp14-modifier-rabbits = rabbits
@@ -23,7 +22,6 @@ cp14-modifier-invisible-whistler = invisible whistlers
cp14-modifier-sheeps = sheeps
cp14-modifier-chasm = bottomless chasms
cp14-modifier-air-lily = air lilies
cp14-modifier-time-limit-10 = temporary disintegration (10 minutes)
cp14-modifier-shadow-kudzu = spreading astral haze
cp14-modifier-night = darkness

View File

@@ -11,4 +11,7 @@ cp14-round-end = Кристалл связи с демипланами окон
cp14-demiplane-echoes = Эхо голосов
cp14-demiplane-countdown = Демиплан начинает коллапсировать! У вас {$duration} минут, чтобы покинуть его!
cp14-demiplane-countdown = Демиплан начинает коллапсировать! У вас {$duration} минут, чтобы покинуть его!
cp14-demiplane-revoke-item = Связанный ключ уничтожен!
cp14-demiplane-revoke-map = Запущен процесс уничтожения связанного демиплана!

View File

@@ -1,6 +1,6 @@
cp14-demiplane-location-cave = Темные пещеры
cp14-demiplane-location-cave = Пещеры
cp14-demiplane-location-cave-grass = Заросшие пещеры
cp14-demiplane-location-cave-magma = Раскаленные пещеры
cp14-demiplane-location-cave-magma = Раскаленные недра
cp14-demiplane-location-grassland-island = Зеленый остров
cp14-demiplane-location-ice-cave = Ледяные пещеры
cp14-demiplane-location-snow-island = Заснеженный остров

View File

@@ -0,0 +1,15 @@
cp14-demiplane-map-title = Карта навигации демипланов
cp14-demiplane-map-eject = Извлечь координаты
cp14-demiplane-map-eject-tooltip = Извлечь координаты демиплана в физическую форму, что позволит открыть в него проход в любое время.
cp14-demiplane-map-revoke = Оборвать связь
cp14-demiplane-map-revoke-tooltip = Вы обрываете связь с выбранным демипланом. Это либо мгновенно удаляет ключ демиплана, либо запускает 2-минутный процесс уничтожения демиплана.
cp14-demiplane-map-add-level = [color=red]Сложность +{$count}[/color]
cp14-demiplane-map-add-level-tooltip = Этот демиплан открывался уже несколько раз, и данная точка пространства становится сложнее и нестабильнее.
cp14-demiplane-map-status-blocked = [color=red]Открыть этот демиплан невозможно: Необходимо исследовать любой ближайший демиплан, для разблокировки координат.[/color]
cp14-demiplane-map-status-allowed = [color=green]Демиплан доступен для изучения.[/color]
cp14-demiplane-map-status-used = [color=yellow]Координаты этого демиплана уже используются. Невозможно создать копию.[/color]
cp14-demiplane-map-status-scanned = [color=purple]Ядро демиплана уничтожено и успешно просканировано. Повторное открытие демиплана невозможно, координаты соседних демипланов доступны для исследования.[/color]

View File

@@ -1,34 +1,32 @@
cp14-modifier-gold-ore = золотой руды
cp14-modifier-iron-ore = железной руды
cp14-modifier-copper-ore = медной руды
cp14-modifier-mithril-ore = мифриловой руды
cp14-modifier-dayflin = днецветов
cp14-modifier-fly-agaric = мухоморов
cp14-modifier-blue-amanita = лазурной аманиты
cp14-modifier-blood-flower = кровоцветов
cp14-modifier-wild-sage = дикого Шалфея
cp14-modifier-lumisroom = люмигрибов
cp14-modifier-explosive = взрывных мин
cp14-modifier-ruins = древних руин
cp14-modifier-xeno = ксеноморфов
cp14-modifier-zombie = толп нежити
cp14-modifier-slime = слаймов
cp14-modifier-gold-ore = золотая руда
cp14-modifier-iron-ore = железная руда
cp14-modifier-copper-ore = медная руда
cp14-modifier-mithril-ore = мифриловая руда
cp14-modifier-dayflin = днецветы
cp14-modifier-fly-agaric = мухоморы
cp14-modifier-blue-amanita = лазурная аманита
cp14-modifier-blood-flower = кровоцветы
cp14-modifier-wild-sage = дикий шалфей
cp14-modifier-lumisroom = люмигрибы
cp14-modifier-explosive = взрывные мины
cp14-modifier-ruins = древние руины
cp14-modifier-zombie = зомби
cp14-modifier-slime = слаймы
cp14-modifier-skeleton = разумные скелеты
cp14-modifier-dyno = доисторической фауны
cp14-modifier-mole = хищных кротов
cp14-modifier-watcher = наблюдателей
cp14-modifier-rabbits = кроликов
cp14-modifier-boars = диких кабанов
cp14-modifier-sheeps = овец
cp14-modifier-invisible-whistler = невидимых свистунов
cp14-modifier-chasm = бездонных пропастей
cp14-modifier-air-lily = воздушных лилий
cp14-modifier-time-limit-10 = временного распада (10 минут)
cp14-modifier-shadow-kudzu = распространяющгося астрального мрака
cp14-modifier-night = темноты
cp14-modifier-dyno = динозавры
cp14-modifier-mole = хищные кроты
cp14-modifier-watcher = наблюдатели
cp14-modifier-rabbits = кролики
cp14-modifier-boars = дикие кабаны
cp14-modifier-sheeps = овцы
cp14-modifier-invisible-whistler = невидимые свистуны
cp14-modifier-chasm = бездонные пропасти
cp14-modifier-air-lily = воздушные лилии
cp14-modifier-shadow-kudzu = поглощающий астральный мрак
cp14-modifier-night = темнота
cp14-modifier-storm = грозы
cp14-modifier-storm = гроза
cp14-modifier-fire-storm = огненный шторм
cp14-modifier-snow-storm = снежный шторм
cp14-modifier-mana-mist = магического тумана
cp14-modifier-anti-mana-mist = антимагического тумана
cp14-modifier-mana-mist = магический туман
cp14-modifier-anti-mana-mist = антимагический туман

View File

@@ -0,0 +1,648 @@
meta:
format: 7
category: Map
engineVersion: 255.0.0
forkId: ""
forkVersion: ""
time: 05/03/2025 21:59:43
entityCount: 104
maps:
- 1
grids:
- 1
orphans: []
nullspace: []
tilemap:
0: Space
20: CP14FloorBase
9: CP14FloorFoundation
13: CP14FloorGrass
14: CP14FloorGrassLight
15: CP14FloorGrassTall
10: CP14FloorOakWoodPlanksBig
12: CP14FloorOakWoodPlanksBroken
11: CP14FloorOakWoodPlanksCruciform
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: PhysicsMap
- type: GridTree
- type: MovedGrids
- type: Broadphase
- type: OccluderTree
- type: MapGrid
chunks:
-1,-1:
ind: -1,-1
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAA
version: 6
0,0:
ind: 0,0
tiles: FAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAA
version: 6
0,1:
ind: 0,1
tiles: AQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAA
version: 6
0,-1:
ind: 0,-1
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAA
version: 6
-1,0:
ind: -1,0
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAA
version: 6
-1,1:
ind: -1,1
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAA
version: 6
1,-1:
ind: 1,-1
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAA
version: 6
1,0:
ind: 1,0
tiles: AQAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAFAAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAFAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAA
version: 6
1,1:
ind: 1,1
tiles: AQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAA
version: 6
-1,2:
ind: -1,2
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
version: 6
0,2:
ind: 0,2
tiles: AQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
version: 6
1,2:
ind: 1,2
tiles: AQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
version: 6
2,-1:
ind: 2,-1
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
version: 6
2,0:
ind: 2,0
tiles: AQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
version: 6
2,1:
ind: 2,1
tiles: AQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
version: 6
2,2:
ind: 2,2
tiles: AQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
version: 6
- type: Gravity
gravityShakeSound: !type:SoundPathSpecifier
path: /Audio/Effects/alert.ogg
- type: DecalGrid
chunkCollection:
version: 2
nodes: []
- type: SpreaderGrid
- type: GridPathfinding
- type: RadiationGridResistance
- proto: CP14DemiplaneCore
entities:
- uid: 14
components:
- type: Transform
pos: 11.5,2.5
parent: 1
- uid: 24
components:
- type: Transform
pos: 19.5,3.5
parent: 1
- uid: 25
components:
- type: Transform
pos: 27.5,3.5
parent: 1
- uid: 71
components:
- type: Transform
pos: 3.5,3.5
parent: 1
- proto: CP14WallDimensit
entities:
- uid: 2
components:
- type: Transform
pos: 20.5,0.5
parent: 1
- uid: 3
components:
- type: Transform
pos: 10.5,1.5
parent: 1
- uid: 4
components:
- type: Transform
pos: 14.5,0.5
parent: 1
- uid: 5
components:
- type: Transform
pos: 4.5,1.5
parent: 1
- uid: 6
components:
- type: Transform
pos: 4.5,5.5
parent: 1
- uid: 7
components:
- type: Transform
pos: 1.5,0.5
parent: 1
- uid: 8
components:
- type: Transform
pos: 3.5,4.5
parent: 1
- uid: 9
components:
- type: Transform
pos: 4.5,4.5
parent: 1
- uid: 10
components:
- type: Transform
pos: 4.5,2.5
parent: 1
- uid: 11
components:
- type: Transform
pos: 28.5,1.5
parent: 1
- uid: 12
components:
- type: Transform
pos: 20.5,5.5
parent: 1
- uid: 13
components:
- type: Transform
pos: 20.5,4.5
parent: 1
- uid: 15
components:
- type: Transform
pos: 20.5,1.5
parent: 1
- uid: 16
components:
- type: Transform
pos: 20.5,2.5
parent: 1
- uid: 17
components:
- type: Transform
pos: 19.5,4.5
parent: 1
- uid: 18
components:
- type: Transform
pos: 19.5,5.5
parent: 1
- uid: 19
components:
- type: Transform
pos: 20.5,3.5
parent: 1
- uid: 20
components:
- type: Transform
pos: 28.5,2.5
parent: 1
- uid: 21
components:
- type: Transform
pos: 29.5,2.5
parent: 1
- uid: 22
components:
- type: Transform
pos: 22.5,1.5
parent: 1
- uid: 23
components:
- type: Transform
pos: 24.5,5.5
parent: 1
- uid: 26
components:
- type: Transform
pos: 22.5,2.5
parent: 1
- uid: 27
components:
- type: Transform
pos: 27.5,0.5
parent: 1
- uid: 28
components:
- type: Transform
pos: 29.5,5.5
parent: 1
- uid: 29
components:
- type: Transform
pos: 27.5,1.5
parent: 1
- uid: 30
components:
- type: Transform
pos: 21.5,1.5
parent: 1
- uid: 31
components:
- type: Transform
pos: 21.5,0.5
parent: 1
- uid: 32
components:
- type: Transform
pos: 21.5,2.5
parent: 1
- uid: 33
components:
- type: Transform
pos: 21.5,3.5
parent: 1
- uid: 34
components:
- type: Transform
pos: 21.5,4.5
parent: 1
- uid: 35
components:
- type: Transform
pos: 25.5,4.5
parent: 1
- uid: 36
components:
- type: Transform
pos: 18.5,0.5
parent: 1
- uid: 37
components:
- type: Transform
pos: 18.5,1.5
parent: 1
- uid: 38
components:
- type: Transform
pos: 18.5,2.5
parent: 1
- uid: 39
components:
- type: Transform
pos: 16.5,4.5
parent: 1
- uid: 40
components:
- type: Transform
pos: 25.5,3.5
parent: 1
- uid: 41
components:
- type: Transform
pos: 9.5,5.5
parent: 1
- uid: 42
components:
- type: Transform
pos: 24.5,4.5
parent: 1
- uid: 43
components:
- type: Transform
pos: 27.5,5.5
parent: 1
- uid: 44
components:
- type: Transform
pos: 17.5,2.5
parent: 1
- uid: 45
components:
- type: Transform
pos: 13.5,6.5
parent: 1
- uid: 46
components:
- type: Transform
pos: 17.5,5.5
parent: 1
- uid: 47
components:
- type: Transform
pos: 17.5,1.5
parent: 1
- uid: 48
components:
- type: Transform
pos: 17.5,3.5
parent: 1
- uid: 49
components:
- type: Transform
pos: 13.5,5.5
parent: 1
- uid: 50
components:
- type: Transform
pos: 9.5,4.5
parent: 1
- uid: 51
components:
- type: Transform
pos: 19.5,2.5
parent: 1
- uid: 52
components:
- type: Transform
pos: 18.5,5.5
parent: 1
- uid: 53
components:
- type: Transform
pos: 18.5,3.5
parent: 1
- uid: 54
components:
- type: Transform
pos: 19.5,0.5
parent: 1
- uid: 55
components:
- type: Transform
pos: 19.5,1.5
parent: 1
- uid: 56
components:
- type: Transform
pos: 18.5,4.5
parent: 1
- uid: 57
components:
- type: Transform
pos: 8.5,5.5
parent: 1
- uid: 58
components:
- type: Transform
pos: 8.5,6.5
parent: 1
- uid: 59
components:
- type: Transform
pos: 16.5,2.5
parent: 1
- uid: 60
components:
- type: Transform
pos: 16.5,3.5
parent: 1
- uid: 61
components:
- type: Transform
pos: 14.5,5.5
parent: 1
- uid: 62
components:
- type: Transform
pos: 12.5,3.5
parent: 1
- uid: 63
components:
- type: Transform
pos: 12.5,1.5
parent: 1
- uid: 64
components:
- type: Transform
pos: 10.5,4.5
parent: 1
- uid: 65
components:
- type: Transform
pos: 8.5,2.5
parent: 1
- uid: 66
components:
- type: Transform
pos: 11.5,5.5
parent: 1
- uid: 67
components:
- type: Transform
pos: 25.5,1.5
parent: 1
- uid: 68
components:
- type: Transform
pos: 12.5,2.5
parent: 1
- uid: 69
components:
- type: Transform
pos: 10.5,3.5
parent: 1
- uid: 70
components:
- type: Transform
pos: 8.5,1.5
parent: 1
- uid: 72
components:
- type: Transform
pos: 1.5,2.5
parent: 1
- uid: 73
components:
- type: Transform
pos: 5.5,2.5
parent: 1
- uid: 74
components:
- type: Transform
pos: 11.5,0.5
parent: 1
- uid: 75
components:
- type: Transform
pos: 10.5,5.5
parent: 1
- uid: 76
components:
- type: Transform
pos: 2.5,1.5
parent: 1
- uid: 77
components:
- type: Transform
pos: 1.5,3.5
parent: 1
- uid: 78
components:
- type: Transform
pos: 0.5,0.5
parent: 1
- uid: 79
components:
- type: Transform
pos: 6.5,6.5
parent: 1
- uid: 80
components:
- type: Transform
pos: 6.5,4.5
parent: 1
- uid: 81
components:
- type: Transform
pos: 11.5,1.5
parent: 1
- uid: 82
components:
- type: Transform
pos: 11.5,3.5
parent: 1
- uid: 83
components:
- type: Transform
pos: 0.5,1.5
parent: 1
- uid: 84
components:
- type: Transform
pos: 6.5,5.5
parent: 1
- uid: 85
components:
- type: Transform
pos: 5.5,3.5
parent: 1
- uid: 86
components:
- type: Transform
pos: 12.5,4.5
parent: 1
- uid: 87
components:
- type: Transform
pos: 14.5,4.5
parent: 1
- uid: 88
components:
- type: Transform
pos: 5.5,4.5
parent: 1
- uid: 89
components:
- type: Transform
pos: 5.5,5.5
parent: 1
- uid: 90
components:
- type: Transform
pos: 4.5,3.5
parent: 1
- uid: 91
components:
- type: Transform
pos: 1.5,1.5
parent: 1
- uid: 92
components:
- type: Transform
pos: 2.5,0.5
parent: 1
- uid: 93
components:
- type: Transform
pos: 3.5,2.5
parent: 1
- uid: 94
components:
- type: Transform
pos: 2.5,4.5
parent: 1
- uid: 95
components:
- type: Transform
pos: 2.5,2.5
parent: 1
- uid: 96
components:
- type: Transform
pos: 2.5,3.5
parent: 1
- uid: 97
components:
- type: Transform
pos: 3.5,1.5
parent: 1
- uid: 98
components:
- type: Transform
pos: 18.5,6.5
parent: 1
- uid: 99
components:
- type: Transform
pos: 21.5,5.5
parent: 1
- uid: 100
components:
- type: Transform
pos: 22.5,3.5
parent: 1
- uid: 101
components:
- type: Transform
pos: 9.5,6.5
parent: 1
- uid: 102
components:
- type: Transform
pos: 17.5,4.5
parent: 1
- uid: 103
components:
- type: Transform
pos: 10.5,2.5
parent: 1
- uid: 104
components:
- type: Transform
pos: 11.5,4.5
parent: 1
...

View File

@@ -116,27 +116,6 @@
service: !type:CP14BuyItemsService
product: CP14GoldBar10
- type: storePositionBuy
id: Demiplane
code: DEMIPLANE_1
price: 100
factions:
- SpiceStream
service: !type:CP14BuyItemsService
product: CP14DemiplaneKeyT1
count: 5
- type: storePositionBuy
id: Demiplane2
code: DEMIPLANE_2
price: 200
factions:
- SpiceStream
service: !type:CP14BuyItemsService
product: CP14DemiplaneKeyT2
count: 5
- type: storePositionBuy
id: Bureaucracy
code: PAPER

View File

@@ -71,6 +71,14 @@
- state: electrified
color: "#601fc2"
shader: unshaded
- type: PointLight
color: "#601fc2"
enabled: true
radius: 2
energy: 2
netsync: false
- type: LightFade
duration: 1
- type: entity
parent: CP14BaseSpellScrollMeta

View File

@@ -56,4 +56,42 @@
farSound:
collection: CP14LightningFar
params:
variation: 0.2
variation: 0.2
- type: entity
id: CP14SkyLightningPurple
parent: CP14SkyLightning
suffix: Purple
components:
- type: PointLight
color: "#8f42ff"
- type: Sprite
color: "#8f42ff"
- type: CP14AreaEntityEffect
range: 1
effects:
- !type:CP14SpellApplyEntityEffect
effects:
- !type:Electrocute
electrocuteTime: 3
- !type:HealthChange
damage:
types:
Shock: 15
- !type:FlammableReaction
multiplier: 1.5
- !type:AdjustTemperature
amount: 15000
- !type:Ignite
- type: CP14FarSound
closeSound:
path: /Audio/_CP14/Ambience/Lightning/lightning_close1.ogg
params:
variation: 0.2
maxDistance: 20
volume: 10
farSound:
collection: CP14LightningFar
params:
variation: 0.2
volume: -5

View File

@@ -1,7 +1,6 @@
- type: entity
parent: BaseItem
id: CP14BaseSubdimensionalKey
abstract: true
id: CP14BaseDemiplaneKey
categories: [ ForkFiltered ]
name: demiplane key
description: The core that connects the real world to the demiplane. Use it to open a temporary passage to the other world.
@@ -17,43 +16,43 @@
- CP14_RU_Demiplanes
- CP14_EN_Demiplanes
- type: CP14DemiplaneUsingOpen
- type: entity
id: CP14DemiplaneKeyT1
parent: CP14BaseSubdimensionalKey
suffix: Level 3
components:
- type: CP14DemiplaneGeneratorData
level: 3
limits:
Reward: 1
Danger: 1
GhostRoleDanger: 1
Fun: 1
Weather: 1
MapLight: 1
- type: CP14DemiplaneData
selectedModifiers:
- DemiplaneDecor
- Core
- EntryRoom
- Exit
- type: entity
id: CP14DemiplaneKeyT2
parent: CP14BaseSubdimensionalKey
suffix: Level 6
components:
- type: Sprite
layers:
- state: core
color: red
- type: CP14DemiplaneGeneratorData
level: 6
limits:
Reward: 1
Danger: 1.5
GhostRoleDanger: 1
Fun: 1
Weather: 1
MapLight: 1
selectedModifiers:
- EntryRoom
- Exit
#- type: entity
# id: CP14DemiplaneKeyT1
# parent: CP14BaseDemiplaneKey
# suffix: Level 3
# components:
# - type: CP14DemiplaneRandomGenerator
# level: 3
# limits:
# Reward: 1
# Danger: 1
# GhostRoleDanger: 1
# Fun: 1
# Weather: 1
# MapLight: 1
#
#- type: entity
# id: CP14DemiplaneKeyT2
# parent: CP14BaseDemiplaneKey
# suffix: Level 6
# components:
# - type: Sprite
# layers:
# - state: core
# color: red
# - type: CP14DemiplaneRandomGenerator
# level: 6
# limits:
# Reward: 1
# Danger: 1.5
# GhostRoleDanger: 1
# Fun: 1
# Weather: 1
# MapLight: 1

View File

@@ -9,6 +9,7 @@
- BaseStationRecords # Required for lobby manifest + cryo leave
- CP14BaseStationCommonObjectives
- CP14BaseStationSalary
- CP14BaseStationDemiplaneMap
- type: entity
id: CP14BaseStationCommonObjectives
@@ -23,4 +24,10 @@
- type: CP14StationSalary
#frequency: every 20 minutes
salary:
CP14Guard: 550
CP14Guard: 550
- type: entity
id: CP14BaseStationDemiplaneMap
abstract: true
components:
- type: CP14StationDemiplaneMap

View File

@@ -52,6 +52,14 @@
range: 10
sound:
path: /Audio/_CP14/Effects/demiplane_heartbeat.ogg
- type: ActivatableUI
key: enum.CP14DemiplaneMapUiKey.Key
requiresComplex: true
- type: CP14DemiplaneNavigationMap
- type: UserInterface
interfaces:
enum.CP14DemiplaneMapUiKey.Key:
type: CP14DemiplaneMapBoundUserInterface
- type: entity
id: CP14PortalFrameCrystal
@@ -92,4 +100,86 @@
maxEnergy: 100
energy: 100
- type: CP14MagicEnergyExaminable
- type: CP14MagicEnergyPortRelay
- type: CP14MagicEnergyPortRelay
- type: entity
id: CP14DemiplaneCore
categories: [ ForkFiltered ]
parent: BaseStructureDynamic
name: demiplane core
description: The heart of the demiplane. Your task is to take it out of the demiplane safe and sound and hand it over to the guildmaster.
components:
- type: CP14DemiplaneCore
- type: Transform
anchored: false
- type: Pullable
- 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
minValue: 1.0
maxValue: 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: 350
behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- trigger:
!type:DamageTrigger
damage: 50
behaviors:
- !type:PlaySoundBehavior
sound:
collection: GlassBreak
- !type:DoActsBehavior
acts: ["Destruction"]

View File

@@ -261,4 +261,46 @@
min: 2
max: 3
- !type:DoActsBehavior
acts: ["Destruction"]
acts: ["Destruction"]
- type: entity
id: CP14WallDimensit
parent: CP14BaseWall
name: dimensit wall
description: The solid form of the interdimensional continuum.
components:
- type: Sprite
sprite: _CP14/Structures/Walls/Natural/cave_dimensit.rsi
- type: Icon
sprite: _CP14/Structures/Walls/Natural/cave_dimensit.rsi
- type: IconSmooth
base: wall
- type: Damageable
damageContainer: Inorganic
damageModifierSet: CP14Rock
- type: MeleeSound
soundGroups:
Brute:
collection: GlassSmash
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 350
behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- trigger:
!type:DamageTrigger
damage: 30
behaviors:
- !type:PlaySoundBehavior
sound:
collection: GlassBreak
- !type:DoActsBehavior
acts: ["Destruction"]
#- !type:SpawnEntitiesBehavior
# spawn:
# CP14StoneBlock1:
# min: 5
# max: 7

View File

@@ -0,0 +1,18 @@
- type: parallax
id: CP14Astral
layers:
- texture:
!type:ImageParallaxTextureSource
path: "/Textures/Parallaxes/KettleParallaxBG.png"
slowness: 0.998046875
scale: "1, 1"
- texture:
!type:GeneratedParallaxTextureSource
id: "hq_wizard_stars"
configPath: "/Prototypes/_CP14/Parallax/stars_purple.toml"
slowness: 0.696625
- texture:
!type:GeneratedParallaxTextureSource
id: "hq_wizard_stars"
configPath: "/Prototypes/_CP14/Parallax/stars_purple_2.toml"
slowness: 0.896625

View File

@@ -0,0 +1,56 @@
# Clear to black.
[[layers]]
type = "clear"
color = "#000000"
# Bright background nebula stars.
[[layers]]
type = "points"
closecolor = "#aa47ad"
count = 1000
seed = 3472
mask = true
masknoise_type = "fbm"
maskoctaves = 4
maskpersistence = "0.5"
maskpower = "0.35"
masklacunarity = "1.5"
maskfrequency = "3"
maskthreshold = "0.37"
maskseed = 3551
# Bright background nebula stars, dim edge.
[[layers]]
type = "points"
closecolor = "#41244a"
pointsize = 2
count = 1000
seed = 3472
mask = true
masknoise_type = "fbm"
maskoctaves = 4
maskpersistence = "0.5"
maskpower = "0.35"
masklacunarity = "1.5"
maskfrequency = "3"
maskthreshold = "0.37"
maskseed = 3551
# Couple of bright pink stars.
[[layers]]
type = "points"
closecolor = "#ff63ef"
count = 30
seed = 6454
# And their dim edge.
[[layers]]
type = "points"
closecolor = "#301a43"
pointsize = 2
count = 30
seed = 6454
# Colour-to-alpha.
[[layers]]
type = "toalpha"

View File

@@ -0,0 +1,56 @@
# Clear to black.
[[layers]]
type = "clear"
color = "#000000"
# Bright background nebula stars.
[[layers]]
type = "points"
closecolor = "#aa47ad"
count = 1000
seed = 4810
mask = true
masknoise_type = "fbm"
maskoctaves = 4
maskpersistence = "0.5"
maskpower = "0.35"
masklacunarity = "1.5"
maskfrequency = "3"
maskthreshold = "0.37"
maskseed = 3551
# Bright background nebula stars, dim edge.
[[layers]]
type = "points"
closecolor = "#41244a"
pointsize = 2
count = 1000
seed = 4810
mask = true
masknoise_type = "fbm"
maskoctaves = 4
maskpersistence = "0.5"
maskpower = "0.35"
masklacunarity = "1.5"
maskfrequency = "3"
maskthreshold = "0.37"
maskseed = 3551
# Couple of bright pink stars.
[[layers]]
type = "points"
closecolor = "#ff63ef"
count = 30
seed = 9019
# And their dim edge.
[[layers]]
type = "points"
closecolor = "#301a43"
pointsize = 2
count = 30
seed = 9019
# Colour-to-alpha.
[[layers]]
type = "toalpha"

View File

@@ -2,7 +2,10 @@
id: T1Caves
levels:
min: 1
max: 4
max: 5
icon:
sprite: _CP14/Interface/Misc/demiplane_locations.rsi
state: caves
locationConfig: CP14DemiplaneCaves
name: cp14-demiplane-location-cave
tags:

View File

@@ -1,8 +1,11 @@
- type: cp14DemiplaneLocation
id: T1IceCaves
levels:
min: 1
max: 10
min: 4
max: 7
icon:
sprite: _CP14/Interface/Misc/demiplane_locations.rsi
state: ice_caves
locationConfig: CP14DemiplaneIceCaves
name: cp14-demiplane-location-ice-cave
tags:

View File

@@ -1,8 +1,11 @@
- type: cp14DemiplaneLocation
id: T1MagmaCaves
levels:
min: 3
min: 7
max: 10
icon:
sprite: _CP14/Interface/Misc/demiplane_locations.rsi
state: magma_caves
locationConfig: CP14DemiplaneCavesRing
name: cp14-demiplane-location-cave-magma
tags:

View File

@@ -1,8 +1,11 @@
- type: cp14DemiplaneLocation
id: T1SwampGeode
id: T1SwampCaves
levels:
min: 4
max: 10
min: 2
max: 5
icon:
sprite: _CP14/Interface/Misc/demiplane_locations.rsi
state: swamp_caves
locationConfig: CP14DemiplaneSwampGeode
name: cp14-demiplane-location-cave-grass
tags:

View File

@@ -2,7 +2,10 @@
id: T1GrasslandIsland
levels:
min: 1
max: 4
max: 2
icon:
sprite: _CP14/Interface/Misc/demiplane_locations.rsi
state: grassland_island
locationConfig: CP14DemiplaneGrasslandIsland
name: cp14-demiplane-location-grassland-island
tags:

View File

@@ -2,7 +2,10 @@
id: T1GrasslandIslandRing
levels:
min: 1
max: 4
max: 2
icon:
sprite: _CP14/Interface/Misc/demiplane_locations.rsi
state: grassland_island
locationConfig: CP14DemiplaneGrasslandIslandRing
name: cp14-demiplane-location-grassland-island
tags:

View File

@@ -1,8 +1,11 @@
- type: cp14DemiplaneLocation
id: T1SnowIsland
levels:
min: 1
max: 10
min: 3
max: 5
icon:
sprite: _CP14/Interface/Misc/demiplane_locations.rsi
state: snow_island
locationConfig: CP14DemiplaneSnowIsland
name: cp14-demiplane-location-snow-island
tags:

View File

@@ -1,8 +1,11 @@
- type: cp14DemiplaneLocation
id: T1LeafMaze
levels:
min: 4
max: 10
min: 3
max: 5
icon:
sprite: _CP14/Interface/Misc/demiplane_locations.rsi
state: leaf_maze
locationConfig: CP14DemiplaneLeafMaze
name: cp14-demiplane-location-leaf-maze
tags:

View File

@@ -1,8 +1,11 @@
- type: cp14DemiplaneLocation
id: T1Wastelands
levels:
min: 1
max: 4
min: 5
max: 7
icon:
sprite: _CP14/Interface/Misc/demiplane_locations.rsi
state: wastelands
locationConfig: CP14DemiplaneWastelandsIsland
name: cp14-demiplane-location-wastelands
tags:

View File

@@ -3,7 +3,7 @@
- type: cp14DemiplaneModifier
id: Chasm
levels:
min: 4
min: 3
max: 10
name: cp14-modifier-chasm
generationWeight: 0.6
@@ -60,7 +60,7 @@
maxGroupSize: 2
- type: cp14DemiplaneModifier
id: ShadowKudzuDebug
id: ShadowKudzu
levels:
min: 7
max: 10

View File

@@ -4,7 +4,7 @@
id: EnemyZombie
levels:
min: 1
max: 4
max: 6
name: cp14-modifier-zombie
generationWeight: 1.5
categories:
@@ -40,7 +40,7 @@
id: MonsterMosquito
levels:
min: 1
max: 4
max: 5
name: cp14-modifier-dyno
categories:
Danger: 0.2
@@ -59,7 +59,7 @@
id: SmallHydra
levels:
min: 1
max: 4
max: 5
name: cp14-modifier-dyno
categories:
Danger: 0.3
@@ -92,7 +92,7 @@
- type: cp14DemiplaneModifier
id: EnemyLurker
levels:
min: 3
min: 1
max: 10
generationWeight: 0.33
generationProb: 0.25
@@ -163,22 +163,22 @@
- type: cp14DemiplaneModifier
id: MobSlimeElectric
levels:
min: 1
min: 5
max: 10
name: cp14-modifier-slime
categories:
Danger: 0.3
Danger: 0.35
layers:
- !type:OreDunGen
entity: CP14MobSlimeElectric
count: 6
minGroupSize: 2
maxGroupSize: 3
minGroupSize: 1
maxGroupSize: 1
- type: cp14DemiplaneModifier
id: MobSlimeFire
levels:
min: 1
min: 4
max: 10
name: cp14-modifier-slime
categories:
@@ -189,8 +189,8 @@
- !type:OreDunGen
entity: CP14MobSlimeFire
count: 6
minGroupSize: 2
maxGroupSize: 3
minGroupSize: 1
maxGroupSize: 2
- type: cp14DemiplaneModifier
id: MobSlimeIce
@@ -206,8 +206,8 @@
- !type:OreDunGen
entity: CP14MobSlimeIce
count: 6
minGroupSize: 2
maxGroupSize: 3
minGroupSize: 1
maxGroupSize: 2
- type: cp14DemiplaneModifier
id: MobSlimeBase
@@ -220,13 +220,13 @@
- !type:OreDunGen
entity: CP14MobSlimeBase
count: 6
minGroupSize: 2
maxGroupSize: 3
minGroupSize: 1
maxGroupSize: 2
- type: cp14DemiplaneModifier
id: MobWatcherIce
levels:
min: 1
min: 3
max: 10
name: cp14-modifier-watcher
categories:
@@ -245,7 +245,7 @@
- type: cp14DemiplaneModifier
id: MobWatcherMagma
levels:
min: 1
min: 3
max: 10
name: cp14-modifier-watcher
categories:
@@ -280,20 +280,4 @@
entity: CP14SpawnPointGhostDemiplaneSkeletonMagicalT2
count: 1
minGroupSize: 0
maxGroupSize: 1
- type: cp14DemiplaneModifier
id: EnemySkeletonHidenT1
levels:
min: 6
max: 10
name: cp14-modifier-skeleton
generationWeight: 1.0
categories:
Danger: 0.25
layers:
- !type:OreDunGen
entity: CP14SpawnPointGhostDemiplaneSkeletonT1
count: 1
minGroupSize: 1
maxGroupSize: 2
maxGroupSize: 1

View File

@@ -4,7 +4,7 @@
id: LootT1
levels:
min: 1
max: 4
max: 5
generationWeight: 2
categories:
Reward: 0.35
@@ -23,7 +23,7 @@
name: cp14-modifier-rabbits
generationWeight: 0.4
categories:
Reward: 0.2
Reward: 0.1
requiredTags:
- CP14DemiplanePeacefulAnimals
layers:
@@ -58,7 +58,7 @@
max: 10
generationWeight: 0.4
categories:
Reward: 0.2
Reward: 0.05
requiredTags:
- CP14DemiplaneAnimalsSwamp
layers:
@@ -90,6 +90,22 @@
minGroupSize: 4
maxGroupSize: 5
- type: cp14DemiplaneModifier
id: Ruins
levels:
min: 0
max: 10
name: cp14-modifier-ruins
categories:
Reward: 0.15
generationProb: 0.8
layers:
- !type:OreDunGen
entity: CP14DemiplaneRuinsRoomSpawner
count: 8
minGroupSize: 1
maxGroupSize: 1
# TIER 2
- type: cp14DemiplaneModifier

View File

@@ -4,7 +4,7 @@
id: IronOre
levels:
min: 1
max: 10
max: 7
name: cp14-modifier-iron-ore
unique: false
categories:
@@ -25,7 +25,7 @@
id: IronOreUnderground
levels:
min: 1
max: 10
max: 7
name: cp14-modifier-iron-ore
unique: false
categories:
@@ -46,7 +46,7 @@
id: CopperOre
levels:
min: 1
max: 4
max: 2
name: cp14-modifier-copper-ore
unique: false
categories:
@@ -67,7 +67,7 @@
id: CopperOreUnderground
levels:
min: 1
max: 4
max: 2
name: cp14-modifier-copper-ore
unique: false
categories:
@@ -89,8 +89,8 @@
- type: cp14DemiplaneModifier
id: GoldOre
levels:
min: 5
max: 6
min: 4
max: 5
name: cp14-modifier-gold-ore
unique: false
categories:
@@ -110,8 +110,8 @@
- type: cp14DemiplaneModifier
id: GoldOreUnderground
levels:
min: 3
max: 6
min: 4
max: 5
name: cp14-modifier-gold-ore
unique: false
categories:

View File

@@ -9,17 +9,12 @@
maxGroupSize: 1
- type: cp14DemiplaneModifier
id: Ruins
levels:
min: 0
max: 10
categories:
Reward: 0.1
generationProb: 0.8
id: Core
generationProb: 0
layers:
- !type:OreDunGen
entity: CP14DemiplaneRuinsRoomSpawner
count: 8
entity: CP14DemiplanCoreRoomMarker
count: 1
minGroupSize: 1
maxGroupSize: 1
@@ -37,16 +32,22 @@
count: 20
minGroupSize: 1
maxGroupSize: 1
- type: cp14DemiplaneModifier
id: DemiplaneDecor
generationProb: 0
layers:
- !type:OreDunGen
entity: CP14AstralCorrosion
count: 15
minGroupSize: 3
maxGroupSize: 10
- type: cp14DemiplaneModifier
id: TimeLimit10
generationProb: 0
name: cp14-modifier-time-limit-10
components:
- type: CP14DemiplaneTimedDestruction
timeToDestruction: 600 # 10 minutes
- !type:OreDunGen
entityMask:
- CP14WallStone
- CP14WallDirt
- CP14WallSnow
entity: CP14WallDimensit
count: 5
minGroupSize: 3
maxGroupSize: 5

View File

@@ -0,0 +1,55 @@
- type: cp14SpecialDemiplane
id: Test
levels:
min: 5
max: 5
location: T1GrasslandIsland
- type: cp14SpecialDemiplane
id: Test2
levels:
min: 5
max: 5
location: T1Caves
- type: cp14SpecialDemiplane
id: Test3
levels:
min: 5
max: 5
location: T1IceCaves
- type: cp14SpecialDemiplane
id: Test4
levels:
min: 5
max: 5
location: T1MagmaCaves
- type: cp14SpecialDemiplane
id: Test5
levels:
min: 5
max: 5
location: T1SnowIsland
- type: cp14SpecialDemiplane
id: Test6
levels:
min: 5
max: 5
location: T1SnowIsland
- type: cp14SpecialDemiplane
id: Test7
levels:
min: 9
max: 10
location: T1MagmaCaves
- type: cp14SpecialDemiplane
id: Test8
levels:
min: 9
max: 10
location: T1Wastelands

View File

@@ -0,0 +1,50 @@
- type: Tag
id: CP14DemiplanCoreRoom
- type: entity
id: CP14DemiplanCoreRoomMarker
categories: [ ForkFiltered ]
parent: BaseRoomMarker
name: Demiplane Core room marker
components:
- type: RoomFill
clearExisting: true
roomWhitelist:
tags:
- CP14DemiplanCoreRoom
- type: dungeonRoom
id: CP14DemiplanCoreRoom_1
size: 7,7
atlas: /Maps/_CP14/Dungeon/demiplane_core.yml
ignoreTile: FloorShuttlePurple
offset: 0,0
tags:
- CP14DemiplanCoreRoom
- type: dungeonRoom
id: CP14DemiplanCoreRoom_2
size: 7,7
atlas: /Maps/_CP14/Dungeon/demiplane_core.yml
ignoreTile: FloorShuttlePurple
offset: 8,0
tags:
- CP14DemiplanCoreRoom
- type: dungeonRoom
id: CP14DemiplanCoreRoom_3
size: 7,7
atlas: /Maps/_CP14/Dungeon/demiplane_core.yml
ignoreTile: FloorShuttlePurple
offset: 16,0
tags:
- CP14DemiplanCoreRoom
- type: dungeonRoom
id: CP14DemiplanCoreRoom_4
size: 7,7
atlas: /Maps/_CP14/Dungeon/demiplane_core.yml
ignoreTile: FloorShuttlePurple
offset: 24,0
tags:
- CP14DemiplanCoreRoom

View File

@@ -16,7 +16,7 @@
- type: dungeonRoom
id: CP14DemiplanEnterRoom_1
size: 7,7
atlas: /Maps/_CP14/Dungeon/demiplan_enter.yml
atlas: /Maps/_CP14/Dungeon/demiplane_enter.yml
ignoreTile: FloorShuttlePurple
offset: 0,0
tags:
@@ -25,7 +25,7 @@
- type: dungeonRoom
id: CP14DemiplanEnterRoom_2
size: 7,7
atlas: /Maps/_CP14/Dungeon/demiplan_enter.yml
atlas: /Maps/_CP14/Dungeon/demiplane_enter.yml
ignoreTile: FloorShuttlePurple
offset: 8,0
tags:
@@ -34,7 +34,7 @@
- type: dungeonRoom
id: CP14DemiplanEnterRoom_3
size: 7,7
atlas: /Maps/_CP14/Dungeon/demiplan_enter.yml
atlas: /Maps/_CP14/Dungeon/demiplane_enter.yml
ignoreTile: FloorShuttlePurple
offset: 16,0
tags:
@@ -43,7 +43,7 @@
- type: dungeonRoom
id: CP14DemiplanEnterRoom_4
size: 7,7
atlas: /Maps/_CP14/Dungeon/demiplan_enter.yml
atlas: /Maps/_CP14/Dungeon/demiplane_enter.yml
ignoreTile: FloorShuttlePurple
offset: 24,0
tags:
@@ -52,7 +52,7 @@
- type: dungeonRoom
id: CP14DemiplanEnterRoom_5
size: 7,7
atlas: /Maps/_CP14/Dungeon/demiplan_enter.yml
atlas: /Maps/_CP14/Dungeon/demiplane_enter.yml
ignoreTile: FloorShuttlePurple
offset: 0,8
tags:
@@ -61,7 +61,7 @@
- type: dungeonRoom
id: CP14DemiplanEnterRoom_6
size: 9,7
atlas: /Maps/_CP14/Dungeon/demiplan_enter.yml
atlas: /Maps/_CP14/Dungeon/demiplane_enter.yml
ignoreTile: FloorShuttlePurple
offset: 8,8
tags:

View File

@@ -190,4 +190,21 @@
effects:
- !type:FlammableReaction
multiplier: 1
- !type:Ignite
- !type:Ignite
- type: weather
id: CP14DemiplaneDestructionStorm
sprite:
sprite: /Textures/Effects/weather.rsi
state: snowfall_heavy
alpha: 0.4
offsetSpeed: 0.56, -0.36
color: "#c034eb"
config:
- maxEntities: 4
frequency: 3
canAffectOnWeatherBlocker: false
effects:
- !type:SpawnEntityOnTop
prob: 0.25
entity: CP14SkyLightningPurple

View File

@@ -5,14 +5,13 @@ Demiplanes are one of the main ways for adventurers and the city as a whole to o
The main goal of exploration is for adventurers to obtain resources (ore, loot, etc.) and successfully return them back to their world.
As an adventurer, you can sell resources from the demiplane to artisans and other players at your own prices.
As an adventurer, you can sell resources from the demiplane to artisans and other players at your own prices.
## What research and preparation looks like.
There are "keys" in the game world that allow you to temporarily activate a portal to move into a procedurally generated demiplane with enemies and resources. Within the location there will be generated exits from the demiplane that you will have to find on your own.
<Box>
<GuideEntityEmbed Entity="CP14DemiplaneKeyT1"/>
<GuideEntityEmbed Entity="CP14DemiplanRiftCore"/>
</Box>.
@@ -42,11 +41,11 @@ To prepare for an expedition to the demiplane, you may need the following items:
- Weapons for all members of the group.
- Vials of healing potions or poison for weapons.
- Sharpening stones to sharpen your weapons on the spot.
- Armor for group members.
- Armor for group members.
- Magical items and/or crossbows (with ammunition) for dealing damage remotely.
- A "key" to open the demiplane.
- Extra bags or crates for collecting loot.
- Belt lanterns or magical lighting.
- Extra bags or crates for collecting loot.
- Belt lanterns or magical lighting.
- A supply of water and food for a short break from exploring.
## Cooperation tips
@@ -58,4 +57,4 @@ The entire Demiplane is designed for adventurers to (and should) work as a team.
- who will be your team leader during critical situations?
- What will you do with dead and injured team members?
</Document>
</Document>

View File

@@ -5,14 +5,13 @@
Основная задача при исследовании - добыча ресурсов (руды, лут, и прочее) авантюристами и удачный возврат обратно в свой мир.
Как авантюрист, вы можете продавать ресурсы с демиплана ремесленникам и иным игрокам по собственным ценам.
Как авантюрист, вы можете продавать ресурсы с демиплана ремесленникам и иным игрокам по собственным ценам.
## Как выглядит исследование и подготовка к ней.
В мире игры существуют «ключи», позволяющие временно активировать портал для перехода в процедурно сгенерированный демиплан с противниками и ресурсами. Внутри локации будут сгенерированы выходы с демиплана которые вам предстоит найти самим.
<Box>
<GuideEntityEmbed Entity="CP14DemiplaneKeyT1"/>
<GuideEntityEmbed Entity="CP14DemiplanRiftCore"/>
</Box>
@@ -42,11 +41,11 @@
- Оружие для всех участников группы.
- Флаконы с зельями лечения или ядом для оружия.
- Точильные камни для заточки вашего оружия на месте.
- Броня для участников группы.
- Броня для участников группы.
- Магические предметы и/или арбалеты (с амуницией) для дистанционного нанесения урона.
- "Ключ" для открытия демиплана.
- Дополнительные сумки или ящики для сбора добычи.
- Поясные фонари или магическое освещение.
- Дополнительные сумки или ящики для сбора добычи.
- Поясные фонари или магическое освещение.
- Запас воды и еды для непродолжительного перерыва в исследовании.
## Советы по кооперации
@@ -58,4 +57,4 @@
- кто будет лидером вашей группы во время критических ситуаций?
- что делать с убитыми и раненными членами группы?
</Document>
</Document>

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

View File

@@ -0,0 +1,35 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY-SA-4.0",
"copyright": "Created by TheShuEd (Github)",
"states": [
{
"name": "caves"
},
{
"name": "grassland_island"
},
{
"name": "ice_caves"
},
{
"name": "leaf_maze"
},
{
"name": "magma_caves"
},
{
"name": "snow_island"
},
{
"name": "swamp_caves"
},
{
"name": "wastelands"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

View File

Before

Width:  |  Height:  |  Size: 219 B

After

Width:  |  Height:  |  Size: 219 B

View File

Before

Width:  |  Height:  |  Size: 260 B

After

Width:  |  Height:  |  Size: 260 B

View File

Before

Width:  |  Height:  |  Size: 240 B

After

Width:  |  Height:  |  Size: 240 B

View File

Before

Width:  |  Height:  |  Size: 399 B

After

Width:  |  Height:  |  Size: 399 B

View File

@@ -0,0 +1,23 @@
{
"version": 1,
"size": {
"x": 48,
"y": 48
},
"license": "CC-BY-SA-4.0",
"copyright": "Created by TheShuEd (Github)",
"states": [
{
"name": "frame"
},
{
"name": "hovered"
},
{
"name": "selected"
},
{
"name": "learned"
}
]
}

View File

Before

Width:  |  Height:  |  Size: 191 B

After

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

View File

@@ -8,7 +8,7 @@
"copyright": "Created by TheShuEd (Github)",
"states": [
{
"name": "available"
"name": "center"
},
{
"name": "frame"

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -10,6 +10,9 @@
{
"name": "dimension"
},
{
"name": "dimension_core"
},
{
"name": "frame"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,46 @@
{
"version": 1,
"size": {
"x": 32,
"y": 64
},
"license": "CC-BY-SA-4.0",
"copyright": "Created by TheShuEd (Github)",
"states": [
{
"name": "wall0",
"directions": 4
},
{
"name": "wall1",
"directions": 4
},
{
"name": "wall2",
"directions": 4
},
{
"name": "wall3",
"directions": 4
},
{
"name": "wall4",
"directions": 4
},
{
"name": "wall5",
"directions": 4
},
{
"name": "wall6",
"directions": 4
},
{
"name": "wall7",
"directions": 4
},
{
"name": "full"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

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