rewritten core side to include tons more metadata/structure
need to rewrite renderer to output full map image rather than skipping sea
This commit is contained in:
Kurt 2026-01-19 01:20:43 -06:00
parent 9a54d0fb9a
commit 50ce05af6f
44 changed files with 968 additions and 544 deletions

View File

@ -16,6 +16,6 @@ public static int GetAcreTileColor(ushort acre, int x, int y)
var shift = (4 * ((y * 64) + x));
var ofs = baseOfs + shift;
var tile = AcreTiles[ofs];
return CollisionUtil.Dict[tile].ToArgb();
return TileCollisionUtil.Dict[tile].ToArgb();
}
}

View File

@ -11,8 +11,8 @@ namespace NHSE.Core;
/// <param name="AcreHeight">Always 6.</param>
public sealed record FieldItemDropper(int AcreWidth, int AcreHeight = 6)
{
private int MapHeight => AcreWidth * FieldItemLayer.TilesPerAcreDim;
private int MapWidth => AcreHeight * FieldItemLayer.TilesPerAcreDim;
private int MapHeight => AcreWidth * LayerFieldItem.TilesPerAcreDim;
private int MapWidth => AcreHeight * LayerFieldItem.TilesPerAcreDim;
// Each dropped item is a 2x2 square, with the top left tile being the root node, and the other 3 being extensions pointing back to the root.

View File

@ -12,11 +12,11 @@ public static class FieldItemUpgrade
private const int ColumnCountNew = 9; // +1 column on each side
private const int RowCount = 6;
private const byte SizeDim = FieldItemLayer.TilesPerAcreDim;
private const byte SizeDim = LayerFieldItem.TilesPerAcreDim;
private const int TilesPerAcre = SizeDim * SizeDim;
private const int FieldItemSizeOld = (ColumnCountOld * RowCount) * TilesPerAcre * Item.SIZE;
private const int FieldItemSizeNew = (ColumnCountNew * RowCount) * TilesPerAcre * Item.SIZE;
private const int FieldItemSizeSingleColumn = RowCount * TilesPerAcre * Item.SIZE;
private const int FieldItemSizeOld = ColumnCountOld * FieldItemSizeSingleColumn;
private const int FieldItemSizeNew = ColumnCountNew * FieldItemSizeSingleColumn;
/// <summary>
/// Checks if an update is needed based on the current size and expected size.

View File

@ -186,12 +186,17 @@ public void SetAcreBytes(ReadOnlySpan<byte> data)
data.CopyTo(Data[Offsets.OutsideField..]);
}
#pragma warning disable CA1822 // Mark members as static
public byte FieldItemAcreWidth => Offsets.FieldItemAcreWidth; // 3.0.0 updated from 7 => 9
// ReSharper disable once MemberCanBeMadeStatic.Global
public byte FieldItemAcreHeight => 6; // always 6
private int FieldItemAcreCount => FieldItemAcreWidth * FieldItemAcreHeight;
#pragma warning restore CA1822 // Mark members as static
private const int TotalTerrainTileCount = TerrainLayer.TilesPerAcreDim * TerrainLayer.TilesPerAcreDim * (7 * 6);
private int TotalFieldItemTileCount => FieldItemLayer.TilesPerAcreDim * FieldItemLayer.TilesPerAcreDim * FieldItemAcreCount;
private const int TotalTerrainTileCount = LayerTerrain.TilesPerAcreDim * LayerTerrain.TilesPerAcreDim * (7 * 6);
private int TotalFieldItemTileCount => LayerFieldItem.TilesPerAcreDim * LayerFieldItem.TilesPerAcreDim * FieldItemAcreCount;
public TerrainTile[] GetTerrainTiles() => TerrainTile.GetArray(Data.Slice(Offsets.LandMakingMap, TotalTerrainTileCount * TerrainTile.SIZE));
public void SetTerrainTiles(IReadOnlyList<TerrainTile> array) => TerrainTile.SetArray(array).CopyTo(Data[Offsets.LandMakingMap..]);
@ -212,17 +217,19 @@ public void ClearDesignTiles()
private int FieldItemLayerSize => TotalFieldItemTileCount * Item.SIZE;
private int FieldItemFlagSize => TotalFieldItemTileCount / sizeof(byte); // bitflags
private int FieldItemLayer1 => Offsets.FieldItem;
private int FieldItemLayer2 => Offsets.FieldItem + FieldItemLayerSize;
public int FieldItemFlag1 => Offsets.FieldItem + (FieldItemLayerSize * 2);
public int FieldItemFlag2 => Offsets.FieldItem + (FieldItemLayerSize * 2) + FieldItemFlagSize;
private int FieldItemLayer0 => Offsets.FieldItem;
private int FieldItemLayer1 => Offsets.FieldItem + FieldItemLayerSize;
public int FieldItemFlag0 => Offsets.FieldItem + (FieldItemLayerSize * 2);
public int FieldItemFlag1 => Offsets.FieldItem + (FieldItemLayerSize * 2) + FieldItemFlagSize;
public Memory<byte> FieldItemFlag0Data => Data.Slice(FieldItemFlag0, FieldItemFlagSize).ToArray();
public Memory<byte> FieldItemFlag1Data => Data.Slice(FieldItemFlag1, FieldItemFlagSize).ToArray();
public Item[] GetFieldItemLayer0() => Item.GetArray(Data.Slice(FieldItemLayer0, FieldItemLayerSize));
public void SetFieldItemLayer0(IReadOnlyList<Item> array) => Item.SetArray(array).CopyTo(Data[FieldItemLayer0..]);
public Item[] GetFieldItemLayer1() => Item.GetArray(Data.Slice(FieldItemLayer1, FieldItemLayerSize));
public void SetFieldItemLayer1(IReadOnlyList<Item> array) => Item.SetArray(array).CopyTo(Data[FieldItemLayer1..]);
public Item[] GetFieldItemLayer2() => Item.GetArray(Data.Slice(FieldItemLayer2, FieldItemLayerSize));
public void SetFieldItemLayer2(IReadOnlyList<Item> array) => Item.SetArray(array).CopyTo(Data[FieldItemLayer2..]);
public ushort OutsideFieldTemplateUniqueId
{
get => ReadUInt16LittleEndian(Data[(Offsets.OutsideField + AcreSizeAll)..]);

View File

@ -0,0 +1,33 @@
using System.Collections.Generic;
namespace NHSE.Core;
/// <summary>
/// Logic for interacting with a map's building layer.
/// </summary>
public interface ILayerBuilding
{
/// <summary>
/// Instantiated list of all buildings on the map.
/// </summary>
IReadOnlyList<Building> Buildings { get; init; }
/// <summary>
/// Converts relative building coordinates to absolute coordinates in the map grid.
/// </summary>
/// <param name="relX">Relative building X coordinate</param>
/// <param name="relY">Relative building Y coordinate</param>
/// <returns>Map absolute X/Y coordinates</returns>
(int X, int Y) GetCoordinatesAbsolute(ushort relX, ushort relY);
/// <summary>
/// Converts absolute map grid coordinates to relative building coordinates.
/// </summary>
/// <param name="absX">Map absolute X coordinate</param>
/// <param name="absY">Map absolute Y coordinate</param>
/// <returns>Relative building X/Y coordinates</returns>
(int X, int Y) GetCoordinatesRelative(int absX, int absY);
Building this[int i] { get; }
int Count { get; }
}

View File

@ -0,0 +1,48 @@
using System;
namespace NHSE.Core;
/// <summary>
/// Logic for managing field item flags within a layer.
/// </summary>
public interface ILayerFieldItemFlag
{
/// <summary>
/// Gets the active state of the field item flag at the specified relative coordinates.
/// </summary>
/// <param name="relX">Relative X coordinate within the array.</param>
/// <param name="relY">Relative Y coordinate within the array.</param>
/// <returns>The active state of the field item flag.</returns>
bool GetIsActive(int relX, int relY);
/// <summary>
/// Sets the active state of the field item flag at the specified relative coordinates.
/// </summary>
/// <param name="relX">Relative X coordinate within the array.</param>
/// <param name="relY">Relative Y coordinate within the array.</param>
/// <param name="value">The active state to set.</param>
void SetIsActive(int relX, int relY, bool value = true);
bool this[int relX, int relY]
{
get => GetIsActive(relX, relY);
set => SetIsActive(relX, relY, value);
}
/// <summary>
/// Deactivates all field item flags.
/// </summary>
void DeactivateAll();
/// <summary>
/// Saves the (in)active flags into the provided destination span.
/// </summary>
/// <param name="dest">Destination span to save the flags into.</param>
void Save(Span<byte> dest);
/// <summary>
/// Imports the (in)active flags from the provided source span.
/// </summary>
/// <param name="src">Source span containing the flags to import.</param>
void Import(Span<byte> src);
}

View File

@ -0,0 +1,27 @@
using System.Collections.Generic;
namespace NHSE.Core;
public interface ILayerFieldItemSet
{
LayerFieldItem Layer0 { get; }
LayerFieldItem Layer1 { get; }
bool IsOccupied(int relX, int relY);
Item GetItem(int relX, int relY, bool baseLayer);
void SetItem(int relX, int relY, bool baseLayer, Item value);
Item this[int relX, int relY, bool baseLayer]
{
get => GetItem(relX, relY, baseLayer);
set => SetItem(relX, relY, baseLayer, value);
}
/// <summary>
/// Lists out all coordinates of tiles present in <see cref="Layer1"/> that don't have anything underneath in <see cref="Layer0"/> to support them.
/// </summary>
List<string> GetUnsupportedTiles(int totalWidth, int totalHeight);
List<string> GetUnsupportedTiles() => GetUnsupportedTiles(Layer0.TileInfo.TotalWidth, Layer0.TileInfo.TotalHeight);
}

View File

@ -0,0 +1,39 @@
using System.Collections.Generic;
namespace NHSE.Core;
/// <summary>
/// Logic for interacting with a map's building layer.
/// </summary>
public sealed record LayerBuilding : ILayerBuilding
{
public required IReadOnlyList<Building> Buildings { get; init; }
// Although there is terrain in the Top Row and Left Column, no buildings can be placed there.
// Buildings can only be placed below that line; per the map layout, there is 2 acres worth of buffer, then our origin starts.
/// <summary>
/// Compared to Item Tiles, building tiles are a 16x16 resolution (2x2 item tiles).
/// When converting between building coordinates and absolute coordinates, we need to account for this.
/// </summary>
private const int BuildingResolution = 2;
private const int BuildingTilesPerAcre = 16;
private const int AcreBufferEdge = 2;
public (int X, int Y) GetCoordinatesAbsolute(ushort relX, ushort relY)
{
int absX = (relX * BuildingResolution) + (AcreBufferEdge * BuildingTilesPerAcre);
int absY = (relY * BuildingResolution) + (AcreBufferEdge * BuildingTilesPerAcre);
return (absX, absY);
}
public (int X, int Y) GetCoordinatesRelative(int absX, int absY)
{
int relX = (absX - (AcreBufferEdge * BuildingTilesPerAcre)) / BuildingResolution;
int relY = (absY - (AcreBufferEdge * BuildingTilesPerAcre)) / BuildingResolution;
return (relX, relY);
}
public Building this[int i] => Buildings[i];
public int Count => Buildings.Count;
}

View File

@ -3,8 +3,8 @@
namespace NHSE.Core;
public sealed record FieldItemLayer(Item[] Tiles, byte AcreWidth, byte AcreHeight)
: ItemLayer(Tiles, GetViewport(AcreWidth, AcreHeight))
public sealed record LayerFieldItem(Item[] Tiles, byte AcreWidth, byte AcreHeight)
: LayerItem(Tiles, GetViewport(AcreWidth, AcreHeight))
{
private static TileGridViewport GetViewport(byte width, byte height) => new(TilesPerAcreDim, TilesPerAcreDim, width, height);
@ -116,4 +116,10 @@ bool IsFlowerWaterable(Item item)
return ModifyAll(xmin, ymin, width, height, IsFlowerWaterable, z => z.Water(all));
}
public Item this[int relX, int relY]
{
get => GetTile(relX, relY);
set => SetTile(relX, relY, value);
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.Diagnostics;
namespace NHSE.Core;
/// <summary>
/// Logic for managing field item flags within a layer.
/// </summary>
public sealed class LayerFieldItemFlag(Memory<byte> raw, int width, int height) : ILayerFieldItemFlag
{
public Span<byte> Data => raw.Span;
public bool GetIsActive(int relX, int relY)
=> FlagUtil.GetFlag(Data, GetLayerFlagIndex(relX, relY));
public void SetIsActive(int relX, int relY, bool value)
=> FlagUtil.SetFlag(Data, GetLayerFlagIndex(relX, relY), value);
public bool this[int relX, int relY]
{
get => GetIsActive(relX, relY);
set => SetIsActive(relX, relY, value);
}
/// <summary>
/// Although the Field Item Tiles are arranged y-column (y-x) based, the 'IsActive' flags are arranged x-row (x-y) based.
/// </summary>
private int GetLayerFlagIndex(int x, int y) => (y * width) + x;
public void DeactivateAll() => Data.Clear();
public void Save(Span<byte> dest) => Data.CopyTo(dest);
public void Import(Span<byte> src) => src.CopyTo(Data);
/// <summary>
/// Diagnostic check of the active flags against the tiles.
/// </summary>
/// <param name="tiles">Tiles to check against.</param>
public void DebugCheckTileActiveFlags(LayerItem tiles)
{
// this doesn't set anything; just diagnostic
// Although the Tiles are arranged y-column (y-x) based, the 'isActive' flags are arranged x-row (x-y) based.
// We can turn the isActive flag off if the item is not a root or the item cannot be animated.
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
var tile = tiles.GetTile(x, y);
var isActive = GetIsActive(x, y);
if (!isActive)
continue;
bool empty = tile.IsNone;
if (empty)
Debug.WriteLine($"Flag at ({x},{y}) is not a root object.");
}
}
}
}

View File

