mirror of
https://github.com/kwsch/NHSE.git
synced 2026-03-26 11:34:44 -05:00
1139 lines
38 KiB
C#
1139 lines
38 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Windows.Forms;
|
|
using NHSE.Core;
|
|
using NHSE.Sprites;
|
|
using NHSE.WinForms.Subforms.Map;
|
|
|
|
namespace NHSE.WinForms
|
|
{
|
|
public sealed partial class FieldItemEditor : Form, IItemLayerEditor
|
|
{
|
|
private readonly MainSave SAV;
|
|
|
|
private readonly MapManager Map;
|
|
private readonly MapViewer View;
|
|
|
|
private bool Loading;
|
|
private int SelectedBuildingIndex;
|
|
|
|
private int HoverX;
|
|
private int HoverY;
|
|
private int DragX = -1;
|
|
private int DragY = -1;
|
|
private bool Dragging;
|
|
|
|
public ItemEditor ItemProvider => ItemEdit;
|
|
public ItemLayer SpawnLayer => Map.CurrentLayer;
|
|
|
|
private TerrainBrushEditor? tbeForm;
|
|
|
|
public FieldItemEditor(MainSave sav)
|
|
{
|
|
InitializeComponent();
|
|
this.TranslateInterface(GameInfo.CurrentLanguage);
|
|
|
|
var scale = (PB_Acre.Width - 2) / 32;
|
|
SAV = sav;
|
|
Map = new MapManager(sav);
|
|
View = new MapViewer(Map, scale);
|
|
|
|
Loading = true;
|
|
|
|
LoadComboBoxes();
|
|
LoadBuildings(sav);
|
|
ReloadMapBackground();
|
|
LoadEditors();
|
|
LB_Items.SelectedIndex = 0;
|
|
CB_Acre.SelectedIndex = 0;
|
|
CB_MapAcre.SelectedIndex = 0;
|
|
Loading = false;
|
|
LoadItemGridAcre();
|
|
}
|
|
|
|
private void LoadComboBoxes()
|
|
{
|
|
foreach (var acre in MapGrid.Acres)
|
|
CB_Acre.Items.Add(acre.Name);
|
|
|
|
var exterior = AcreCoordinate.GetGridWithExterior(9, 8);
|
|
foreach (var acre in exterior)
|
|
CB_MapAcre.Items.Add(acre.Name);
|
|
|
|
CB_MapAcreSelect.DisplayMember = nameof(ComboItem.Text);
|
|
CB_MapAcreSelect.ValueMember = nameof(ComboItem.Value);
|
|
CB_MapAcreSelect.DataSource = ComboItemUtil.GetArray<ushort>(typeof(OutsideAcre));
|
|
|
|
NUD_MapAcreTemplateOutside.Value = SAV.OutsideFieldTemplateUniqueId;
|
|
NUD_MapAcreTemplateField.Value = SAV.MainFieldParamUniqueID;
|
|
}
|
|
|
|
private void LoadBuildings(MainSave sav)
|
|
{
|
|
NUD_PlazaX.Value = sav.EventPlazaLeftUpX;
|
|
NUD_PlazaY.Value = sav.EventPlazaLeftUpZ;
|
|
|
|
foreach (var obj in Map.Buildings)
|
|
LB_Items.Items.Add(obj.ToString());
|
|
}
|
|
|
|
private void LoadEditors()
|
|
{
|
|
var data = GameInfo.Strings.ItemDataSource.ToList();
|
|
var field = FieldItemList.Items.Select(z => z.Value).ToList();
|
|
data.Add(field, GameInfo.Strings.InternalNameTranslation);
|
|
ItemEdit.Initialize(data, true);
|
|
PG_TerrainTile.SelectedObject = new TerrainTile();
|
|
}
|
|
|
|
private int AcreIndex => CB_Acre.SelectedIndex;
|
|
|
|
private void ChangeAcre(object sender, EventArgs e)
|
|
{
|
|
ChangeViewToAcre(AcreIndex);
|
|
CB_MapAcre.Text = CB_Acre.Text;
|
|
}
|
|
|
|
private void ChangeViewToAcre(int acre)
|
|
{
|
|
View.SetViewToAcre(acre);
|
|
LoadItemGridAcre();
|
|
}
|
|
|
|
private void LoadItemGridAcre()
|
|
{
|
|
ReloadItems();
|
|
ReloadAcreBackground();
|
|
UpdateArrowVisibility();
|
|
}
|
|
|
|
private int GetItemTransparency() => ((int)(0xFF * TR_Transparency.Value / 100d) << 24) | 0x00FF_FFFF;
|
|
|
|
private void ReloadMapBackground()
|
|
{
|
|
PB_Map.BackgroundImage = View.GetBackgroundTerrain(SelectedBuildingIndex);
|
|
PB_Map.Invalidate(); // background image reassigning to same img doesn't redraw; force it
|
|
}
|
|
|
|
private void ReloadAcreBackground()
|
|
{
|
|
var tbuild = (byte)TR_BuildingTransparency.Value;
|
|
var tterrain = (byte)TR_Terrain.Value;
|
|
PB_Acre.BackgroundImage = View.GetBackgroundAcre(L_Coordinates.Font, tbuild, tterrain, SelectedBuildingIndex);
|
|
PB_Acre.Invalidate(); // background image reassigning to same img doesn't redraw; force it
|
|
}
|
|
|
|
private void ReloadMapItemGrid() => PB_Map.Image = View.GetMapWithReticle(GetItemTransparency());
|
|
|
|
private void ReloadAcreItemGrid() => PB_Acre.Image = View.GetLayerAcre(GetItemTransparency());
|
|
|
|
public void ReloadItems()
|
|
{
|
|
ReloadAcreItemGrid();
|
|
ReloadMapItemGrid();
|
|
}
|
|
|
|
private void ReloadBuildingsTerrain()
|
|
{
|
|
ReloadAcreBackground();
|
|
ReloadMapBackground();
|
|
}
|
|
|
|
private void UpdateArrowVisibility()
|
|
{
|
|
B_Up.Enabled = View.CanUp;
|
|
B_Down.Enabled = View.CanDown;
|
|
B_Left.Enabled = View.CanLeft;
|
|
B_Right.Enabled = View.CanRight;
|
|
}
|
|
|
|
private void PB_Acre_MouseClick(object sender, MouseEventArgs e)
|
|
{
|
|
if (Dragging)
|
|
{
|
|
ResetDrag();
|
|
return;
|
|
}
|
|
|
|
if (RB_Item.Checked)
|
|
OmniTile(e);
|
|
else if (RB_Terrain.Checked)
|
|
OmniTileTerrain(e);
|
|
}
|
|
|
|
private void ResetDrag()
|
|
{
|
|
DragX = -1;
|
|
DragY = -1;
|
|
Dragging = false;
|
|
}
|
|
|
|
private void OmniTile(MouseEventArgs e)
|
|
{
|
|
var tile = GetTile(Map.CurrentLayer, e, out var x, out var y);
|
|
OmniTile(tile, x, y);
|
|
}
|
|
|
|
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);
|
|
if (tbeForm?.brushSelected != true)
|
|
{
|
|
OmniTileTerrain(tile);
|
|
return;
|
|
}
|
|
|
|
if (tbeForm.Slider_thickness.Value <= 1)
|
|
{
|
|
SetTile(tile);
|
|
return;
|
|
}
|
|
|
|
List<TerrainTile> selectedTiles = new();
|
|
int radius = tbeForm.Slider_thickness.Value;
|
|
int threshold = (radius * radius) / 2;
|
|
for (int i = -radius; i < radius; i++)
|
|
{
|
|
for (int j = -radius; j < radius; j++)
|
|
{
|
|
if ((i * i) + (j * j) < threshold)
|
|
selectedTiles.Add(Map.Terrain.GetTile((x / 2) + i, (y / 2) + j));
|
|
}
|
|
}
|
|
|
|
SetTiles(selectedTiles);
|
|
}
|
|
|
|
private void OmniTile(Item tile, int x, int y)
|
|
{
|
|
switch (ModifierKeys)
|
|
{
|
|
default:
|
|
ViewTile(tile, x, y);
|
|
return;
|
|
|
|
case Keys.Alt | Keys.Control:
|
|
case Keys.Alt | Keys.Control | Keys.Shift:
|
|
ReplaceTile(tile, x, y);
|
|
return;
|
|
|
|
case Keys.Shift:
|
|
SetTile(tile, x, y);
|
|
return;
|
|
|
|
case Keys.Alt:
|
|
DeleteTile(tile, x, y);
|
|
return;
|
|
}
|
|
}
|
|
|
|
private void OmniTileTerrain(TerrainTile tile)
|
|
{
|
|
switch (ModifierKeys)
|
|
{
|
|
default:
|
|
ViewTile(tile);
|
|
return;
|
|
|
|
case Keys.Shift | Keys.Control:
|
|
RotateTile(tile);
|
|
return;
|
|
|
|
case Keys.Shift:
|
|
SetTile(tile);
|
|
return;
|
|
|
|
case Keys.Alt:
|
|
DeleteTile(tile);
|
|
return;
|
|
}
|
|
}
|
|
|
|
private Item GetTile(FieldItemLayer layer, MouseEventArgs e, out int x, out int y)
|
|
{
|
|
SetHoveredItem(e);
|
|
return layer.GetTile(x = View.X + HoverX, y = View.Y + HoverY);
|
|
}
|
|
|
|
private void SetHoveredItem(MouseEventArgs e)
|
|
{
|
|
GetAcreCoordinates(e, out HoverX, out HoverY);
|
|
|
|
// Mouse event may fire with a slightly too large x/y; clamp just in case.
|
|
HoverX &= 0x1F;
|
|
HoverY &= 0x1F;
|
|
}
|
|
|
|
private void GetAcreCoordinates(MouseEventArgs e, out int x, out int y)
|
|
{
|
|
x = e.X / View.AcreScale;
|
|
y = e.Y / View.AcreScale;
|
|
}
|
|
|
|
private void PB_Acre_MouseDown(object sender, MouseEventArgs e) => ResetDrag();
|
|
|
|
private void PB_Acre_MouseMove(object sender, MouseEventArgs e)
|
|
{
|
|
var l = Map.CurrentLayer;
|
|
if (e.Button == MouseButtons.Left && CHK_MoveOnDrag.Checked)
|
|
{
|
|
MoveDrag(e);
|
|
return;
|
|
}
|
|
if (e.Button == MouseButtons.Left && tbeForm?.brushSelected == true)
|
|
{
|
|
OmniTileTerrain(e);
|
|
}
|
|
|
|
var oldTile = l.GetTile(View.X + HoverX, View.Y + HoverY);
|
|
var tile = GetTile(l, e, out var x, out var y);
|
|
if (ReferenceEquals(tile, oldTile))
|
|
return;
|
|
var str = GameInfo.Strings;
|
|
var name = str.GetItemName(tile);
|
|
bool active = Map.Items.GetIsActive(NUD_Layer.Value == 0, x, y);
|
|
if (active)
|
|
name = $"{name} [Active]";
|
|
TT_Hover.SetToolTip(PB_Acre, name);
|
|
SetCoordinateText(x, y);
|
|
}
|
|
|
|
private void MoveDrag(MouseEventArgs e)
|
|
{
|
|
GetAcreCoordinates(e, out var nhX, out var nhY);
|
|
|
|
if (DragX == -1)
|
|
{
|
|
DragX = nhX;
|
|
DragY = nhY;
|
|
return;
|
|
}
|
|
|
|
var dX = DragX - nhX;
|
|
var dY = DragY - nhY;
|
|
|
|
if (ModifierKeys == Keys.Control)
|
|
{
|
|
dX *= 2;
|
|
dY *= 2;
|
|
}
|
|
|
|
if ((dX & 1) == 1)
|
|
dX ^= 1;
|
|
if ((dY & 1) == 1)
|
|
dY ^= 1;
|
|
|
|
var aX = Math.Abs(dX);
|
|
var aY = Math.Abs(dY);
|
|
if (aX < 2 && aY < 2)
|
|
return;
|
|
|
|
DragX = nhX;
|
|
DragY = nhY;
|
|
if (!View.SetViewTo(View.X + dX, View.Y + dY))
|
|
return;
|
|
|
|
Dragging = true;
|
|
LoadItemGridAcre();
|
|
}
|
|
|
|
private void ViewTile(Item tile, int x, int y)
|
|
{
|
|
if (CHK_RedirectExtensionLoad.Checked && tile.IsExtension)
|
|
{
|
|
var l = Map.CurrentLayer;
|
|
var rx = Math.Max(0, Math.Min(l.MaxWidth - 1, x - tile.ExtensionX));
|
|
var ry = Math.Max(0, Math.Min(l.MaxHeight - 1, y - tile.ExtensionY));
|
|
var redir = l.GetTile(rx, ry);
|
|
if (redir.IsRoot && redir.ItemId == tile.ExtensionItemId)
|
|
tile = redir;
|
|
}
|
|
|
|
ViewTile(tile);
|
|
}
|
|
|
|
private void ViewTile(Item tile)
|
|
{
|
|
ItemEdit.LoadItem(tile);
|
|
TC_Editor.SelectedTab = Tab_Item;
|
|
}
|
|
|
|
private void ViewTile(TerrainTile tile)
|
|
{
|
|
var pgt = (TerrainTile)PG_TerrainTile.SelectedObject;
|
|
pgt.CopyFrom(tile);
|
|
PG_TerrainTile.SelectedObject = pgt;
|
|
TC_Editor.SelectedTab = Tab_Terrain;
|
|
}
|
|
|
|
private void SetTile(Item tile, int x, int y)
|
|
{
|
|
var l = Map.CurrentLayer;
|
|
var pgt = new Item();
|
|
ItemEdit.SetItem(pgt);
|
|
|
|
if (pgt.IsFieldItem && CHK_FieldItemSnap.Checked)
|
|
{
|
|
// coordinates must be even (not odd-half)
|
|
x &= 0xFFFE;
|
|
y &= 0xFFFE;
|
|
tile = l.GetTile(x, y);
|
|
}
|
|
|
|
var permission = l.IsOccupied(pgt, x, y);
|
|
switch (permission)
|
|
{
|
|
case PlacedItemPermission.OutOfBounds:
|
|
case PlacedItemPermission.Collision when CHK_NoOverwrite.Checked:
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
return;
|
|
}
|
|
|
|
// Clean up original placed data
|
|
if (tile.IsRoot && CHK_AutoExtension.Checked)
|
|
l.DeleteExtensionTiles(tile, x, y);
|
|
|
|
// Set new placed data
|
|
if (pgt.IsRoot && CHK_AutoExtension.Checked)
|
|
l.SetExtensionTiles(pgt, x, y);
|
|
tile.CopyFrom(pgt);
|
|
|
|
ReloadItems();
|
|
}
|
|
|
|
private void ReplaceTile(Item tile, int x, int y)
|
|
{
|
|
var l = Map.CurrentLayer;
|
|
var pgt = new Item();
|
|
ItemEdit.SetItem(pgt);
|
|
|
|
if (pgt.IsFieldItem && CHK_FieldItemSnap.Checked)
|
|
{
|
|
// coordinates must be even (not odd-half)
|
|
x &= 0xFFFE;
|
|
y &= 0xFFFE;
|
|
tile = l.GetTile(x, y);
|
|
}
|
|
|
|
var permission = l.IsOccupied(pgt, x, y);
|
|
switch (permission)
|
|
{
|
|
case PlacedItemPermission.OutOfBounds:
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
return;
|
|
}
|
|
|
|
bool wholeMap = (ModifierKeys & Keys.Shift) != 0;
|
|
var copy = new Item(tile.RawValue);
|
|
var count = View.ReplaceFieldItems(copy, pgt, wholeMap);
|
|
if (count == 0)
|
|
{
|
|
WinFormsUtil.Alert(MessageStrings.MsgFieldItemModifyNone);
|
|
return;
|
|
}
|
|
LoadItemGridAcre();
|
|
WinFormsUtil.Alert(string.Format(MessageStrings.MsgFieldItemModifyCount, count));
|
|
}
|
|
|
|
private void RotateTile(TerrainTile tile)
|
|
{
|
|
bool rotated = tile.Rotate();
|
|
if (!rotated)
|
|
{
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
return;
|
|
}
|
|
ReloadBuildingsTerrain();
|
|
}
|
|
|
|
private void SetTile(TerrainTile tile)
|
|
{
|
|
var pgt = (TerrainTile)PG_TerrainTile.SelectedObject;
|
|
if (tbeForm?.randomizeVariation == true)
|
|
{
|
|
switch (pgt.UnitModel)
|
|
{
|
|
case TerrainUnitModel.Cliff5B:
|
|
case TerrainUnitModel.River5B:
|
|
Random rand = new();
|
|
pgt.Variation = (ushort)rand.Next(4);
|
|
break;
|
|
}
|
|
}
|
|
|
|
tile.CopyFrom(pgt);
|
|
|
|
ReloadBuildingsTerrain();
|
|
}
|
|
|
|
private void SetTiles(IEnumerable<TerrainTile> tiles)
|
|
{
|
|
var pgt = (TerrainTile)PG_TerrainTile.SelectedObject;
|
|
foreach (TerrainTile tile in tiles)
|
|
{
|
|
tile.CopyFrom(pgt);
|
|
}
|
|
|
|
ReloadBuildingsTerrain();
|
|
}
|
|
|
|
private void DeleteTile(Item tile, int x, int y)
|
|
{
|
|
if (CHK_AutoExtension.Checked)
|
|
{
|
|
if (!tile.IsRoot)
|
|
{
|
|
x -= tile.ExtensionX;
|
|
y -= tile.ExtensionY;
|
|
tile = Map.CurrentLayer.GetTile(x, y);
|
|
}
|
|
Map.CurrentLayer.DeleteExtensionTiles(tile, x, y);
|
|
}
|
|
|
|
tile.Delete();
|
|
ReloadItems();
|
|
}
|
|
|
|
private void DeleteTile(TerrainTile tile)
|
|
{
|
|
tile.Clear();
|
|
ReloadBuildingsTerrain();
|
|
}
|
|
|
|
private void B_Cancel_Click(object sender, EventArgs e) => Close();
|
|
|
|
private void B_Save_Click(object sender, EventArgs e)
|
|
{
|
|
var unsupported = Map.Items.GetUnsupportedTiles();
|
|
if (unsupported.Count != 0)
|
|
{
|
|
var err = MessageStrings.MsgFieldItemUnsupportedLayer2Tile;
|
|
var ask = MessageStrings.MsgAskContinue;
|
|
var prompt = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, err, ask);
|
|
if (prompt != DialogResult.Yes)
|
|
return;
|
|
}
|
|
|
|
Map.Items.Save();
|
|
SAV.SetTerrainTiles(Map.Terrain.Tiles);
|
|
|
|
SAV.SetAcreBytes(Map.Terrain.BaseAcres);
|
|
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();
|
|
}
|
|
|
|
private void Menu_View_Click(object sender, EventArgs e)
|
|
{
|
|
var x = View.X + HoverX;
|
|
var y = View.Y + HoverY;
|
|
|
|
if (RB_Item.Checked)
|
|
{
|
|
var tile = Map.CurrentLayer.GetTile(x, y);
|
|
ViewTile(tile, x, y);
|
|
}
|
|
else if (RB_Terrain.Checked)
|
|
{
|
|
var tile = Map.Terrain.GetTile(x / 2, y / 2);
|
|
ViewTile(tile);
|
|
}
|
|
}
|
|
|
|
private void Menu_Set_Click(object sender, EventArgs e)
|
|
{
|
|
var x = View.X + HoverX;
|
|
var y = View.Y + HoverY;
|
|
|
|
if (RB_Item.Checked)
|
|
{
|
|
var tile = Map.CurrentLayer.GetTile(x, y);
|
|
SetTile(tile, x, y);
|
|
}
|
|
else if (RB_Terrain.Checked)
|
|
{
|
|
var tile = Map.Terrain.GetTile(x / 2, y / 2);
|
|
SetTile(tile);
|
|
}
|
|
}
|
|
|
|
private void Menu_Reset_Click(object sender, EventArgs e)
|
|
{
|
|
var x = View.X + HoverX;
|
|
var y = View.Y + HoverY;
|
|
|
|
if (RB_Item.Checked)
|
|
{
|
|
var tile = Map.CurrentLayer.GetTile(x, y);
|
|
DeleteTile(tile, x, y);
|
|
}
|
|
else if (RB_Terrain.Checked)
|
|
{
|
|
var tile = Map.Terrain.GetTile(x / 2, y / 2);
|
|
DeleteTile(tile);
|
|
}
|
|
}
|
|
|
|
private bool hasActivate = true;
|
|
|
|
private void CM_Click_Opening(object sender, System.ComponentModel.CancelEventArgs e)
|
|
{
|
|
if (!RB_Item.Checked)
|
|
{
|
|
if (hasActivate)
|
|
CM_Click.Items.Remove(Menu_Activate);
|
|
hasActivate = false;
|
|
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";
|
|
CM_Click.Items.Add(Menu_Activate);
|
|
hasActivate = true;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
private void B_Up_Click(object sender, EventArgs e)
|
|
{
|
|
if (ModifierKeys == Keys.Shift)
|
|
CB_Acre.SelectedIndex = Math.Max(0, CB_Acre.SelectedIndex - MapGrid.AcreWidth);
|
|
else if (View.ArrowUp())
|
|
LoadItemGridAcre();
|
|
}
|
|
|
|
private void B_Left_Click(object sender, EventArgs e)
|
|
{
|
|
if (ModifierKeys == Keys.Shift)
|
|
CB_Acre.SelectedIndex = Math.Max(0, CB_Acre.SelectedIndex - 1);
|
|
else if (View.ArrowLeft())
|
|
LoadItemGridAcre();
|
|
}
|
|
|
|
private void B_Right_Click(object sender, EventArgs e)
|
|
{
|
|
if (ModifierKeys == Keys.Shift)
|
|
CB_Acre.SelectedIndex = Math.Min(CB_Acre.SelectedIndex + 1, CB_Acre.Items.Count - 1);
|
|
else if (View.ArrowRight())
|
|
LoadItemGridAcre();
|
|
}
|
|
|
|
private void B_Down_Click(object sender, EventArgs e)
|
|
{
|
|
if (ModifierKeys == Keys.Shift)
|
|
CB_Acre.SelectedIndex = Math.Min(CB_Acre.SelectedIndex + MapGrid.AcreWidth, 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_DumpAllAcres_Click(object sender, EventArgs e) => MapDumpHelper.DumpLayerAcreAll(Map.CurrentLayer);
|
|
|
|
private void B_ImportAcre_Click(object sender, EventArgs e)
|
|
{
|
|
var layer = Map.CurrentLayer;
|
|
if (!MapDumpHelper.ImportToLayerAcreSingle(layer, AcreIndex, CB_Acre.Text, (int)NUD_Layer.Value))
|
|
return;
|
|
ChangeViewToAcre(AcreIndex);
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
}
|
|
|
|
private void B_ImportAllAcres_Click(object sender, EventArgs e)
|
|
{
|
|
if (!MapDumpHelper.ImportToLayerAcreAll(Map.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_ImportBuildings_Click(object sender, EventArgs e)
|
|
{
|
|
if (!MapDumpHelper.ImportBuildings(Map.Buildings))
|
|
return;
|
|
|
|
for (int i = 0; i < Map.Buildings.Count; i++)
|
|
LB_Items.Items[i] = Map.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_DumpTerrainAll_Click(object sender, EventArgs e) => MapDumpHelper.DumpTerrainAll(Map.Terrain);
|
|
|
|
private void B_ImportTerrainAcre_Click(object sender, EventArgs e)
|
|
{
|
|
if (!MapDumpHelper.ImportTerrainAcre(Map.Terrain, AcreIndex, CB_Acre.Text))
|
|
return;
|
|
ChangeViewToAcre(AcreIndex);
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
}
|
|
|
|
private void B_ImportTerrainAll_Click(object sender, EventArgs e)
|
|
{
|
|
if (!MapDumpHelper.ImportTerrainAll(Map.Terrain))
|
|
return;
|
|
ChangeViewToAcre(AcreIndex);
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
}
|
|
|
|
private void Menu_SavePNG_Click(object sender, EventArgs e)
|
|
{
|
|
var pb = WinFormsUtil.GetUnderlyingControl<PictureBox>(sender);
|
|
if (pb?.Image == null)
|
|
{
|
|
WinFormsUtil.Alert(MessageStrings.MsgNoPictureLoaded);
|
|
return;
|
|
}
|
|
|
|
CM_Picture.Close(ToolStripDropDownCloseReason.CloseCalled);
|
|
|
|
const string name = "map";
|
|
using var sfd = new SaveFileDialog
|
|
{
|
|
Filter = "png file (*.png)|*.png|All files (*.*)|*.*",
|
|
FileName = $"{name}.png",
|
|
};
|
|
if (sfd.ShowDialog() != DialogResult.OK)
|
|
return;
|
|
|
|
if (!Menu_SavePNGTerrain.Checked)
|
|
{
|
|
PB_Map.Image.Save(sfd.FileName, ImageFormat.Png);
|
|
}
|
|
else if (!Menu_SavePNGItems.Checked)
|
|
{
|
|
PB_Map.BackgroundImage.Save(sfd.FileName, ImageFormat.Png);
|
|
}
|
|
else
|
|
{
|
|
var img = (Bitmap)PB_Map.BackgroundImage.Clone();
|
|
using var gfx = Graphics.FromImage(img);
|
|
gfx.DrawImage(PB_Map.Image, new Point(0, 0));
|
|
img.Save(sfd.FileName, ImageFormat.Png);
|
|
}
|
|
}
|
|
|
|
private void CM_Picture_Closing(object sender, ToolStripDropDownClosingEventArgs e)
|
|
{
|
|
if (e.CloseReason == ToolStripDropDownCloseReason.ItemClicked && sender != Menu_SavePNG)
|
|
e.Cancel = true;
|
|
}
|
|
|
|
private void PB_Map_MouseDown(object sender, MouseEventArgs e)
|
|
{
|
|
if (e.Button != MouseButtons.Left)
|
|
return;
|
|
ClickMapAt(e, true);
|
|
}
|
|
|
|
private void ClickMapAt(MouseEventArgs e, bool skipLagCheck)
|
|
{
|
|
var layer = Map.Items.Layer1;
|
|
int mX = e.X;
|
|
int mY = e.Y;
|
|
bool centerReticle = CHK_SnapToAcre.Checked;
|
|
View.GetViewAnchorCoordinates(mX, mY, out var x, out var y, centerReticle);
|
|
x &= 0xFFFE;
|
|
y &= 0xFFFE;
|
|
|
|
var acre = layer.GetAcre(x, y);
|
|
bool sameAcre = AcreIndex == acre;
|
|
if (!skipLagCheck)
|
|
{
|
|
if (CHK_SnapToAcre.Checked)
|
|
{
|
|
if (sameAcre)
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
const int delta = 0; // disabled = 0
|
|
var dx = Math.Abs(View.X - x);
|
|
var dy = Math.Abs(View.Y - y);
|
|
if (dx <= delta && dy <= delta && !sameAcre)
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!CHK_SnapToAcre.Checked)
|
|
{
|
|
if (View.SetViewTo(x, y))
|
|
LoadItemGridAcre();
|
|
return;
|
|
}
|
|
|
|
if (!sameAcre)
|
|
CB_Acre.SelectedIndex = acre;
|
|
}
|
|
|
|
private void PB_Map_MouseMove(object sender, MouseEventArgs e)
|
|
{
|
|
if (e.Button == MouseButtons.Left)
|
|
{
|
|
ClickMapAt(e, false);
|
|
}
|
|
else if (e.Button == MouseButtons.None)
|
|
{
|
|
View.GetCursorCoordinates(e.X, e.Y, out var x, out var y);
|
|
SetCoordinateText(x, y);
|
|
}
|
|
}
|
|
|
|
private void SetCoordinateText(int x, int y) => L_Coordinates.Text = $"({x:000},{y:000}) = (0x{x:X2},0x{y:X2})";
|
|
|
|
private void NUD_Layer_ValueChanged(object sender, EventArgs e)
|
|
{
|
|
Map.MapLayer = (int)NUD_Layer.Value - 1;
|
|
LoadItemGridAcre();
|
|
}
|
|
|
|
private void Remove(ToolStripItem sender, Func<int, int, int, int, int> removal)
|
|
{
|
|
bool wholeMap = (ModifierKeys & Keys.Shift) != 0;
|
|
|
|
string q = string.Format(MessageStrings.MsgFieldItemRemoveAsk, sender.Text);
|
|
var question = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, q);
|
|
if (question != DialogResult.Yes)
|
|
return;
|
|
|
|
int count = View.ModifyFieldItems(removal, wholeMap);
|
|
|
|
if (count == 0)
|
|
{
|
|
WinFormsUtil.Alert(MessageStrings.MsgFieldItemRemoveNone);
|
|
return;
|
|
}
|
|
LoadItemGridAcre();
|
|
WinFormsUtil.Alert(string.Format(MessageStrings.MsgFieldItemRemoveCount, count));
|
|
}
|
|
|
|
private void Modify(ToolStripItem sender, Func<int, int, int, int, int> action)
|
|
{
|
|
bool wholeMap = (ModifierKeys & Keys.Shift) != 0;
|
|
|
|
string q = string.Format(MessageStrings.MsgFieldItemModifyAsk, sender.Text);
|
|
var question = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, q);
|
|
if (question != DialogResult.Yes)
|
|
return;
|
|
|
|
int count = View.ModifyFieldItems(action, wholeMap);
|
|
|
|
if (count == 0)
|
|
{
|
|
WinFormsUtil.Alert(MessageStrings.MsgFieldItemModifyNone);
|
|
return;
|
|
}
|
|
LoadItemGridAcre();
|
|
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_RemoveAllWeeds_Click(object sender, EventArgs e) => Remove(B_RemoveAllWeeds, Map.CurrentLayer.RemoveAllWeeds);
|
|
|
|
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 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
|
|
|
|
private void B_Help_Click(object sender, EventArgs e)
|
|
{
|
|
using var form = new BuildingHelp();
|
|
form.ShowDialog();
|
|
}
|
|
|
|
private void NUD_PlazaX_ValueChanged(object sender, EventArgs e)
|
|
{
|
|
if (Loading)
|
|
return;
|
|
Map.PlazaX = (uint)NUD_PlazaX.Value;
|
|
ReloadBuildingsTerrain();
|
|
}
|
|
|
|
private void NUD_PlazaY_ValueChanged(object sender, EventArgs e)
|
|
{
|
|
if (Loading)
|
|
return;
|
|
Map.PlazaY = (uint)NUD_PlazaY.Value;
|
|
ReloadBuildingsTerrain();
|
|
}
|
|
|
|
private void LB_Items_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
if (LB_Items.SelectedIndex < 0)
|
|
return;
|
|
LoadIndex(LB_Items.SelectedIndex);
|
|
|
|
// View location snap has changed the view. Reload everything
|
|
LoadItemGridAcre();
|
|
ReloadMapBackground();
|
|
}
|
|
|
|
private void LoadIndex(int index)
|
|
{
|
|
Loading = true;
|
|
SelectedBuildingIndex = index;
|
|
var b = Map.Buildings[index];
|
|
NUD_BuildingType.Value = (int)b.BuildingType;
|
|
NUD_X.Value = b.X;
|
|
NUD_Y.Value = b.Y;
|
|
NUD_Angle.Value = b.Angle;
|
|
NUD_Bit.Value = b.Bit;
|
|
NUD_Type.Value = b.Type;
|
|
NUD_TypeArg.Value = b.TypeArg;
|
|
NUD_UniqueID.Value = b.UniqueID;
|
|
Loading = false;
|
|
|
|
// -32 for relative offset on map (buildings can be placed on the exterior ocean acres)
|
|
// -16 to put it in the center of the view
|
|
const int shift = 48;
|
|
var x = (b.X - shift) & 0xFFFE;
|
|
var y = (b.Y - shift) & 0xFFFE;
|
|
View.SetViewTo(x, y);
|
|
}
|
|
|
|
private void NUD_BuildingType_ValueChanged(object sender, EventArgs e)
|
|
{
|
|
if (Loading || sender is not NumericUpDown n)
|
|
return;
|
|
|
|
var b = Map.Buildings[SelectedBuildingIndex];
|
|
if (sender == NUD_BuildingType)
|
|
b.BuildingType = (BuildingType)n.Value;
|
|
else if (sender == NUD_X)
|
|
b.X = (ushort)n.Value;
|
|
else if (sender == NUD_Y)
|
|
b.Y = (ushort)n.Value;
|
|
else if (sender == NUD_Angle)
|
|
b.Angle = (byte)n.Value;
|
|
else if (sender == NUD_Bit)
|
|
b.Bit = (sbyte)n.Value;
|
|
else if (sender == NUD_Type)
|
|
b.Type = (ushort)n.Value;
|
|
else if (sender == NUD_TypeArg)
|
|
b.TypeArg = (byte)n.Value;
|
|
else if (sender == NUD_UniqueID)
|
|
b.UniqueID = (ushort)n.Value;
|
|
|
|
LB_Items.Items[SelectedBuildingIndex] = Map.Buildings[SelectedBuildingIndex].ToString();
|
|
ReloadBuildingsTerrain();
|
|
}
|
|
|
|
#endregion Buildings
|
|
|
|
#region Acres
|
|
|
|
private void CB_MapAcre_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
var acre = Map.Terrain.BaseAcres[CB_MapAcre.SelectedIndex * 2];
|
|
CB_MapAcreSelect.SelectedValue = (int)acre;
|
|
|
|
// Jump view if available
|
|
if (CB_Acre.Items.OfType<string>().Any(z => z == CB_MapAcre.Text))
|
|
CB_Acre.Text = CB_MapAcre.Text;
|
|
}
|
|
|
|
private void CB_MapAcreSelect_SelectedValueChanged(object sender, EventArgs e)
|
|
{
|
|
if (Loading)
|
|
return;
|
|
|
|
var index = CB_MapAcre.SelectedIndex;
|
|
var value = WinFormsUtil.GetIndex(CB_MapAcreSelect);
|
|
|
|
var oldValue = Map.Terrain.BaseAcres[index * 2];
|
|
if (value == oldValue)
|
|
return;
|
|
Map.Terrain.BaseAcres[index * 2] = (byte)value;
|
|
ReloadBuildingsTerrain();
|
|
}
|
|
|
|
private void B_DumpMapAcres_Click(object sender, EventArgs e)
|
|
{
|
|
if (!MapDumpHelper.DumpMapAcresAll(Map.Terrain.BaseAcres))
|
|
return;
|
|
ReloadBuildingsTerrain();
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
}
|
|
|
|
private void B_ImportMapAcres_Click(object sender, EventArgs e)
|
|
{
|
|
if (!MapDumpHelper.ImportMapAcresAll(Map.Terrain.BaseAcres))
|
|
return;
|
|
ReloadBuildingsTerrain();
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
}
|
|
|
|
#endregion Acres
|
|
|
|
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)
|
|
t.Elevation = 0;
|
|
ReloadBuildingsTerrain();
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
}
|
|
|
|
private void B_SetAllTerrain_Click(object sender, EventArgs e)
|
|
{
|
|
if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MessageStrings.MsgTerrainSetAll))
|
|
return;
|
|
|
|
var pgt = (TerrainTile)PG_TerrainTile.SelectedObject;
|
|
bool interiorOnly = DialogResult.Yes == WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MessageStrings.MsgTerrainSetAllSkipExterior);
|
|
Map.Terrain.SetAll(pgt, interiorOnly);
|
|
|
|
ReloadBuildingsTerrain();
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
}
|
|
|
|
private void B_SetAllRoadTiles_Click(object sender, EventArgs e)
|
|
{
|
|
if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MessageStrings.MsgTerrainSetAll))
|
|
return;
|
|
|
|
var pgt = (TerrainTile)PG_TerrainTile.SelectedObject;
|
|
bool interiorOnly = DialogResult.Yes == WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MessageStrings.MsgTerrainSetAllSkipExterior);
|
|
Map.Terrain.SetAllRoad(pgt, interiorOnly);
|
|
|
|
ReloadBuildingsTerrain();
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
}
|
|
|
|
private void B_ClearPlacedDesigns_Click(object sender, EventArgs e)
|
|
{
|
|
MapManager.ClearDesignTiles(SAV);
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
}
|
|
|
|
private void B_ExportPlacedDesigns_Click(object sender, EventArgs e)
|
|
{
|
|
using var sfd = new SaveFileDialog
|
|
{
|
|
Filter = "nhmd file (*.nhmd)|*.nhmd",
|
|
FileName = "Island MyDesignMap.nhmd",
|
|
};
|
|
if (sfd.ShowDialog() != DialogResult.OK)
|
|
return;
|
|
|
|
string path = sfd.FileName;
|
|
var tiles = MapManager.ExportDesignTiles(SAV);
|
|
File.WriteAllBytes(path, tiles);
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
}
|
|
|
|
private void B_ImportPlacedDesigns_Click(object sender, EventArgs e)
|
|
{
|
|
using var ofd = new OpenFileDialog
|
|
{
|
|
Filter = "nhmd file (*.nhmd)|*.nhmd",
|
|
FileName = "Island MyDesignMap.nhmd",
|
|
};
|
|
if (ofd.ShowDialog() != DialogResult.OK)
|
|
return;
|
|
|
|
string path = ofd.FileName;
|
|
var tiles = File.ReadAllBytes(path);
|
|
MapManager.ImportDesignTiles(SAV, tiles);
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
}
|
|
|
|
private void Menu_Spawn_Click(object sender, EventArgs e) => new BulkSpawn(this, View.X, View.Y).ShowDialog();
|
|
|
|
private void Menu_Bulk_Click(object sender, EventArgs e)
|
|
{
|
|
var editor = new BatchEditor(SpawnLayer.Tiles, ItemEdit.SetItem(new Item()));
|
|
editor.ShowDialog();
|
|
SpawnLayer.ClearDanglingExtensions(0, 0, SpawnLayer.MaxWidth, SpawnLayer.MaxHeight);
|
|
LoadItemGridAcre();
|
|
}
|
|
|
|
private void B_TerrainBrush_Click(object sender, EventArgs e)
|
|
{
|
|
tbeForm = new TerrainBrushEditor(PG_TerrainTile, this);
|
|
tbeForm.Show();
|
|
}
|
|
|
|
private void FieldItemEditor_FormClosed(object sender, FormClosedEventArgs e)
|
|
{
|
|
tbeForm?.Close();
|
|
}
|
|
}
|
|
|
|
public interface IItemLayerEditor
|
|
{
|
|
void ReloadItems();
|
|
|
|
ItemEditor ItemProvider { get; }
|
|
ItemLayer SpawnLayer { get; }
|
|
}
|
|
} |