@ -0,0 +1,59 @@
using System.Collections.Generic;
namespace NHSE.Core;
/// <summary>
/// Manages the <see cref="Item"/> data for the player's outside overworld.
/// </summary>
public sealed class LayerFieldItemSet : ILayerFieldItemSet
{
/// <summary>
/// Base layer of items
/// </summary>
public required LayerFieldItem Layer0 { get; init; }
/// <summary>
/// Layer of items that are supported by <see cref="Layer0"/>
/// </summary>
public required LayerFieldItem Layer1 { get; init; }
/// <summary>
/// Lists out all coordinates of tiles present in <see cref="Layer1"/> that don't have anything underneath in <see cref="Layer0"/> to support them.
/// </summary>
public List<string> GetUnsupportedTiles(int totalWidth, int totalHeight)
{
var result = new List<string>();
for (int x = 0; x < totalWidth; x++)
{
for (int y = 0; y < totalHeight; y++)
{
// If there is an item on this layer...
var tile = Layer1.GetTile(x, y);
if (tile.IsNone)
continue;
// Then there must be something underneath it.
var support = Layer0.GetTile(x, y);
if (!support.IsNone)
continue; // dunno how to check if the tile can actually have an item put on top of it...
result.Add($"{x:000},{y:000}");
}
}
return result;
}
public bool IsOccupied(int relX, int relY) => !Layer0.GetTile(relX, relY).IsNone || !Layer1.GetTile(relX, relY).IsNone;
public Item GetItem(int relX, int relY, bool baseLayer)
{
var layer = baseLayer ? Layer0 : Layer1;
return layer.GetTile(relX, relY);
}
public void SetItem(int relX, int relY, bool baseLayer, Item value)
{
var layer = baseLayer ? Layer0 : Layer1;
layer[relX, relY] = value;
}
}

View File

@ -4,21 +4,24 @@
namespace NHSE.Core;
public abstract record ItemLayer : AcreSelectionGrid
public abstract record LayerItem : AcreSelectionGrid
{
public readonly Item[] Tiles;
protected ItemLayer(Item[] tiles, [ConstantExpected] byte w, [ConstantExpected] byte h) : this(tiles, new(w, h, w, h))
#pragma warning disable CA1857
protected LayerItem(Item[] tiles, [ConstantExpected] byte w, [ConstantExpected] byte h) : this(tiles, new(w, h, w, h))
#pragma warning restore CA1857
{
}
protected ItemLayer(Item[] tiles, TileGridViewport tileTileInfo) : base(tileTileInfo)
protected LayerItem(Item[] tiles, TileGridViewport tileTileInfo) : base(tileTileInfo)
{
Tiles = tiles;
Debug.Assert(TileInfo.TotalWidth * TileInfo.TotalHeight == tiles.Length);
}
public Item GetTile(in int x, in int y) => this[TileInfo.GetTileIndex(x, y)];
public void SetTile(in int x, in int y, Item tile) => this[TileInfo.GetTileIndex(x, y)] = tile;
public Item this[int index]
{

View File

@ -12,7 +12,7 @@ namespace NHSE.Core;
/// <param name="ShiftHeight">Vertical acre shift from the map's origin.</param>
/// <param name="TilesPerAcre">Number of tiles per acre in one dimension (16 or 32).</param>
/// <param name="TileBitShift">Bit shift value to convert between tiles and acres (4 for 16 tiles, 5 for 32 tiles).</param>
public readonly record struct MapLayerConfigAcre(
public readonly record struct LayerPositionConfig(
byte CountWidth, byte CountHeight,
byte ShiftWidth, byte ShiftHeight,
[ConstantExpected] byte TilesPerAcre, byte TileBitShift)
@ -42,20 +42,20 @@ namespace NHSE.Core;
private const byte Shift16 = 4; // div16 is same as sh 4
/// <summary>
/// Creates a new <see cref="MapLayerConfigAcre"/> instance, centering the layer within the acre.
/// Creates a new <see cref="LayerPositionConfig"/> instance, centering the layer within the acre.
/// </summary>
/// <param name="width">Width of the layer in acres.</param>
/// <param name="height">Height of the layer in acres.</param>
/// <param name="tilesPerAcre">Number of tiles per acre (16 or 32).</param>
/// <returns>A new <see cref="MapLayerConfigAcre"/> instance.</returns>
public static MapLayerConfigAcre Create(byte width, byte height, [ConstantExpected(Min = Grid16, Max = Grid32)] byte tilesPerAcre)
/// <returns>A new <see cref="LayerPositionConfig"/> instance.</returns>
public static LayerPositionConfig Create(byte width, byte height, [ConstantExpected(Min = Grid16, Max = Grid32)] byte tilesPerAcre)
{
var shiftW = (byte)((MapAcreWidth - width) / 2); // centered
var shiftH = (byte)((MapAcreHeight - height) / 2); // centered
var bitShift = tilesPerAcre == Grid16 ? Shift16 : Shift32;
#pragma warning disable CA1857
return new MapLayerConfigAcre(width, height, shiftW, shiftH, tilesPerAcre, bitShift);
return new LayerPositionConfig(width, height, shiftW, shiftH, tilesPerAcre, bitShift);
#pragma warning restore CA1857
}

View File

@ -3,27 +3,27 @@
namespace NHSE.Core;
public sealed record RoomItemLayer : ItemLayer
public sealed record LayerRoomItem : LayerItem
{
public const int SIZE = Width * Height * Item.SIZE;
private const byte Width = 20;
private const byte Height = 20;
public RoomItemLayer(ReadOnlySpan<byte> data) : this(Item.GetArray(data)) { }
public RoomItemLayer(Item[] tiles) : base(tiles, Width, Height) { }
public LayerRoomItem(ReadOnlySpan<byte> data) : this(Item.GetArray(data)) { }
public LayerRoomItem(Item[] tiles) : base(tiles, Width, Height) { }
public static RoomItemLayer[] GetArray(ReadOnlySpan<byte> data)
public static LayerRoomItem[] GetArray(ReadOnlySpan<byte> data)
{
var result = new RoomItemLayer[data.Length / SIZE];
var result = new LayerRoomItem[data.Length / SIZE];
for (int i = 0; i < result.Length; i++)
{
var slice = data.Slice(i * SIZE, SIZE);
result[i] = new RoomItemLayer(slice);
result[i] = new LayerRoomItem(slice);
}
return result;
}
public static byte[] SetArray(IReadOnlyList<RoomItemLayer> data)
public static byte[] SetArray(IReadOnlyList<LayerRoomItem> data)
{
var result = new byte[data.Count * SIZE];
for (int i = 0; i < data.Count; i++)

View File

@ -7,7 +7,7 @@ namespace NHSE.Core;
/// <summary>
/// Grid of <see cref="TerrainTile"/>
/// </summary>
public sealed record TerrainLayer : AcreSelectionGrid
public sealed record LayerTerrain : AcreSelectionGrid
{
public TerrainTile[] Tiles { get; init; }
public Memory<byte> BaseAcres { get; init; }
@ -19,7 +19,7 @@ public sealed record TerrainLayer : AcreSelectionGrid
private static TileGridViewport Viewport => new(TilesPerAcreDim, TilesPerAcreDim, CountAcreWidth, CountAcreHeight);
public TerrainLayer(TerrainTile[] tiles, Memory<byte> acres) : base(Viewport)
public LayerTerrain(TerrainTile[] tiles, Memory<byte> acres) : base(Viewport)
{
BaseAcres = acres;
Tiles = tiles;
@ -140,22 +140,6 @@ public void SetAllRoad(TerrainTile tile, bool interiorOnly = true)
}
}
public void GetBuildingCoordinate(ushort bx, ushort by, int scale, out int x, out int y)
{
// Although there is terrain in the Top Row and Left Column, no buildings can be placed there.
// Adjust the building coordinates down-right by an acre.
int buildingShift = TileInfo.ViewWidth;
x = (int)(((bx / 2f) - buildingShift) * scale);
y = (int)(((by / 2f) - buildingShift) * scale);
}
public void GetBuildingRelativeCoordinates(int topX, int topY, int acreScale, ushort bx, ushort by, out int relX, out int relY)
{
GetBuildingCoordinate(bx, by, acreScale, out var x, out var y);
relX = x - (topX * acreScale);
relY = y - (topY * acreScale);
}
public bool IsWithinGrid(int acreScale, int relX, int relY)
{
if ((uint)relX >= TileInfo.ViewWidth * acreScale)

View File

@ -1,109 +0,0 @@
using System.Collections.Generic;
using System.Diagnostics;
namespace NHSE.Core;
/// <summary>
/// Manages the <see cref="Item"/> data for the player's outside overworld.
/// </summary>
public sealed class FieldItemManager
{
/// <summary>
/// Base layer of items
/// </summary>
public readonly FieldItemLayer Layer1;
/// <summary>
/// Layer of items that are supported by <see cref="Layer1"/>
/// </summary>
public readonly FieldItemLayer Layer2;
/// <summary>
/// Reference to the save file that will be updated when <see cref="Save"/> is called.
/// </summary>
public readonly MainSave SAV;
public readonly int FieldAcreWidth;
public readonly int FieldAcreHeight;
public readonly int FieldItemWidth;
public readonly int FieldItemHeight;
public FieldItemManager(MainSave sav)
{
var (aWidth, aHeight) = (sav.FieldItemAcreWidth, sav.FieldItemAcreHeight);
Layer1 = new FieldItemLayer(sav.GetFieldItemLayer1(), aWidth, aHeight);
Layer2 = new FieldItemLayer(sav.GetFieldItemLayer2(), aWidth, aHeight);
SAV = sav;
FieldAcreWidth = aWidth;
FieldAcreHeight = aHeight;
FieldItemWidth = Layer1.TileInfo.TotalWidth;
FieldItemHeight = Layer1.TileInfo.TotalHeight;
}
/// <summary>
/// Stores all values for the <see cref="FieldItemManager"/> back to the <see cref="SAV"/>.
/// </summary>
public void Save()
{
SAV.SetFieldItemLayer1(Layer1.Tiles);
SAV.SetFieldItemLayer2(Layer2.Tiles);
SetTileActiveFlags(Layer1, SAV.FieldItemFlag1);
SetTileActiveFlags(Layer2, SAV.FieldItemFlag2);
}
/// <summary>
/// Lists out all coordinates of tiles present in <see cref="Layer2"/> that don't have anything underneath in <see cref="Layer1"/> to support them.
/// </summary>
public List<string> GetUnsupportedTiles(int totalWidth, int totalHeight)
{
var result = new List<string>();
for (int x = 0; x < totalWidth; x++)
{
for (int y = 0; y < totalHeight; y++)
{
var tile = Layer2.GetTile(x, y);
if (tile.IsNone)
continue;
var support = Layer1.GetTile(x, y);
if (!support.IsNone)
continue; // dunno how to check if the tile can actually have an item put on top of it...
result.Add($"{x:000},{y:000}");
}
}
return result;
}
private void SetTileActiveFlags(ItemLayer tiles, int ofs)
{
// this doesn't set anything; just diagnostic
// Although the Tiles are arranged y-column (y-x) based, the 'isActive' flags are arranged x-row (x-y) based.
// We can turn the isActive flag off if the item is not a root or the item cannot be animated.
for (int x = 0; x < FieldItemWidth; x++)
{
for (int y = 0; y < FieldItemHeight; y++)
{
var tile = tiles.GetTile(x, y);
var isActive = GetIsActive(ofs, x, y);
if (!isActive)
continue;
bool empty = tile.IsNone;
if (empty)
Debug.WriteLine($"Flag at ({x},{y}) is not a root object.");
}
}
}
public bool GetIsActive(bool baseLayer, int x, int y) => GetIsActive(baseLayer ? SAV.FieldItemFlag1 : SAV.FieldItemFlag2, x, y);
public void SetIsActive(bool baseLayer, int x, int y, bool value) => SetIsActive(baseLayer ? SAV.FieldItemFlag1 : SAV.FieldItemFlag2, x, y, value);
private bool GetIsActive(int ofs, int x, int y) => FlagUtil.GetFlag(SAV.Data, ofs, (y * FieldItemWidth) + x);
private void SetIsActive(int ofs, int x, int y, bool value) => FlagUtil.SetFlag(SAV.Data, ofs, (y * FieldItemWidth) + x, value);
public bool IsOccupied(int x, int y) => !Layer1.GetTile(x, y).IsNone || !Layer2.GetTile(x, y).IsNone;
}

View File

@ -0,0 +1,47 @@
namespace NHSE.Core;
public sealed class MapEditor
{
/// <summary>
/// Master interactor for mutating the map.
/// </summary>
public required MapMutator Mutator { get; init; }
/// <summary>
/// Amount of pixel upscaling compared to a 1px = 1 tile map.
/// </summary>
public int MapScale { get; set; } = 2;
/// <summary>
/// Amount of pixel upscaling compared to a 1px = 1 tile map.
/// </summary>
public int AcreScale { get; set; } = 8;
/// <summary>
/// Converts an upscaled coordinate to a tile coordinate.
/// </summary>
/// <param name="mX">X coordinate (mouse on upscaled image).</param>
/// <param name="mY">Y coordinate (mouse on upscaled image).</param>
/// <param name="x">Absolute tile X coordinate.</param>
/// <param name="y">Absolute tile Y coordinate.</param>
public void GetCursorCoordinates(in int mX, in int mY, out int x, out int y)
{
x = mX / MapScale;
y = mY / MapScale;
}
/// <summary>
/// Creates a new instance of the MapEditor class initialized from the specified save file.
/// </summary>
/// <param name="sav">The save file containing the data used to initialize the MapEditor. Cannot be null.</param>
/// <returns>A MapEditor instance populated with data from the provided save file.</returns>
public static MapEditor FromSaveFile(MainSave sav)
=> new() { Mutator = MapMutator.FromSaveFile(sav) };
public int X => Mutator.View.X;
public int Y => Mutator.View.Y;
public LayerTerrain Terrain => Mutator.Manager.LayerTerrain;
public ILayerBuilding Buildings => Mutator.Manager.LayerBuildings;
public ILayerFieldItemSet Items => Mutator.Manager.FieldItems;
}

View File

@ -0,0 +1,16 @@
namespace NHSE.Core;
public sealed class MapTileManager
{
public required LayerPositionConfig ConfigTerrain { get; init; }
public required LayerPositionConfig ConfigItems { get; init; }
public required LayerPositionConfig ConfigBuildings { get; init; }
public required ILayerFieldItemSet FieldItems { get; init; }
public required ILayerFieldItemFlag LayerItemFlag0 { get; init; }
public required ILayerFieldItemFlag LayerItemFlag1 { get; init; }
public required ILayerBuilding LayerBuildings { get; init; }
public required MapStatePlaza Plaza { get; init; }
public required LayerTerrain LayerTerrain { get; set; }
}

View File

@ -0,0 +1,61 @@
using System;
namespace NHSE.Core;
public sealed class MapMutator
{
public MapViewState View { get; init; } = new();
public required MapTileManager Manager { get; init; }
// Mutability State Tracking
public uint ItemLayerIndex { get; set => field = value & 1; }
public LayerFieldItem CurrentLayer => ItemLayerIndex == 0 ? Manager.FieldItems.Layer0 : Manager.FieldItems.Layer1;
/// <inheritdoc cref="ModifyFieldItems(Func{int,int,int,int,int},in bool,LayerFieldItem)"/>
public int ModifyFieldItems(Func<int, int, int, int, int> action, in bool wholeMap)
=> ModifyFieldItems(action, wholeMap, CurrentLayer);
/// <inheritdoc cref="ReplaceFieldItems(Item,Item,bool,LayerFieldItem)"/>
public int ReplaceFieldItems(Item oldItem, Item newItem, in bool wholeMap)
=> ReplaceFieldItems(oldItem, newItem, wholeMap, CurrentLayer);
/// <summary>
/// Modifies field items in the specified <paramref name="layerField"/> using the provided <paramref name="action"/> function.
/// </summary>
/// <param name="action">Range selector (xmin, ymin, width, height) to use.</param>
/// <param name="wholeMap">If true, the modification is applied across the entire map; otherwise, only within the current view.</param>
/// <param name="layerField">The layer field item to perform the modification on.</param>
/// <returns>The number of items modified.</returns>
public int ModifyFieldItems(Func<int, int, int, int, int> action, in bool wholeMap, LayerFieldItem layerField)
{
var (xMin, yMin) = wholeMap ? (0, 0) : (View.X, View.Y);
var info = layerField.TileInfo;
var (width, height) = wholeMap ? info.DimTotal : info.DimAcre;
return action(xMin, yMin, width, height);
}
/// <summary>
/// Replaces all instances of <paramref name="oldItem"/> with <paramref name="newItem"/> in the specified <paramref name="layerField"/>.
/// </summary>
/// <param name="oldItem">Item to be replaced.</param>
/// <param name="newItem">Item to replace with.</param>
/// <param name="wholeMap">If true, the replacement is done across the entire map; otherwise, only within the current view.</param>
/// <param name="layerField">The layer field item to perform the replacement on.</param>
/// <returns>The number of items replaced.</returns>
private int ReplaceFieldItems(Item oldItem, Item newItem, bool wholeMap, LayerFieldItem layerField)
{
var (xMin, yMin) = wholeMap ? (0, 0) : (View.X, View.Y);
var info = layerField.TileInfo;
var (width, height) = wholeMap ? info.DimTotal : info.DimAcre;
return layerField.ReplaceAll(oldItem, newItem, xMin, yMin, width, height);
}
/// <summary>
/// Creates a <see cref="MapMutator"/> from the provided <see cref="MainSave"/> file.
/// </summary>
/// <param name="sav">The save file containing the data used to initialize the MapMutator. Cannot be null.</param>
/// <returns>A MapMutator instance populated with data from the provided save file.</returns>
public static MapMutator FromSaveFile(MainSave sav)
=> new() { Manager = MapTileManagerUtil.FromSaveFile(sav) };
}

View File

@ -0,0 +1,18 @@
namespace NHSE.Core;
/// <summary>
/// Value storage for plaza position in the map.
/// </summary>
public sealed class MapStatePlaza
{
/// <summary>
/// Plaza Position X coordinate.
/// </summary>
public required uint X { get; set; }
/// <summary>
/// Plaza Position Z coordinate.
/// </summary>
public required uint Z { get; set; }
}

View File

@ -0,0 +1,41 @@
namespace NHSE.Core;
public static class MapTileManagerUtil
{
/// <summary>
/// Retrieves a <see cref="MapTileManager"/> from the provided <see cref="MainSave"/>.
/// </summary>
public static MapTileManager FromSaveFile(MainSave sav) => new()
{
ConfigTerrain = LayerPositionConfig.Create(7, 6, 16),
ConfigBuildings = LayerPositionConfig.Create(5, 4, 16),
ConfigItems = LayerPositionConfig.Create(sav.FieldItemAcreWidth, sav.FieldItemAcreHeight, 32),
LayerTerrain = new LayerTerrain(sav.GetTerrainTiles(), sav.GetAcreBytes()),
LayerBuildings = new LayerBuilding { Buildings = sav.Buildings },
FieldItems = new LayerFieldItemSet
{
Layer0 = new LayerFieldItem(sav.GetFieldItemLayer0(), sav.FieldItemAcreWidth, sav.FieldItemAcreHeight),
Layer1 = new LayerFieldItem(sav.GetFieldItemLayer1(), sav.FieldItemAcreWidth, sav.FieldItemAcreHeight),
},
LayerItemFlag0 = new LayerFieldItemFlag(sav.FieldItemFlag0Data, sav.FieldItemAcreWidth, sav.FieldItemAcreHeight),
LayerItemFlag1 = new LayerFieldItemFlag(sav.FieldItemFlag1Data, sav.FieldItemAcreWidth, sav.FieldItemAcreHeight),
Plaza = new MapStatePlaza { X = sav.EventPlazaLeftUpX, Z = sav.EventPlazaLeftUpZ },
};
/// <summary>
/// Sets the values from the provided <see cref="MapTileManager"/> into the provided <see cref="MainSave"/>.
/// </summary>
public static void SetManager(this MapTileManager mgr, MainSave sav)
{
sav.Buildings = mgr.LayerBuildings.Buildings;
sav.SetTerrainTiles(mgr.LayerTerrain.Tiles);
sav.SetFieldItemLayer0(mgr.FieldItems.Layer0.Tiles);
sav.SetFieldItemLayer1(mgr.FieldItems.Layer1.Tiles);
mgr.LayerItemFlag0.Save(sav.FieldItemFlag0Data.Span);
mgr.LayerItemFlag1.Save(sav.FieldItemFlag1Data.Span);
sav.SetAcreBytes(mgr.LayerTerrain.BaseAcres.Span);
sav.EventPlazaLeftUpX = mgr.Plaza.X;
sav.EventPlazaLeftUpZ = mgr.Plaza.Z;
}
}

View File

@ -0,0 +1,132 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace NHSE.Core;
public sealed record MapViewState
{
private const int MaxX = LayerFieldItem.TilesPerAcreDim * AcreCoordinate.CountAcreExteriorWidth;
private const int MaxY = LayerFieldItem.TilesPerAcreDim * AcreCoordinate.CountAcreExteriorHeight;
private const int EdgeBuffer = LayerFieldItem.TilesPerAcreDim;
/// <summary>
/// Amount the X/Y coordinates change when using arrow movement.
/// </summary>
[Range(1, 8)] public uint ArrowViewInterval { get; set; } = 2;
[Range(0, 1)] public int ItemLayerIndex
{
get;
set
{
if (value is not (0 or 1))
throw new ArgumentOutOfRangeException(nameof(value), "Item layer index must be 0 or 1.");
field = value;
}
}
/// <summary>
/// Top-left-origin X coordinate of the view.
/// </summary>
public int X
{
get;
private set
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)value, (uint)MaxX);
field = value;
}
}
/// <summary>
/// Top-left-origin Y coordinate of the view.
/// </summary>
public int Y
{
get;
private set
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)value, (uint)MaxY);
field = value;
}
}
public bool CanUp => Y != 0;
public bool CanDown => Y != MaxY;
public bool CanLeft => X != 0;
public bool CanRight => X != MaxX;
/// <summary>
/// Moves the view up by <see cref="ArrowViewInterval"/> tiles.
/// </summary>
/// <returns><see langword="true"/> if the view changed; otherwise, <see langword="false"/>.</returns>
public bool ArrowUp()
{
if (!CanUp)
return false;
Y = Math.Max(0, Y - (int)ArrowViewInterval);
return true;
}
/// <summary>
/// Moves the view left by <see cref="ArrowViewInterval"/> tiles.
/// </summary>
/// <returns><see langword="true"/> if the view changed; otherwise, <see langword="false"/>.</returns>
public bool ArrowLeft()
{
if (!CanLeft)
return false;
X = Math.Max(0, X - (int)ArrowViewInterval);
return true;
}
/// <summary>
/// Moves the view right by <see cref="ArrowViewInterval"/> tiles.
/// </summary>
/// <returns><see langword="true"/> if the view changed; otherwise, <see langword="false"/>.</returns>
public bool ArrowRight()
{
if (!CanRight)
return false;
X = Math.Min(MaxX - EdgeBuffer, X + (int)ArrowViewInterval);
return true;
}
/// <summary>
/// Moves the view down by <see cref="ArrowViewInterval"/> tiles.
/// </summary>
/// <returns><see langword="true"/> if the view changed; otherwise, <see langword="false"/>.</returns>
public bool ArrowDown()
{
if (!CanDown)
return false;
Y = Math.Min(MaxY - EdgeBuffer, Y + (int)ArrowViewInterval);
return true;
}
/// <summary>
/// Applies the requested coordinates (sanity checked).
/// </summary>
/// <returns><see langword="true"/> if the view changed; otherwise, <see langword="false"/>.</returns>
public bool SetViewTo(in int absX, in int absY)
{
var (x, y) = (X, Y);
X = Math.Clamp(absX, 0, MaxX - EdgeBuffer);
Y = Math.Clamp(absY, 0, MaxY - EdgeBuffer);
return x != X || y != Y;
}
/// <summary>
/// Sets the view to the top-left of the specified acre.
/// </summary>
/// <remarks>
/// Acres are ordered Y-down-first.
/// </remarks>
/// <param name="acre">Acre index to set the view to.</param>
public void SetViewToAcre(int acre)
{
var acreX = acre % (MaxX / EdgeBuffer);
var acreY = acre / (MaxX / EdgeBuffer);
SetViewTo(acreX * EdgeBuffer, acreY * EdgeBuffer);
}
}

View File

@ -1,6 +1,6 @@
namespace NHSE.Core;
public enum RoomLayerSurface
public enum RoomLayerSurface : byte
{
Floor = 0,
FloorSupported = 1,

View File

@ -4,14 +4,18 @@
namespace NHSE.Core;
public sealed class RoomItemManager
public sealed class RoomManager
{
public readonly RoomItemLayer[] Layers;
public readonly LayerRoomItem[] Layers;
public readonly IPlayerRoom Room;
/// <summary>
/// <see cref="RoomLayerSurface"/>
/// </summary>
private const int LayerCount = 8;
public RoomItemManager(IPlayerRoom room)
public RoomManager(IPlayerRoom room)
{
Layers = room.GetItemLayers();
Room = room;
@ -20,14 +24,14 @@ public RoomItemManager(IPlayerRoom room)
public void Save() => Room.SetItemLayers(Layers);
public bool IsOccupied(int layer, int x, int y)
public bool IsOccupied(RoomLayerSurface layer, int x, int y)
{
if ((uint)layer >= LayerCount)
throw new ArgumentOutOfRangeException(nameof(layer));
var l = Layers[layer];
var l = Layers[(int)layer];
var tile = l.GetTile(x, y);
return !tile.IsNone || (layer == (int)RoomLayerSurface.FloorSupported && IsOccupied((int)RoomLayerSurface.Floor, x, y));
return !tile.IsNone || (layer == RoomLayerSurface.FloorSupported && IsOccupied(RoomLayerSurface.Floor, x, y));
}
public List<string> GetUnsupportedTiles()
@ -35,9 +39,11 @@ public List<string> GetUnsupportedTiles()
var lBase = Layers[(int)RoomLayerSurface.Floor];
var lSupport = Layers[(int)RoomLayerSurface.FloorSupported];
var result = new List<string>();
for (int x = 0; x < lBase.TileInfo.TotalWidth; x++)
var (width, height) = lBase.TileInfo.DimTotal;
for (int x = 0; x < width; x++)
{
for (int y = 0; y < lBase.TileInfo.TotalHeight; y++)
for (int y = 0; y < height; y++)
{
var tile = lSupport.GetTile(x, y);
if (tile.IsNone)

View File

@ -1,7 +1,4 @@
using System.Collections.Generic;
using System.Drawing;
namespace NHSE.Core;
namespace NHSE.Core;
public enum OutsideAcre : ushort
{
@ -223,38 +220,4 @@ public enum OutsideAcre : ushort
FldOutEGarden00 = 315,
FldOutNGardenLFront00 = 316,
FldOutNGardenRFront00 = 317,
}
public static class CollisionUtil
{
public static readonly Dictionary<byte, Color> Dict = new()
{
{00, Color.FromArgb( 70, 120, 64)}, // Grass
{01, Color.FromArgb(128, 215, 195)}, // River
{03, Color.FromArgb(192, 192, 192)}, // Stone
{04, Color.FromArgb(240, 230, 170)}, // Sand
{05, Color.FromArgb(128, 215, 195)}, // Sea
{06, Color.FromArgb(255, 128, 128)}, // Wood
{07, Color.FromArgb(0 , 0, 0)}, // Null
{08, Color.FromArgb(32 , 32, 32)}, // Building
{09, Color.FromArgb(255, 0, 0)}, // ??
{10, Color.FromArgb(48 , 48, 48)}, // Door
{12, Color.FromArgb(128, 215, 195)}, // Water at mouths of river
{15, Color.FromArgb(128, 215, 195)}, // Strip of water between river mouth and river
{22, Color.FromArgb(190, 98, 98)}, // Wood (thin)
{28, Color.FromArgb(255, 0, 0)}, // ?? this one isn't even in ColGroundAttributeParam...
{29, Color.FromArgb(232, 222, 162)}, // Edge of beach, next to sea
{41, Color.FromArgb(118, 122, 132)}, // Rocks at top of map
{42, Color.FromArgb(128, 133, 147)}, // Taller regions, rocks at top of map
{43, Color.Cyan}, // Tide pool
{44, Color.FromArgb( 62, 112, 56)}, // Edge connecting grass and beach
{45, Color.FromArgb(118, 122, 132)}, // Some kind of rock
{46, Color.FromArgb(120, 207, 187)}, // Edge of sea, next to beach
{47, Color.FromArgb(128, 128, 0)}, // Sandstone
{49, Color.FromArgb(190, 98, 98)}, // Pier
{51, Color.FromArgb(32 , 152, 32)}, // "Grass-growing building"??
{70, Color.FromArgb(109, 113, 124)}, // Kapp'n's island rock
{149, Color.FromArgb(179, 207, 252)}, // Ice (traversable)
{150, Color.FromArgb(61 , 119, 212)}, // Ice (tall, with collision)
};
}

View File

@ -3,20 +3,20 @@
/// <summary>
/// Flagging various issues when trying to place an item.
/// </summary>
public enum PlacedItemPermission
public enum PlacedItemPermission : byte
{
/// <summary>
/// Item does not have any of its tiles overlapping with any other items.
/// </summary>
NoCollision,
NoCollision = 0,
/// <summary>
/// Item tiles are overlapping with another item.
/// </summary>
Collision,
Collision = 1,
/// <summary>
/// Item tiles would overflow out-of-bounds.
/// </summary>
OutOfBounds,
OutOfBounds = 2,
}

View File

@ -0,0 +1,38 @@
using System.Collections.Generic;
using System.Drawing;
namespace NHSE.Core;
public static class TileCollisionUtil
{
public static readonly Dictionary<byte, Color> Dict = new()
{
{00, Color.FromArgb( 70, 120, 64)}, // Grass
{01, Color.FromArgb(128, 215, 195)}, // River
{03, Color.FromArgb(192, 192, 192)}, // Stone
{04, Color.FromArgb(240, 230, 170)}, // Sand
{05, Color.FromArgb(128, 215, 195)}, // Sea
{06, Color.FromArgb(255, 128, 128)}, // Wood
{07, Color.FromArgb(0 , 0, 0)}, // Null
{08, Color.FromArgb(32 , 32, 32)}, // Building
{09, Color.FromArgb(255, 0, 0)}, // ??
{10, Color.FromArgb(48 , 48, 48)}, // Door
{12, Color.FromArgb(128, 215, 195)}, // Water at mouths of river
{15, Color.FromArgb(128, 215, 195)}, // Strip of water between river mouth and river
{22, Color.FromArgb(190, 98, 98)}, // Wood (thin)
{28, Color.FromArgb(255, 0, 0)}, // ?? this one isn't even in ColGroundAttributeParam...
{29, Color.FromArgb(232, 222, 162)}, // Edge of beach, next to sea
{41, Color.FromArgb(118, 122, 132)}, // Rocks at top of map
{42, Color.FromArgb(128, 133, 147)}, // Taller regions, rocks at top of map
{43, Color.Cyan}, // Tide pool
{44, Color.FromArgb( 62, 112, 56)}, // Edge connecting grass and beach
{45, Color.FromArgb(118, 122, 132)}, // Some kind of rock
{46, Color.FromArgb(120, 207, 187)}, // Edge of sea, next to beach
{47, Color.FromArgb(128, 128, 0)}, // Sandstone
{49, Color.FromArgb(190, 98, 98)}, // Pier
{51, Color.FromArgb(32 , 152, 32)}, // "Grass-growing building"??
{70, Color.FromArgb(109, 113, 124)}, // Kapp'n's island rock
{149, Color.FromArgb(179, 207, 252)}, // Ice (traversable)
{150, Color.FromArgb(61 , 119, 212)}, // Ice (tall, with collision)
};
}

View File

@ -32,6 +32,9 @@ namespace NHSE.Core;
/// </summary>
public int TotalCount => TotalWidth * TotalHeight;
public (int X, int Y) DimAcre => (ViewWidth, ViewHeight);
public (int X, int Y) DimTotal => (TotalWidth, TotalHeight);
public int GetTileIndex(int acreX, int acreY, int gridX, int gridY)
{
var x = (acreX * ViewWidth) + gridX;

View File

@ -1,21 +0,0 @@
using System.Collections.Generic;
namespace NHSE.Core;
public sealed class MapManager(MainSave sav) : MapTerrainStructure(sav)
{
public readonly FieldItemManager Items = new(sav);
public int MapLayer { get; set; } // 0 or 1
public FieldItemLayer CurrentLayer => MapLayer == 0 ? Items.Layer1 : Items.Layer2;
}
public abstract class MapTerrainStructure(MainSave sav)
{
public readonly TerrainLayer Terrain = new(sav.GetTerrainTiles(), sav.GetAcreBytes());
public readonly IReadOnlyList<Building> Buildings = sav.Buildings;
public uint PlazaX { get; set; } = sav.EventPlazaLeftUpX;
public uint PlazaY { get; set; } = sav.EventPlazaLeftUpZ;
}

View File

@ -1,101 +0,0 @@
using System;
namespace NHSE.Core;
public abstract class MapView(MapManager m, int scale = 16)
{
private const int ViewInterval = 2;
public readonly MapManager Map = m;
public int MapScale { get; } = 1;
public int AcreScale { get; } = scale;
public int TerrainScale => AcreScale * 2;
// Top Left Anchor Coordinates
public int X { get; set; }
public int Y { get; set; }
public bool CanUp => Y != 0;
public bool CanDown => Y < Map.CurrentLayer.TileInfo.TotalHeight - Map.CurrentLayer.TileInfo.ViewHeight;
public bool CanLeft => X != 0;
public bool CanRight => X < Map.CurrentLayer.TileInfo.TotalWidth - Map.CurrentLayer.TileInfo.ViewWidth;
public bool ArrowUp()
{
if (Y <= 0)
return false;
Y -= ViewInterval;
return true;
}
public bool ArrowLeft()
{
if (X <= 0)
return false;
X -= ViewInterval;
return true;
}
public bool ArrowRight()
{
if (X >= Map.CurrentLayer.TileInfo.TotalWidth - 2)
return false;
X += ViewInterval;
return true;
}
public bool ArrowDown()
{
if (Y >= Map.CurrentLayer.TileInfo.TotalHeight - ViewInterval)
return false;
Y += ViewInterval;
return true;
}
public bool SetViewTo(in int x, in int y)
{
var info = Map.CurrentLayer;
var newX = Math.Max(0, Math.Min(x, info.TileInfo.TotalWidth - info.TileInfo.ViewWidth));
var newY = Math.Max(0, Math.Min(y, info.TileInfo.TotalHeight - info.TileInfo.ViewHeight));
bool diff = X != newX || Y != newY;
X = newX;
Y = newY;
return diff;
}
public void SetViewToAcre(in int acre)
{
var layer = Map.Items.Layer1;
layer.GetViewAnchorCoordinates(acre, out var x, out var y);
SetViewTo(x, y);
}
public int ModifyFieldItems(Func<int, int, int, int, int> action, in bool wholeMap)
{
var layer = Map.CurrentLayer;
return wholeMap
? action(0, 0, layer.TileInfo.TotalWidth, layer.TileInfo.TotalHeight)
: action(X, Y, layer.TileInfo.ViewWidth, layer.TileInfo.ViewHeight);
}
public int ReplaceFieldItems(Item oldItem, Item newItem, in bool wholeMap)
{
var layer = Map.CurrentLayer;
return wholeMap
? layer.ReplaceAll(oldItem, newItem, 0, 0, layer.TileInfo.TotalWidth, layer.TileInfo.TotalHeight)
: layer.ReplaceAll(oldItem, newItem, X, Y, layer.TileInfo.ViewWidth, layer.TileInfo.ViewHeight);
}
public void GetCursorCoordinates(in int mX, in int mY, out int x, out int y)
{
x = mX / MapScale;
y = mY / MapScale;
}
public void GetViewAnchorCoordinates(int mX, int mY, out int x, out int y, bool centerReticle)
{
GetCursorCoordinates(mX, mY, out x, out y);
var layer = Map.Items.Layer1;
layer.TileInfo.GetViewAnchorCoordinates(ref x, ref y, centerReticle);
}
}

View File

@ -8,6 +8,6 @@ public interface IPlayerRoom
byte[] Write();
string Extension { get; }
RoomItemLayer[] GetItemLayers();
void SetItemLayers(IReadOnlyList<RoomItemLayer> value);
LayerRoomItem[] GetItemLayers();
void SetItemLayers(IReadOnlyList<LayerRoomItem> value);
}

View File

@ -20,8 +20,8 @@ public class PlayerRoom1(Memory<byte> raw) : IPlayerRoom
*/
public const int LayerCount = 8;
public RoomItemLayer[] GetItemLayers() => RoomItemLayer.GetArray(Data[..(LayerCount * RoomItemLayer.SIZE)].ToArray());
public void SetItemLayers(IReadOnlyList<RoomItemLayer> value) => RoomItemLayer.SetArray(value).CopyTo(Data);
public LayerRoomItem[] GetItemLayers() => LayerRoomItem.GetArray(Data[..(LayerCount * LayerRoomItem.SIZE)].ToArray());
public void SetItemLayers(IReadOnlyList<LayerRoomItem> value) => LayerRoomItem.SetArray(value).CopyTo(Data);
public bool GetIsActive(int layer, int x, int y) => FlagUtil.GetFlag(Data, 0x6400 + (layer * 0x34), (y * 20) + x);
public void SetIsActive(int layer, int x, int y, bool value = true) => FlagUtil.SetFlag(Data, 0x6400 + (layer * 0x34), (y * 20) + x, value);

View File

@ -21,4 +21,19 @@ public static void SetFlag(Span<byte> arr, int offset, int bitIndex, bool value)
arr[offset] &= (byte)~(1 << bitIndex);
arr[offset] |= (byte)((value ? 1 : 0) << bitIndex);
}
public static bool GetFlag(ReadOnlySpan<byte> arr, int bitIndex)
{
var b = arr[bitIndex >> 3];
var mask = 1 << (bitIndex & 7);
return (b & mask) != 0;
}
public static void SetFlag(Span<byte> arr, int bitIndex, bool value)
{
var offset = bitIndex >> 3;
bitIndex &= 7; // ensure bit access is 0-7
arr[offset] &= (byte)~(1 << bitIndex);
arr[offset] |= (byte)((value ? 1 : 0) << bitIndex);
}
}

View File

@ -47,7 +47,7 @@ private byte[] GetTiles()
}
public byte GetTile(int x, int y) => Tiles[(y * Width) + x];
public Color GetTileColor(int x, int y) => CollisionUtil.Dict[GetTile(x, y)];
public Color GetTileColor(int x, int y) => TileCollisionUtil.Dict[GetTile(x, y)];
public uint Magic => ReadUInt32LittleEndian(Data);
public uint Width => ReadUInt32LittleEndian(Data[0x04..]);

View File

@ -6,7 +6,7 @@ namespace NHSE.Sprites;
public static class ItemLayerSprite
{
public static Bitmap GetBitmapItemLayer(ItemLayer layer)
public static Bitmap GetBitmapItemLayer(LayerItem layer)
{
var items = layer.Tiles;
var height = layer.TileInfo.TotalHeight;
@ -32,7 +32,7 @@ private static void LoadBitmapLayer(ReadOnlySpan<Item> items, Span<int> bmpData,
}
}
private static void LoadPixelsFromLayer(ItemLayer layer, int x0, int y0, int width, Span<int> bmpData)
private static void LoadPixelsFromLayer(LayerItem layer, int x0, int y0, int width, Span<int> bmpData)
{
var stride = layer.TileInfo.ViewWidth;
@ -50,7 +50,7 @@ private static void LoadPixelsFromLayer(ItemLayer layer, int x0, int y0, int wid
}
// non-allocation image generator
public static Bitmap GetBitmapItemLayerViewGrid(ItemLayer layer, int x0, int y0, int scale, Span<int> acre1, int[] acreScale, Bitmap dest, int transparency = -1, int gridlineColor = 0)
public static Bitmap GetBitmapItemLayerViewGrid(LayerItem layer, int x0, int y0, int scale, Span<int> acre1, int[] acreScale, Bitmap dest, int transparency = -1, int gridlineColor = 0)
{
int w = layer.TileInfo.ViewWidth;
int h = layer.TileInfo.ViewHeight;
@ -73,7 +73,7 @@ public static Bitmap GetBitmapItemLayerViewGrid(ItemLayer layer, int x0, int y0,
return dest;
}
private static void DrawDirectionals(Span<int> data, ItemLayer layer, int w, int x0, int y0, int scale)
private static void DrawDirectionals(Span<int> data, LayerItem layer, int w, int x0, int y0, int scale)
{
for (int x = x0; x < x0 + layer.TileInfo.ViewWidth; x++)
{
@ -232,7 +232,7 @@ public static void DrawGrid(Span<int> data, int w, int h, int scale, int gridlin
}
}
public static Bitmap GetBitmapItemLayer(ItemLayer layer, int x, int y, int[] data, Bitmap dest, int transparency = -1)
public static Bitmap GetBitmapItemLayer(LayerItem layer, int x, int y, int[] data, Bitmap dest, int transparency = -1)
{
LoadBitmapLayer(layer.Tiles, data, layer.TileInfo.TotalWidth, layer.TileInfo.TotalHeight);
if (transparency >>> 24 != 0xFF)

View File

@ -7,8 +7,12 @@ namespace NHSE.Sprites;
/// <summary>
/// Produces bitmaps for viewing map acres and full maps.
/// </summary>
public sealed class MapViewer : MapView, IDisposable
public sealed class MapRenderer : IDisposable
{
private readonly MapEditor Map;
private int AcreScale => Map.MapScale;
private int MapScale => Map.MapScale * 2;
private const byte ScaleAsMap = 2;
private const byte FieldItemWidthOld = 7;
private const byte FieldItemWidthNew = 9;
@ -23,13 +27,16 @@ public sealed class MapViewer : MapView, IDisposable
private readonly int[] PixelsBackgroundAcre1;
private readonly int[] PixelsBackgroundAcreX;
private readonly Bitmap BackgroundAcre;
private readonly int[] PixelsBackgroundMap1;
private readonly int[] PixelsBackgroundMapX;
private readonly Bitmap BackgroundMap;
public MapViewer(MapManager m, int scale) : base(m, scale)
public MapRenderer(MapEditor m)
{
var l1 = m.Items.Layer1;
Map = m;
var l1 = m.Mutator.Manager.FieldItems.Layer0;
var info = l1.TileInfo;
PixelsItemAcre1 = new int[info.ViewWidth * info.ViewHeight];
PixelsItemAcreX = new int[PixelsItemAcre1.Length * AcreScale * AcreScale];
@ -55,8 +62,8 @@ public void Dispose()
BackgroundMap.Dispose();
}
public Bitmap GetLayerAcre(int t) => GetLayerAcre(X, Y, t);
public Bitmap GetMapWithReticle(int t) => GetMapWithReticle(X, Y, t, Map.CurrentLayer);
public Bitmap GetLayerAcre(int t) => GetLayerAcre(Map.Mutator.View.X, Map.Mutator.View.Y, t);
public Bitmap GetMapWithReticle(int t) => GetMapWithReticle(Map.Mutator.View.X, Map.Mutator.View.Y, t, Map.Mutator.CurrentLayer);
public Bitmap GetBackgroundTerrain(int index = -1)
{
@ -82,17 +89,17 @@ public Bitmap GetInflatedImage(Bitmap regular)
private Bitmap GetLayerAcre(int topX, int topY, int transparency)
{
var layer = Map.CurrentLayer;
var layer = Map.Mutator.CurrentLayer;
return ItemLayerSprite.GetBitmapItemLayerViewGrid(layer, topX, topY, AcreScale, PixelsItemAcre1, PixelsItemAcreX, ScaleAcre, transparency);
}
public Bitmap GetBackgroundAcre(Font f, byte transparencyBuilding, byte transparencyTerrain, int index = -1)
{
return TerrainSprite.GetAcre(this, f, PixelsBackgroundAcre1, PixelsBackgroundAcreX, BackgroundAcre, index, transparencyBuilding, transparencyTerrain);
return TerrainSprite.CreateAcreView(this, f, PixelsBackgroundAcre1, PixelsBackgroundAcreX, BackgroundAcre, index, transparencyBuilding, transparencyTerrain);
}
private Bitmap GetMapWithReticle(int topX, int topY, int t, FieldItemLayer layer)
private Bitmap GetMapWithReticle(int topX, int topY, int t, LayerFieldItem layerField)
{
return ItemLayerSprite.GetBitmapItemLayer(layer, topX, topY, PixelsItemMap, MapReticle, t);
return ItemLayerSprite.GetBitmapItemLayer(layerField, topX, topY, PixelsItemMap, MapReticle, t);
}
}

View File

@ -17,23 +17,24 @@ public static class TerrainSprite
private const int PlazaWidth = 6 * 2;
private const int PlazaHeight = 5 * 2;
public static void CreateMap(TerrainLayer mgr, Span<int> pixels)
public static void CreateMap(LayerTerrain mgr, Span<int> pixels)
{
// Populate the image, with each pixel being a single tile.
int width = mgr.TileInfo.TotalWidth;
int height = mgr.TileInfo.TotalHeight;
int i = 0;
for (int y = 0; y < mgr.TileInfo.TotalHeight; y++)
for (int y = 0; y < height; y++)
{
for (int x = 0; x < mgr.TileInfo.TotalWidth; x++, i++)
{
for (int x = 0; x < width; x++, i++)
pixels[i] = mgr.GetTileColor(x, y, x, y);
}
}
}
public static Bitmap CreateMap(TerrainLayer mgr, Span<int> scale1, int[] scaleX, Bitmap map, int scale, int acreIndex = -1)
public static Bitmap CreateMap(LayerTerrain mgr, Span<int> scale1, Span<int> scaleX, Bitmap map, int scale, int acreIndex = -1)
{
CreateMap(mgr, scale1);
ImageUtil.ScalePixelImage(scale1, scaleX, map.Width, map.Height, scale);
ImageUtil.SetBitmapData(map, scaleX);
map.SetBitmapData(scaleX);
if (acreIndex < 0)
return map;
@ -56,7 +57,7 @@ private static Bitmap DrawReticle(Bitmap map, TileGridViewport mgr, int x, int y
return map;
}
public static Bitmap GetMapWithBuildings(MapTerrainStructure m, Font? f, Span<int> scale1, int[] scaleX, Bitmap map, int scale = 4, int index = -1)
public static Bitmap GetMapWithBuildings(MapTerrainStructure m, Font? f, Span<int> scale1, Span<int> scaleX, Bitmap map, int scale = 4, int index = -1)
{
CreateMap(m.Terrain, scale1, scaleX, map, scale);
using var gfx = Graphics.FromImage(map);
@ -66,7 +67,7 @@ public static Bitmap GetMapWithBuildings(MapTerrainStructure m, Font? f, Span<in
return map;
}
private static void DrawPlaza(this Graphics gfx, TerrainLayer g, ushort px, ushort py, int scale)
private static void DrawPlaza(this Graphics gfx, LayerTerrain g, ushort px, ushort py, int scale)
{
g.GetBuildingCoordinate(px, py, scale, out var x, out var y);
@ -76,7 +77,7 @@ private static void DrawPlaza(this Graphics gfx, TerrainLayer g, ushort px, usho
gfx.FillRectangle(Plaza, x, y, width, height);
}
private static void DrawBuildings(this Graphics gfx, TerrainLayer g, IReadOnlyList<Building> buildings, Font? f, int scale, int index = -1)
private static void DrawBuildings(this Graphics gfx, LayerTerrain g, IReadOnlyList<Building> buildings, Font? f, int scale, int index = -1)
{
for (int i = 0; i < buildings.Count; i++)
{
@ -101,13 +102,13 @@ private static void DrawBuilding(Graphics gfx, Font? f, int scale, Brush pen, in
gfx.DrawString(name, f, text, new PointF(x, y - (scale * 2)), BuildingTextFormat);
}
private static void SetAcreTerrainPixels(int x, int y, TerrainLayer t, Span<int> data, Span<int> scaleX, int scale)
private static void SetAcreTerrainPixels(int x, int y, LayerTerrain t, Span<int> data, Span<int> scaleX, int scale)
{
GetAcre1(x, y, t, data);
ImageUtil.ScalePixelImage(data, scaleX, 16 * scale, 16 * scale, scale / 16);
}
private static void GetAcre1(int tileTopX, int tileTopY, TerrainLayer t, Span<int> data)
private static void GetAcre1(int tileTopX, int tileTopY, LayerTerrain t, Span<int> data)
{
int index = 0;
@ -129,26 +130,28 @@ private static void GetAcre1(int tileTopX, int tileTopY, TerrainLayer t, Span<in
}
}
public static Bitmap GetAcre(MapView m, Font f, Span<int> scale1, int[] scaleX, Bitmap acre, int index, byte tbuild, byte tterrain)
public static Bitmap CreateAcreView(MapEditor m, Font f, Span<int> scale1, Span<int> scaleX, Bitmap acre, int index, byte tbuild, byte tterrain)
{
// Convert from absolute to relative.
int mx = m.X / 2;
int my = m.Y / 2;
SetAcreTerrainPixels(mx, my, m.Map.Terrain, scale1, scaleX, m.TerrainScale);
SetAcreTerrainPixels(mx, my, m.Terrain, scale1, scaleX, m.Terrain.Scale);
const int grid1 = unchecked((int)0xFF888888u);
const int grid2 = unchecked((int)0xFF666666u);
ImageUtil.SetBitmapData(acre, scaleX);
acre.SetBitmapData(scaleX);
using var gfx = Graphics.FromImage(acre);
gfx.DrawAcrePlaza(m.Map.Terrain, mx, my, (ushort)m.Map.PlazaX, (ushort)m.Map.PlazaY, m.TerrainScale, tbuild);
var plaza = m.Mutator.Manager.Plaza;
gfx.DrawAcrePlaza(m.Terrain, mx, my, plaza.X, plaza.Z, m.Terrain.Scale, tbuild);
var buildings = m.Map.Buildings;
var t = m.Map.Terrain;
var buildings = m.Buildings.Buildings;
var t = m.Terrain;
for (var i = 0; i < buildings.Count; i++)
{
var b = buildings[i];
t.GetBuildingRelativeCoordinates(mx, my, m.TerrainScale, b.X, b.Y, out var x, out var y);
t.GetBuildingRelativeCoordinates(mx, my, m.Terrain.Scale, b.X, b.Y, out var x, out var y);
var pen = index == i ? Selected : Others;
if (tbuild != byte.MaxValue)
@ -157,30 +160,31 @@ public static Bitmap GetAcre(MapView m, Font f, Span<int> scale1, int[] scaleX,
pen = new SolidBrush(Color.FromArgb(tbuild, orig));
}
DrawBuilding(gfx, null, m.TerrainScale, pen, x, y, b, Text);
DrawBuilding(gfx, null, m.Terrain.Scale, pen, x, y, b, Text);
}
ImageUtil.GetBitmapData(acre, scaleX);
acre.GetBitmapData(scaleX);
ItemLayerSprite.DrawGrid(scaleX, acre.Width, acre.Height, m.AcreScale, grid1);
ItemLayerSprite.DrawGrid(scaleX, acre.Width, acre.Height, m.TerrainScale, grid2);
ImageUtil.SetBitmapData(acre, scaleX);
ItemLayerSprite.DrawGrid(scaleX, acre.Width, acre.Height, m.Terrain.Scale, grid2);
acre.SetBitmapData(scaleX);
foreach (var b in buildings)
{
t.GetBuildingRelativeCoordinates(mx, my, m.TerrainScale, b.X, b.Y, out var x, out var y);
if (!t.IsWithinGrid(m.TerrainScale, x, y))
t.GetBuildingRelativeCoordinates(mx, my, m.Terrain.Scale, b.X, b.Y, out var x, out var y);
if (!t.IsWithinGrid(m.Terrain.Scale, x, y))
continue;
var name = b.BuildingType.ToString();
gfx.DrawString(name, f, Text, new PointF(x, y - (m.TerrainScale * 2)), BuildingTextFormat);
var labelPosition = new PointF(x, y - (m.Terrain.Scale * 2));
gfx.DrawString(name, f, Text, labelPosition, BuildingTextFormat);
}
if (tterrain != 0)
DrawTerrainTileNames(mx, my, gfx, t, f, m.TerrainScale, tterrain);
DrawTerrainTileNames(mx, my, gfx, t, f, m.Terrain.Scale, tterrain);
return acre;
}
private static void DrawTerrainTileNames(int topX, int topY, Graphics gfx, TerrainLayer t, Font f, int scale, byte transparency)
private static void DrawTerrainTileNames(int topX, int topY, Graphics gfx, LayerTerrain t, Font f, int scale, byte transparency)
{
var pen = transparency != byte.MaxValue ? new SolidBrush(Color.FromArgb(transparency, Color.Black)) : Tile;
@ -200,9 +204,9 @@ private static void DrawTerrainTileNames(int topX, int topY, Graphics gfx, Terra
}
}
private static void DrawAcrePlaza(this Graphics gfx, TerrainLayer g, int topX, int topY, ushort px, ushort py, int scale, byte transparency)
private static void DrawAcrePlaza(this Graphics gfx, LayerTerrain g, int topX, int topY, uint plazaX, uint plazaY, int scale, byte transparency)
{
g.GetBuildingRelativeCoordinates(topX, topY, scale, px, py, out var x, out var y);
g.GetBuildingRelativeCoordinates(topX, topY, scale, plazaX, plazaY, out var x, out var y);
var width = scale * PlazaWidth;
var height = scale * PlazaHeight;

View File

@ -24,39 +24,69 @@ public static class ImageUtil
public Bitmap GetPalette() => GetBitmap(bg.GetPaletteBitmap(), DesignPatternPRO.PaletteColorCount, 1, PixelFormat.Format24bppRgb);
}
extension(Bitmap bmp)
{
public Span<byte> GetBitmapData(out BitmapData bmpData, PixelFormat format = PixelFormat.Format32bppArgb, byte bpp = 4)
{
bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, format);
return GetSpan(bmpData.Scan0, bmp.Width * bmp.Height * bpp);
}
public void GetBitmapData(Span<byte> data, PixelFormat format = PixelFormat.Format32bppArgb, byte bpp = 4)
{
var span = bmp.GetBitmapData(out var bmpData, format, bpp);
span.CopyTo(data);
bmp.UnlockBits(bmpData);
}
public void GetBitmapData(Span<int> data, PixelFormat format = PixelFormat.Format32bppArgb, byte bpp = 4)
{
var span = bmp.GetBitmapData(out var bmpData, format, bpp);
var src = MemoryMarshal.Cast<byte, int>(span);
src.CopyTo(data);
bmp.UnlockBits(bmpData);
}
public void SetBitmapData(ReadOnlySpan<byte> data, PixelFormat format = PixelFormat.Format32bppArgb, byte bpp = 4)
{
var span = bmp.GetBitmapData(out var bmpData, format, bpp);
data.CopyTo(span);
bmp.UnlockBits(bmpData);
}
public void SetBitmapData(Span<int> data, PixelFormat format = PixelFormat.Format32bppArgb, byte bpp = 4)
{
var span = bmp.GetBitmapData(out var bmpData, format, bpp);
var dest = MemoryMarshal.Cast<byte, int>(span);
data.CopyTo(dest);
bmp.UnlockBits(bmpData);
}
}
public static Bitmap GetBitmap(ReadOnlySpan<int> data, int width, int height, PixelFormat format = PixelFormat.Format32bppArgb)
{
var span = MemoryMarshal.Cast<int, byte>(data);
return GetBitmap(span, width, height, format);
}
public static Bitmap GetBitmap(ReadOnlySpan<byte> data, int width, int height, PixelFormat format = PixelFormat.Format32bppArgb)
{
var bmp = new Bitmap(width, height, format);
var bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, format);
var length = data.Length;
var span = MemoryMarshal.CreateSpan(ref Unsafe.AddByteOffset(ref Unsafe.NullRef<byte>(), bmpData.Scan0), length);
data[..length].CopyTo(span);
var span = bmp.GetBitmapData(out var bmpData);
data[..span.Length].CopyTo(span);
bmp.UnlockBits(bmpData);
return bmp;
}
public static Bitmap GetBitmap(int[] data, int width, int height, PixelFormat format = PixelFormat.Format32bppArgb)
public static Bitmap GetBitmap(Span<byte> data, int width, int height, PixelFormat format = PixelFormat.Format32bppArgb)
{
var bmp = new Bitmap(width, height, format);
SetBitmapData(bmp, data, format);
bmp.SetBitmapData(data, format);
return bmp;
}
public static void SetBitmapData(Bitmap bmp, int[] data, PixelFormat format = PixelFormat.Format32bppArgb)
{
var bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, format);
var ptr = bmpData.Scan0;
Marshal.Copy(data, 0, ptr, data.Length);
bmp.UnlockBits(bmpData);
}
public static void GetBitmapData(Bitmap bmp, int[] data, PixelFormat format = PixelFormat.Format32bppArgb)
{
var bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, format);
var ptr = bmpData.Scan0;
Marshal.Copy(ptr, data, 0, data.Length);
bmp.UnlockBits(bmpData);
}
private static Span<byte> GetSpan(IntPtr ptr, int length)
=> MemoryMarshal.CreateSpan(ref Unsafe.AddByteOffset(ref Unsafe.NullRef<byte>(), ptr), length);
// https://stackoverflow.com/a/24199315
public static Bitmap ResizeImage(Image image, int width, int height)
@ -127,4 +157,10 @@ public static void ClampAllTransparencyTo(Span<int> data, int trans)
for (int i = 0; i < data.Length; i++)
data[i] &= trans;
}
public static void ClampAllTransparencyTo(Span<byte> data, byte trans)
{
for (int i = 0; i < data.Length; i += 4)
data[i + 3] &= trans;
}
}

View File

@ -111,6 +111,8 @@ private Item LoadExtensionItem(Item item)
return item;
}
public Item LoadFieldsToNewItem() => SetItem(new Item());
public Item SetItem(Item item)
{
if (CHK_IsExtension.Checked)

View File

@ -88,7 +88,7 @@ private void B_Apply_Click(object sender, EventArgs e)
if (sizeY % 2 == 1)
sizeY++;
var ctr = SpawnItems(Editor.SpawnLayer, items, x, y, arrange, sizeX, sizeY, true);
var ctr = SpawnItems(Editor.Spawn, items, x, y, arrange, sizeX, sizeY, true);
if (ctr == 0)
{
WinFormsUtil.Alert(MessageStrings.MsgFieldItemModifyNone);
@ -98,7 +98,7 @@ private void B_Apply_Click(object sender, EventArgs e)
WinFormsUtil.Alert(string.Format(MessageStrings.MsgFieldItemModifyCount, count));
}
private static int SpawnItems(ItemLayer layer, IReadOnlyList<Item> items, int x, int y, SpawnArrangement arrange, int sizeX, int sizeY, bool noOverwrite)
private static int SpawnItems(LayerItem layer, IReadOnlyList<Item> items, int x, int y, SpawnArrangement arrange, int sizeX, int sizeY, bool noOverwrite)
{
// every {setting} tiles, we jump down to the next available row of tiles.
int x0 = x;

View File

@ -14,9 +14,11 @@ namespace NHSE.WinForms;
public sealed partial class FieldItemEditor : Form, IItemLayerEditor
{
private readonly MainSave SAV;
private readonly MapEditor Editor;
private MapViewState View => Editor.Mutator.View;
private MapTileManager Map => Editor.Mutator.Manager;
private readonly MapManager Map;
private readonly MapViewer View;
private readonly bool IsExtendedMap30;
private bool Loading;
@ -29,7 +31,13 @@ public sealed partial class FieldItemEditor : Form, IItemLayerEditor
private bool Dragging;
public ItemEditor ItemProvider => ItemEdit;
public ItemLayer SpawnLayer => Map.CurrentLayer;
/// <summary>
/// Layer to spawn items into.
/// </summary>
public LayerItem Spawn => CurrentLayer;
public LayerFieldItem CurrentLayer => Editor.Mutator.CurrentLayer;
private TerrainBrushEditor? tbeForm;
@ -38,11 +46,14 @@ public FieldItemEditor(MainSave sav)
InitializeComponent();
this.TranslateInterface(GameInfo.CurrentLanguage);
var scale = (PB_Acre.Width - 2) / FieldItemLayer.TilesPerAcreDim; // 1px border
// Read the expected scale from the control.
var scale = (PB_Acre.Width - 2) / LayerFieldItem.TilesPerAcreDim; // 1px border
SAV = sav;
IsExtendedMap30 = sav.FieldItemAcreWidth != 7;
Map = new MapManager(sav);
View = new MapViewer(Map, scale);
Editor = MapEditor.FromSaveFile(sav);
Editor.MapScale = scale;
Editor.AcreScale = scale;
Renderer = new MapRenderer(Editor);
Loading = true;
@ -80,7 +91,7 @@ private void LoadBuildings(MainSave sav)
NUD_PlazaX.Value = sav.EventPlazaLeftUpX;
NUD_PlazaY.Value = sav.EventPlazaLeftUpZ;
foreach (var obj in Map.Buildings)
foreach (var obj in Editor.Mutator.Manager.LayerBuildings.Buildings)
LB_Items.Items.Add(obj.ToString());
}
@ -193,7 +204,7 @@ private void ResetDrag()
private void OmniTile(MouseEventArgs e)
{
var tile = GetTile(Map.CurrentLayer, e, out var x, out var y);
var tile = GetTile(CurrentLayer, e, out var x, out var y);
OmniTile(tile, x, y);
}
@ -202,7 +213,7 @@ private void OmniTileTerrain(MouseEventArgs e)
SetHoveredItem(e);
var x = View.X + HoverX;
var y = View.Y + HoverY;
var tile = Map.Terrain.GetTile(x / 2, y / 2);
TerrainTile tile = Editor.Terrain.GetTile(x / 2, y / 2);
if (tbeForm?.IsBrushSelected != true)
{
OmniTileTerrain(tile);
@ -223,7 +234,7 @@ private void OmniTileTerrain(MouseEventArgs e)
for (int j = -radius; j < radius; j++)
{
if ((i * i) + (j * j) < threshold)
selectedTiles.Add(Map.Terrain.GetTile((x / 2) + i, (y / 2) + j));
selectedTiles.Add(Editor.Terrain.GetTile((x / 2) + i, (y / 2) + j));
}
}
@ -275,10 +286,10 @@ private void OmniTileTerrain(TerrainTile tile)
}
}
private Item GetTile(FieldItemLayer layer, MouseEventArgs e, out int x, out int y)
private Item GetTile(LayerFieldItem layerField, MouseEventArgs e, out int x, out int y)
{
SetHoveredItem(e);
return layer.GetTile(x = View.X + HoverX, y = View.Y + HoverY);
return layerField.GetTile(x = View.X + HoverX, y = View.Y + HoverY);
}
private void SetHoveredItem(MouseEventArgs e)
@ -292,15 +303,15 @@ private void SetHoveredItem(MouseEventArgs e)
private void GetAcreCoordinates(MouseEventArgs e, out int x, out int y)
{
x = e.X / View.AcreScale;
y = e.Y / View.AcreScale;
x = e.X / Editor.AcreScale;
y = e.Y / Editor.AcreScale;
}
private void PB_Acre_MouseDown(object sender, MouseEventArgs e) => ResetDrag();
private void PB_Acre_MouseMove(object sender, MouseEventArgs e)
{
var l = Map.CurrentLayer;
var l = CurrentLayer;
if (e.Button == MouseButtons.Left && CHK_MoveOnDrag.Checked)
{
MoveDrag(e);
@ -317,8 +328,9 @@ private void PB_Acre_MouseMove(object sender, MouseEventArgs e)
return;
var str = GameInfo.Strings;
var name = str.GetItemName(tile);
bool active = Map.Items.GetIsActive(NUD_Layer.Value == 0, x, y);
if (active)
var flagLayer = NUD_Layer.Value == 0 ? Map.LayerItemFlag0 : Map.LayerItemFlag1;
var isActive = flagLayer.GetIsActive(x, y);
if (isActive)
name = $"{name} [Active]";
TT_Hover.SetToolTip(PB_Acre, name);
SetCoordinateText(x, y);
@ -367,7 +379,7 @@ private void ViewTile(Item tile, int x, int y)
{
if (CHK_RedirectExtensionLoad.Checked && tile.IsExtension)
{
var l = Map.CurrentLayer;
var l = CurrentLayer;
var rx = Math.Max(0, Math.Min(l.TileInfo.TotalWidth - 1, x - tile.ExtensionX));
var ry = Math.Max(0, Math.Min(l.TileInfo.TotalHeight - 1, y - tile.ExtensionY));
var redir = l.GetTile(rx, ry);
@ -394,7 +406,7 @@ private void ViewTile(TerrainTile tile)
private void SetTile(Item tile, int x, int y)
{
var l = Map.CurrentLayer;
var l = CurrentLayer;
var pgt = new Item();
ItemEdit.SetItem(pgt);
@ -429,7 +441,7 @@ private void SetTile(Item tile, int x, int y)
private void ReplaceTile(Item tile, int x, int y)
{
var l = Map.CurrentLayer;
var l = CurrentLayer;
var pgt = new Item();
ItemEdit.SetItem(pgt);
@ -451,7 +463,7 @@ private void ReplaceTile(Item tile, int x, int y)
bool wholeMap = (ModifierKeys & Keys.Shift) != 0;
var copy = new Item(tile.RawValue);
var count = View.ReplaceFieldItems(copy, pgt, wholeMap);
var count = Editor.Mutator.ReplaceFieldItems(copy, pgt, wholeMap);
if (count == 0)
{
WinFormsUtil.Alert(MessageStrings.MsgFieldItemModifyNone);
@ -507,13 +519,14 @@ private void DeleteTile(Item tile, int x, int y)
{
if (CHK_AutoExtension.Checked)
{
var layer = CurrentLayer;
if (!tile.IsRoot)
{
x -= tile.ExtensionX;
y -= tile.ExtensionY;
tile = Map.CurrentLayer.GetTile(x, y);
tile = layer.GetTile(x, y);
}
Map.CurrentLayer.DeleteExtensionTiles(tile, x, y);
layer.DeleteExtensionTiles(tile, x, y);
}
tile.Delete();
@ -530,8 +543,8 @@ private void DeleteTile(TerrainTile tile)
private void B_Save_Click(object sender, EventArgs e)
{
var view = View.Map.Items.Layer1.TileInfo;
var unsupported = Map.Items.GetUnsupportedTiles(view.TotalWidth, view.TotalHeight);
var set = Map.FieldItems;
var unsupported = set.GetUnsupportedTiles();
if (unsupported.Count != 0)
{
var err = MessageStrings.MsgFieldItemUnsupportedLayer2Tile;
@ -541,16 +554,9 @@ private void B_Save_Click(object sender, EventArgs e)
return;
}
Map.Items.Save();
SAV.SetTerrainTiles(Map.Terrain.Tiles);
SAV.SetAcreBytes(Map.Terrain.BaseAcres.Span);
Map.SetManager(SAV);
SAV.OutsideFieldTemplateUniqueId = (ushort)NUD_MapAcreTemplateOutside.Value;
SAV.MainFieldParamUniqueID = (ushort)NUD_MapAcreTemplateField.Value;
SAV.Buildings = Map.Buildings;
SAV.EventPlazaLeftUpX = Map.PlazaX;
SAV.EventPlazaLeftUpZ = Map.PlazaY;
Close();
}
@ -561,12 +567,12 @@ private void Menu_View_Click(object sender, EventArgs e)
if (RB_Item.Checked)
{
var tile = Map.CurrentLayer.GetTile(x, y);
var tile = CurrentLayer.GetTile(x, y);
ViewTile(tile, x, y);
}
else if (RB_Terrain.Checked)
{
var tile = Map.Terrain.GetTile(x / 2, y / 2);
TerrainTile tile = Editor.Terrain.GetTile(x / 2, y / 2);
ViewTile(tile);
}
}
@ -578,12 +584,12 @@ private void Menu_Set_Click(object sender, EventArgs e)
if (RB_Item.Checked)
{
var tile = Map.CurrentLayer.GetTile(x, y);
var tile = CurrentLayer.GetTile(x, y);
SetTile(tile, x, y);
}
else if (RB_Terrain.Checked)
{
var tile = Map.Terrain.GetTile(x / 2, y / 2);
var tile = Editor.Terrain.GetTile(x / 2, y / 2);
SetTile(tile);
}
}
@ -595,12 +601,12 @@ private void Menu_Reset_Click(object sender, EventArgs e)
if (RB_Item.Checked)
{
var tile = Map.CurrentLayer.GetTile(x, y);
var tile = CurrentLayer.GetTile(x, y);
DeleteTile(tile, x, y);
}
else if (RB_Terrain.Checked)
{
var tile = Map.Terrain.GetTile(x / 2, y / 2);
var tile = Editor.Terrain.GetTile(x / 2, y / 2);
DeleteTile(tile);
}
}
@ -617,10 +623,11 @@ private void CM_Click_Opening(object sender, System.ComponentModel.CancelEventAr
return;
}
var isBase = NUD_Layer.Value == 0;
var x = View.X + HoverX;
var y = View.Y + HoverY;
Menu_Activate.Text = Map.Items.GetIsActive(isBase, x, y) ? "Inactivate" : "Activate";
var flagLayer = NUD_Layer.Value == 0 ? Map.LayerItemFlag0 : Map.LayerItemFlag1;
var isActive = flagLayer.GetIsActive(x, y);
Menu_Activate.Text = isActive ? "Inactivate" : "Activate";
CM_Click.Items.Add(Menu_Activate);
hasActivate = true;
}
@ -629,14 +636,15 @@ private void Menu_Activate_Click(object sender, EventArgs e)
{
var x = View.X + HoverX;
var y = View.Y + HoverY;
var isBase = NUD_Layer.Value == 0;
Map.Items.SetIsActive(isBase, x, y, !Map.Items.GetIsActive(isBase, x, y));
var flagLayer = NUD_Layer.Value == 0 ? Map.LayerItemFlag0 : Map.LayerItemFlag1;
var isActive = flagLayer.GetIsActive(x, y);
flagLayer.SetIsActive(x, y, !isActive);
}
private void B_Up_Click(object sender, EventArgs e)
{
if (ModifierKeys == Keys.Shift)
CB_Acre.SelectedIndex = Math.Max(0, CB_Acre.SelectedIndex - View.Map.Items.Layer1.TileInfo.Columns);
CB_Acre.SelectedIndex = Math.Max(0, CB_Acre.SelectedIndex - Editor.Items.Layer0.TileInfo.Columns);
else if (View.ArrowUp())
LoadItemGridAcre();
}
@ -660,18 +668,18 @@ private void B_Right_Click(object sender, EventArgs e)
private void B_Down_Click(object sender, EventArgs e)
{
if (ModifierKeys == Keys.Shift)
CB_Acre.SelectedIndex = Math.Min(CB_Acre.SelectedIndex + View.Map.Items.Layer1.TileInfo.Columns, CB_Acre.Items.Count - 1);
CB_Acre.SelectedIndex = Math.Min(CB_Acre.SelectedIndex + Editor.Items.Layer0.TileInfo.Columns, CB_Acre.Items.Count - 1);
else if (View.ArrowDown())
LoadItemGridAcre();
}
private void B_DumpAcre_Click(object sender, EventArgs e) => MapDumpHelper.DumpLayerAcreSingle(Map.CurrentLayer, AcreIndex, CB_Acre.Text, (int)NUD_Layer.Value);
private void B_DumpAcre_Click(object sender, EventArgs e) => MapDumpHelper.DumpLayerAcreSingle(CurrentLayer, AcreIndex, CB_Acre.Text, (int)NUD_Layer.Value);
private void B_DumpAllAcres_Click(object sender, EventArgs e) => MapDumpHelper.DumpLayerAcreAll(Map.CurrentLayer);
private void B_DumpAllAcres_Click(object sender, EventArgs e) => MapDumpHelper.DumpLayerAcreAll(CurrentLayer);
private void B_ImportAcre_Click(object sender, EventArgs e)
{
var layer = Map.CurrentLayer;
var layer = CurrentLayer;
if (!MapDumpHelper.ImportToLayerAcreSingle(layer, AcreIndex, CB_Acre.Text, (int)NUD_Layer.Value))
return;
ChangeViewToAcre(AcreIndex);
@ -680,33 +688,33 @@ private void B_ImportAcre_Click(object sender, EventArgs e)
private void B_ImportAllAcres_Click(object sender, EventArgs e)
{
if (!MapDumpHelper.ImportToLayerAcreAll(Map.CurrentLayer))
if (!MapDumpHelper.ImportToLayerAcreAll(CurrentLayer))
return;
ChangeViewToAcre(AcreIndex);
System.Media.SystemSounds.Asterisk.Play();
}
private void B_DumpBuildings_Click(object sender, EventArgs e) => MapDumpHelper.DumpBuildings(Map.Buildings);
private void B_DumpBuildings_Click(object sender, EventArgs e) => MapDumpHelper.DumpBuildings(Editor.Buildings.Buildings);
private void B_ImportBuildings_Click(object sender, EventArgs e)
{
if (!MapDumpHelper.ImportBuildings(Map.Buildings))
if (!MapDumpHelper.ImportBuildings(Editor.Buildings.Buildings))
return;
for (int i = 0; i < Map.Buildings.Count; i++)
LB_Items.Items[i] = Map.Buildings[i].ToString();
for (int i = 0; i < Editor.Buildings.Count; i++)
LB_Items.Items[i] = Editor.Buildings[i].ToString();
LB_Items.SelectedIndex = 0;
System.Media.SystemSounds.Asterisk.Play();
ReloadBuildingsTerrain();
}
private void B_DumpTerrainAcre_Click(object sender, EventArgs e) => MapDumpHelper.DumpTerrainAcre(Map.Terrain, AcreIndex, CB_Acre.Text);
private void B_DumpTerrainAcre_Click(object sender, EventArgs e) => MapDumpHelper.DumpTerrainAcre(Editor.Terrain, AcreIndex, CB_Acre.Text);
private void B_DumpTerrainAll_Click(object sender, EventArgs e) => MapDumpHelper.DumpTerrainAll(Map.Terrain);
private void B_DumpTerrainAll_Click(object sender, EventArgs e) => MapDumpHelper.DumpTerrainAll(Editor.Terrain);
private void B_ImportTerrainAcre_Click(object sender, EventArgs e)
{
if (!MapDumpHelper.ImportTerrainAcre(Map.Terrain, AcreIndex, CB_Acre.Text))
if (!MapDumpHelper.ImportTerrainAcre(Editor.Terrain, AcreIndex, CB_Acre.Text))
return;
ChangeViewToAcre(AcreIndex);
System.Media.SystemSounds.Asterisk.Play();
@ -714,7 +722,7 @@ private void B_ImportTerrainAcre_Click(object sender, EventArgs e)
private void B_ImportTerrainAll_Click(object sender, EventArgs e)
{
if (!MapDumpHelper.ImportTerrainAll(Map.Terrain))
if (!MapDumpHelper.ImportTerrainAll(Editor.Terrain))
return;
ChangeViewToAcre(AcreIndex);
System.Media.SystemSounds.Asterisk.Play();
@ -769,7 +777,7 @@ private void PB_Map_MouseDown(object sender, MouseEventArgs e)
private void ClickMapAt(MouseEventArgs e, bool skipLagCheck)
{
var layer = Map.Items.Layer1;
var layer = Editor.Items.Layer0;
int mX = e.X;
int mY = e.Y;
bool centerReticle = CHK_SnapToAcre.Checked;
@ -824,7 +832,7 @@ private void PB_Map_MouseMove(object sender, MouseEventArgs e)
private void NUD_Layer_ValueChanged(object sender, EventArgs e)
{
Map.MapLayer = (int)NUD_Layer.Value - 1;
View.ItemLayerIndex = (int)NUD_Layer.Value - 1;
LoadItemGridAcre();
}
@ -837,7 +845,7 @@ private void Remove(ToolStripItem sender, Func<int, int, int, int, int> removal)
if (question != DialogResult.Yes)
return;
int count = View.ModifyFieldItems(removal, wholeMap);
int count = Editor.Mutator.ModifyFieldItems(removal, wholeMap);
if (count == 0)
{
@ -857,7 +865,7 @@ private void Modify(ToolStripItem sender, Func<int, int, int, int, int> action)
if (question != DialogResult.Yes)
return;
int count = View.ModifyFieldItems(action, wholeMap);
int count = Editor.Mutator.ModifyFieldItems(action, wholeMap);
if (count == 0)
{
@ -868,54 +876,46 @@ private void Modify(ToolStripItem sender, Func<int, int, int, int, int> action)
WinFormsUtil.Alert(string.Format(MessageStrings.MsgFieldItemModifyCount, count));
}
private void B_RemoveEditor_Click(object sender, EventArgs e) => Remove(B_RemoveEditor, (min, max, x, y)
=> Map.CurrentLayer.RemoveAllLike(min, max, x, y, ItemEdit.SetItem(new Item())));
private void B_RemoveEditor_Click(object sender, EventArgs e)
{
var item = ItemEdit.LoadFieldsToNewItem();
var lambda = new Func<int, int, int, int, int>((min, max, x, y)
=> CurrentLayer.RemoveAllLike(min, max, x, y, item));
Remove(B_RemoveEditor, lambda);
}
private void B_RemoveAllWeeds_Click(object sender, EventArgs e) => Remove(B_RemoveAllWeeds, Map.CurrentLayer.RemoveAllWeeds);
private void B_WaterFlowers_Click(object sender, EventArgs e)
{
var all = (ModifierKeys & Keys.Control) != 0;
var lambda = new Func<int, int, int, int, int>((xmin, ymin, width, height)
=> CurrentLayer.WaterAllFlowers(xmin, ymin, width, height, all));
Modify(B_WaterFlowers, lambda);
}
private void B_RemoveAllTrees_Click(object sender, EventArgs e) => Remove(B_RemoveAllTrees, Map.CurrentLayer.RemoveAllTrees);
private void B_FillHoles_Click(object sender, EventArgs e) => Remove(B_FillHoles, Map.CurrentLayer.RemoveAllHoles);
private void B_RemovePlants_Click(object sender, EventArgs e) => Remove(B_RemovePlants, Map.CurrentLayer.RemoveAllPlants);
private void B_RemoveFences_Click(object sender, EventArgs e) => Remove(B_RemoveFences, Map.CurrentLayer.RemoveAllFences);
private void B_RemoveObjects_Click(object sender, EventArgs e) => Remove(B_RemoveObjects, Map.CurrentLayer.RemoveAllObjects);
private void B_RemoveAll_Click(object sender, EventArgs e) => Remove(B_RemoveAll, Map.CurrentLayer.RemoveAll);
private void B_RemovePlacedItems_Click(object sender, EventArgs e) => Remove(B_RemovePlacedItems, Map.CurrentLayer.RemoveAllPlacedItems);
private void B_RemoveShells_Click(object sender, EventArgs e) => Remove(B_RemoveShells, Map.CurrentLayer.RemoveAllShells);
private void B_RemoveBranches_Click(object sender, EventArgs e) => Remove(B_RemoveBranches, Map.CurrentLayer.RemoveAllBranches);
private void B_RemoveFlowers_Click(object sender, EventArgs e) => Remove(B_RemoveFlowers, Map.CurrentLayer.RemoveAllFlowers);
private void B_RemoveBushes_Click(object sender, EventArgs e) => Remove(B_RemoveBushes, Map.CurrentLayer.RemoveAllBushes);
private void B_WaterFlowers_Click(object sender, EventArgs e) => Modify(B_WaterFlowers, (xmin, ymin, width, height)
=> Map.CurrentLayer.WaterAllFlowers(xmin, ymin, width, height, (ModifierKeys & Keys.Control) != 0));
private void B_RemoveAllWeeds_Click(object sender, EventArgs e) => Remove(B_RemoveAllWeeds, CurrentLayer.RemoveAllWeeds);
private void B_RemoveAllTrees_Click(object sender, EventArgs e) => Remove(B_RemoveAllTrees, CurrentLayer.RemoveAllTrees);
private void B_FillHoles_Click(object sender, EventArgs e) => Remove(B_FillHoles, CurrentLayer.RemoveAllHoles);
private void B_RemovePlants_Click(object sender, EventArgs e) => Remove(B_RemovePlants, CurrentLayer.RemoveAllPlants);
private void B_RemoveFences_Click(object sender, EventArgs e) => Remove(B_RemoveFences, CurrentLayer.RemoveAllFences);
private void B_RemoveObjects_Click(object sender, EventArgs e) => Remove(B_RemoveObjects, CurrentLayer.RemoveAllObjects);
private void B_RemoveAll_Click(object sender, EventArgs e) => Remove(B_RemoveAll, CurrentLayer.RemoveAll);
private void B_RemovePlacedItems_Click(object sender, EventArgs e) => Remove(B_RemovePlacedItems, CurrentLayer.RemoveAllPlacedItems);
private void B_RemoveShells_Click(object sender, EventArgs e) => Remove(B_RemoveShells, CurrentLayer.RemoveAllShells);
private void B_RemoveBranches_Click(object sender, EventArgs e) => Remove(B_RemoveBranches, CurrentLayer.RemoveAllBranches);
private void B_RemoveFlowers_Click(object sender, EventArgs e) => Remove(B_RemoveFlowers, CurrentLayer.RemoveAllFlowers);
private void B_RemoveBushes_Click(object sender, EventArgs e) => Remove(B_RemoveBushes, CurrentLayer.RemoveAllBushes);
private static void ShowContextMenuBelow(ToolStripDropDown c, Control n) => c.Show(n.PointToScreen(new Point(0, n.Height)));
private void B_RemoveItemDropDown_Click(object sender, EventArgs e) => ShowContextMenuBelow(CM_Remove, B_RemoveItemDropDown);
private void B_DumpLoadField_Click(object sender, EventArgs e) => ShowContextMenuBelow(CM_DLField, B_DumpLoadField);
private void B_DumpLoadTerrain_Click(object sender, EventArgs e) => ShowContextMenuBelow(CM_DLTerrain, B_DumpLoadTerrain);
private void B_DumpLoadBuildings_Click(object sender, EventArgs e) => ShowContextMenuBelow(CM_DLBuilding, B_DumpLoadBuildings);
private void B_ModifyAllTerrain_Click(object sender, EventArgs e) => ShowContextMenuBelow(CM_Terrain, B_ModifyAllTerrain);
private void B_DumpLoadAcres_Click(object sender, EventArgs e) => ShowContextMenuBelow(CM_DLMapAcres, B_DumpLoadAcres);
private void TR_Transparency_Scroll(object sender, EventArgs e) => ReloadItems();
private void TR_BuildingTransparency_Scroll(object sender, EventArgs e) => ReloadBuildingsTerrain();
private void TR_Terrain_Scroll(object sender, EventArgs e) => ReloadBuildingsTerrain();
#region Buildings
@ -930,7 +930,7 @@ private void NUD_PlazaX_ValueChanged(object sender, EventArgs e)
{
if (Loading)
return;
Map.PlazaX = (uint)NUD_PlazaX.Value;
Map.Plaza.X = (uint)NUD_PlazaX.Value;
ReloadBuildingsTerrain();
}
@ -938,7 +938,7 @@ private void NUD_PlazaY_ValueChanged(object sender, EventArgs e)
{
if (Loading)
return;
Map.PlazaY = (uint)NUD_PlazaY.Value;
Map.Plaza.Z = (uint)NUD_PlazaY.Value;
ReloadBuildingsTerrain();
}
@ -957,7 +957,7 @@ private void LoadIndex(int index)
{
Loading = true;
SelectedBuildingIndex = index;
var b = Map.Buildings[index];
var b = Editor.Buildings[index];
NUD_BuildingType.Value = (int)b.BuildingType;
NUD_X.Value = b.X;
NUD_Y.Value = b.Y;
@ -981,7 +981,7 @@ private void NUD_BuildingType_ValueChanged(object sender, EventArgs e)
if (Loading || sender is not NumericUpDown n)
return;
var b = Map.Buildings[SelectedBuildingIndex];
var b = Editor.Buildings[SelectedBuildingIndex];
if (sender == NUD_BuildingType)
b.BuildingType = (BuildingType)n.Value;
else if (sender == NUD_X)
@ -999,7 +999,7 @@ private void NUD_BuildingType_ValueChanged(object sender, EventArgs e)
else if (sender == NUD_UniqueID)
b.UniqueID = (ushort)n.Value;
LB_Items.Items[SelectedBuildingIndex] = Map.Buildings[SelectedBuildingIndex].ToString();
LB_Items.Items[SelectedBuildingIndex] = Editor.Buildings[SelectedBuildingIndex].ToString();
ReloadBuildingsTerrain();
}
@ -1009,7 +1009,7 @@ private void NUD_BuildingType_ValueChanged(object sender, EventArgs e)
private void CB_MapAcre_SelectedIndexChanged(object sender, EventArgs e)
{
var acre = Map.Terrain.BaseAcres.Span[CB_MapAcre.SelectedIndex * 2];
var acre = Editor.Terrain.BaseAcres.Span[CB_MapAcre.SelectedIndex * 2];
CB_MapAcreSelect.SelectedValue = (int)acre;
// Jump view if available
@ -1025,7 +1025,7 @@ private void CB_MapAcreSelect_SelectedValueChanged(object sender, EventArgs e)
var index = CB_MapAcre.SelectedIndex;
var value = WinFormsUtil.GetIndex(CB_MapAcreSelect);
var span = Map.Terrain.BaseAcres.Span.Slice(index * 2, 2);
var span = Editor.Terrain.BaseAcres.Span.Slice(index * 2, 2);
var oldValue = span[0];
if (value == oldValue)
return;
@ -1036,7 +1036,7 @@ private void CB_MapAcreSelect_SelectedValueChanged(object sender, EventArgs e)
private void B_DumpMapAcres_Click(object sender, EventArgs e)
{
if (!MapDumpHelper.DumpMapAcresAll(Map.Terrain.BaseAcres.Span))
if (!MapDumpHelper.DumpMapAcresAll(Editor.Terrain.BaseAcres.Span))
return;
ReloadBuildingsTerrain();
System.Media.SystemSounds.Asterisk.Play();
@ -1044,7 +1044,7 @@ private void B_DumpMapAcres_Click(object sender, EventArgs e)
private void B_ImportMapAcres_Click(object sender, EventArgs e)
{
if (!MapDumpHelper.ImportMapAcresAll(Map.Terrain.BaseAcres.Span))
if (!MapDumpHelper.ImportMapAcresAll(Editor.Terrain.BaseAcres.Span))
return;
ReloadBuildingsTerrain();
System.Media.SystemSounds.Asterisk.Play();
@ -1056,7 +1056,7 @@ private void B_ZeroElevation_Click(object sender, EventArgs e)
{
if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MessageStrings.MsgTerrainSetElevation0))
return;
foreach (var t in Map.Terrain.Tiles)
foreach (var t in Editor.Terrain.Tiles)
t.Elevation = 0;
ReloadBuildingsTerrain();
System.Media.SystemSounds.Asterisk.Play();
@ -1069,7 +1069,7 @@ private void B_SetAllTerrain_Click(object sender, EventArgs e)
var pgt = (TerrainTile)PG_TerrainTile.SelectedObject!;
bool interiorOnly = DialogResult.Yes == WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MessageStrings.MsgTerrainSetAllSkipExterior);
Map.Terrain.SetAll(pgt, interiorOnly);
Editor.Terrain.SetAll(pgt, interiorOnly);
ReloadBuildingsTerrain();
System.Media.SystemSounds.Asterisk.Play();
@ -1082,7 +1082,7 @@ private void B_SetAllRoadTiles_Click(object sender, EventArgs e)
var pgt = (TerrainTile)PG_TerrainTile.SelectedObject!;
bool interiorOnly = DialogResult.Yes == WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MessageStrings.MsgTerrainSetAllSkipExterior);
Map.Terrain.SetAllRoad(pgt, interiorOnly);
Editor.Terrain.SetAllRoad(pgt, interiorOnly);
ReloadBuildingsTerrain();
System.Media.SystemSounds.Asterisk.Play();
@ -1126,9 +1126,9 @@ private void B_ImportPlacedDesigns_Click(object sender, EventArgs e)
private void Menu_Bulk_Click(object sender, EventArgs e)
{
var editor = new BatchEditor(SpawnLayer.Tiles, ItemEdit.SetItem(new Item()));
var editor = new BatchEditor(Spawn.Tiles, ItemEdit.SetItem(new Item()));
editor.ShowDialog();
SpawnLayer.ClearDanglingExtensions(0, 0, SpawnLayer.TileInfo.TotalWidth, SpawnLayer.TileInfo.TotalHeight);
Spawn.ClearDanglingExtensions(0, 0, Spawn.TileInfo.TotalWidth, Spawn.TileInfo.TotalHeight);
LoadItemGridAcre();
}
@ -1149,5 +1149,5 @@ public interface IItemLayerEditor
void ReloadItems();
ItemEditor ItemProvider { get; }
ItemLayer SpawnLayer { get; }
LayerItem Spawn { get; }
}

View File

@ -8,7 +8,7 @@ namespace NHSE.WinForms;
public static class MapDumpHelper
{
public static bool ImportToLayerAcreSingle(FieldItemLayer layer, int acreIndex, string acre, int layerIndex)
public static bool ImportToLayerAcreSingle(LayerFieldItem layerField, int acreIndex, string acre, int layerIndex)
{
using var ofd = new OpenFileDialog();
ofd.Filter = "New Horizons Field Item Layer (*.nhl)|*.nhl|All files (*.*)|*.*";
@ -19,7 +19,7 @@ public static bool ImportToLayerAcreSingle(FieldItemLayer layer, int acreIndex,
var path = ofd.FileName;
var fi = new FileInfo(path);
int expect = layer.TileInfo.ViewCount * Item.SIZE;
int expect = layerField.TileInfo.ViewCount * Item.SIZE;
if (fi.Length != expect)
{
WinFormsUtil.Error(string.Format(MessageStrings.MsgDataSizeMismatchImport, fi.Length, expect));
@ -27,11 +27,11 @@ public static bool ImportToLayerAcreSingle(FieldItemLayer layer, int acreIndex,
}
var data = File.ReadAllBytes(path);
layer.ImportAcre(acreIndex, data);
layerField.ImportAcre(acreIndex, data);
return true;
}
public static bool ImportToLayerAcreAll(FieldItemLayer layer)
public static bool ImportToLayerAcreAll(LayerFieldItem layerField)
{
using var ofd = new OpenFileDialog();
ofd.Filter = "New Horizons Field Item Layer (*.nhl)|*.nhl|All files (*.*)|*.*";
@ -41,7 +41,7 @@ public static bool ImportToLayerAcreAll(FieldItemLayer layer)
var path = ofd.FileName;
var fi = new FileInfo(path);
var expect = layer.TileInfo.TotalCount * Item.SIZE;
var expect = layerField.TileInfo.TotalCount * Item.SIZE;
if (fi.Length != expect && !FieldItemUpgrade.IsUpdateNeeded(fi.Length, expect))
{
WinFormsUtil.Error(string.Format(MessageStrings.MsgDataSizeMismatchImport, fi.Length, expect));
@ -50,11 +50,11 @@ public static bool ImportToLayerAcreAll(FieldItemLayer layer)
var data = File.ReadAllBytes(path);
FieldItemUpgrade.DetectUpdate(ref data, expect);
layer.ImportAll(data);
layerField.ImportAll(data);
return true;
}
public static void DumpLayerAcreSingle(FieldItemLayer layer, int acreIndex, string acre, int layerIndex)
public static void DumpLayerAcreSingle(LayerFieldItem layerField, int acreIndex, string acre, int layerIndex)
{
using var sfd = new SaveFileDialog();
sfd.Filter = "New Horizons Field Item Layer (*.nhl)|*.nhl|All files (*.*)|*.*";
@ -63,11 +63,11 @@ public static void DumpLayerAcreSingle(FieldItemLayer layer, int acreIndex, stri
return;
var path = sfd.FileName;
var data = layer.DumpAcre(acreIndex);
var data = layerField.DumpAcre(acreIndex);
File.WriteAllBytes(path, data);
}
public static void DumpLayerAcreAll(FieldItemLayer layer)
public static void DumpLayerAcreAll(LayerFieldItem layerField)
{
using var sfd = new SaveFileDialog();
sfd.Filter = "New Horizons Field Item Layer (*.nhl)|*.nhl|All files (*.*)|*.*";
@ -76,11 +76,11 @@ public static void DumpLayerAcreAll(FieldItemLayer layer)
return;
var path = sfd.FileName;
var data = layer.DumpAll();
var data = layerField.DumpAll();
File.WriteAllBytes(path, data);
}
public static bool ImportTerrainAcre(TerrainLayer m, int acreIndex, string acre)
public static bool ImportTerrainAcre(LayerTerrain m, int acreIndex, string acre)
{
using var ofd = new OpenFileDialog();
ofd.Filter = "New Horizons Terrain (*.nht)|*.nht|All files (*.*)|*.*";
@ -103,7 +103,7 @@ public static bool ImportTerrainAcre(TerrainLayer m, int acreIndex, string acre)
return true;
}
public static bool ImportTerrainAll(TerrainLayer m)
public static bool ImportTerrainAll(LayerTerrain m)
{
using var ofd = new OpenFileDialog();
ofd.Filter = "New Horizons Terrain (*.nht)|*.nht|All files (*.*)|*.*";
@ -126,7 +126,7 @@ public static bool ImportTerrainAll(TerrainLayer m)
return true;
}
public static void DumpTerrainAcre(TerrainLayer m, int acreIndex, string acre)
public static void DumpTerrainAcre(LayerTerrain m, int acreIndex, string acre)
{
using var sfd = new SaveFileDialog();
sfd.Filter = "New Horizons Terrain (*.nht)|*.nht|All files (*.*)|*.*";
@ -139,7 +139,7 @@ public static void DumpTerrainAcre(TerrainLayer m, int acreIndex, string acre)
File.WriteAllBytes(path, data);
}
public static void DumpTerrainAll(TerrainLayer m)
public static void DumpTerrainAll(LayerTerrain m)
{
using var sfd = new SaveFileDialog();
sfd.Filter = "New Horizons Terrain (*.nht)|*.nht|All files (*.*)|*.*";

View File

@ -12,7 +12,7 @@ public partial class PlayerHouseEditor : Form
private readonly MainSave SAV;
private readonly IPlayerHouse[] Houses;
private readonly IReadOnlyList<Player> Players;
private RoomItemManager Manager;
private RoomManager Manager;
private const int scale = 24;
private int Index = -1;
@ -25,7 +25,7 @@ public PlayerHouseEditor(IPlayerHouse[] houses, IReadOnlyList<Player> players, M
SAV = sav;
Houses = houses;
Players = players;
Manager = new RoomItemManager(houses[0].GetRoom(0));
Manager = new RoomManager(houses[0].GetRoom(0));
var data = GameInfo.Strings.ItemDataSource;
ItemEdit.Initialize(data, true);
@ -111,7 +111,7 @@ private void B_EditFlags_Click(object sender, EventArgs e)
private void PB_Room_MouseMove(object sender, MouseEventArgs e)
{
var l = CurrentLayer;
var l = Current;
var oldTile = l.GetTile(HoverX, HoverY);
var tile = GetTile(l, e, out var x, out var y);
if (ReferenceEquals(tile, oldTile))
@ -124,7 +124,7 @@ private void PB_Room_MouseMove(object sender, MouseEventArgs e)
private void SetCoordinateText(int x, int y, string name) => L_Coordinates.Text = $"({x:000},{y:000}) = {name}";
private Item GetTile(ItemLayer layer, MouseEventArgs e, out int x, out int y)
private Item GetTile(LayerItem layer, MouseEventArgs e, out int x, out int y)
{
SetHoveredItem(e);
return layer.GetTile(x = HoverX, y = HoverY);
@ -157,12 +157,12 @@ private void ReloadManager(IPlayerHouse house)
if (unsupported.Count != 0)
WinFormsUtil.Alert(MessageStrings.MsgFieldItemUnsupportedLayer2Tile);
var room = house.GetRoom(RoomIndex);
Manager = new RoomItemManager(room);
Manager = new RoomManager(room);
}
private void DrawLayer() => DrawRoom(CurrentLayer);
private void DrawLayer() => DrawRoom(Current);
private void DrawRoom(ItemLayer layer)
private void DrawRoom(LayerItem layer)
{
var w = layer.TileInfo.TotalWidth;
var h = layer.TileInfo.TotalHeight;
@ -186,7 +186,7 @@ private void NUD_Layer_ValueChanged(object sender, EventArgs e)
private void PlayerHouseEditor_Click(object sender, MouseEventArgs e)
{
var tile = GetTile(CurrentLayer, e, out var x, out var y);
var tile = GetTile(Current, e, out var x, out var y);
OmniTile(tile, x, y);
}
@ -211,7 +211,7 @@ private void Menu_View_Click(object sender, EventArgs e)
var x = HoverX;
var y = HoverY;
var tile = CurrentLayer.GetTile(x, y);
var tile = Current.GetTile(x, y);
ViewTile(tile, x, y);
}
@ -220,7 +220,7 @@ private void Menu_Set_Click(object sender, EventArgs e)
var x = HoverX;
var y = HoverY;
var tile = CurrentLayer.GetTile(x, y);
var tile = Current.GetTile(x, y);
SetTile(tile, x, y);
}
@ -229,17 +229,17 @@ private void Menu_Reset_Click(object sender, EventArgs e)
var x = HoverX;
var y = HoverY;
var tile = CurrentLayer.GetTile(x, y);
var tile = Current.GetTile(x, y);
DeleteTile(tile, x, y);
}
private ItemLayer CurrentLayer => Manager.Layers[(int)NUD_Layer.Value - 1];
private LayerItem Current => Manager.Layers[(int)NUD_Layer.Value - 1];
private void ViewTile(Item tile, int x, int y)
{
if (CHK_RedirectExtensionLoad.Checked && tile.IsExtension)
{
var l = CurrentLayer;
var l = Current;
var rx = Math.Max(0, Math.Min(l.TileInfo.TotalWidth - 1, x - tile.ExtensionX));
var ry = Math.Max(0, Math.Min(l.TileInfo.TotalHeight - 1, y - tile.ExtensionY));
var redir = l.GetTile(rx, ry);
@ -257,7 +257,7 @@ private void ViewTile(Item tile)
private void SetTile(Item tile, int x, int y)
{
var l = CurrentLayer;
var l = Current;
var pgt = new Item();
ItemEdit.SetItem(pgt);
var permission = l.IsOccupied(pgt, x, y);
@ -289,9 +289,9 @@ private void DeleteTile(Item tile, int x, int y)
{
x -= tile.ExtensionX;
y -= tile.ExtensionY;
tile = CurrentLayer.GetTile(x, y);
tile = Current.GetTile(x, y);
}
CurrentLayer.DeleteExtensionTiles(tile, x, y);
Current.DeleteExtensionTiles(tile, x, y);
}
tile.Delete();