mirror of
https://github.com/kwsch/NHSE.git
synced 2026-05-10 12:41:37 -05:00
Adds a transparency slider to have the field items fade so you can see the underlying terrain tile. Not really optimized (allocates every time), but it should be fine for now. Make the None item transparent, so that the entire map bleeds through (clear clarity).
459 lines
15 KiB
C#
459 lines
15 KiB
C#
using System;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.IO;
|
|
using System.Windows.Forms;
|
|
using NHSE.Core;
|
|
using NHSE.Sprites;
|
|
|
|
namespace NHSE.WinForms
|
|
{
|
|
public partial class FieldItemEditor : Form
|
|
{
|
|
private readonly MainSave SAV;
|
|
private readonly FieldItemManager Items;
|
|
|
|
private const int MapScale = 1;
|
|
private const int AcreScale = 16;
|
|
|
|
// Cached acre view objects to remove allocation/GC
|
|
private readonly int[] Scale1;
|
|
private readonly int[] ScaleX;
|
|
private readonly Bitmap ScaleAcre;
|
|
|
|
private readonly int[] Map;
|
|
private readonly Bitmap MapReticle;
|
|
|
|
private readonly TerrainManager Terrain;
|
|
|
|
private int X;
|
|
private int Y;
|
|
|
|
public FieldItemEditor(MainSave sav)
|
|
{
|
|
InitializeComponent();
|
|
this.TranslateInterface(GameInfo.CurrentLanguage);
|
|
|
|
SAV = sav;
|
|
Items = new FieldItemManager(SAV.GetFieldItems());
|
|
|
|
var l1 = Items.Layer1;
|
|
Scale1 = new int[l1.GridWidth * l1.GridHeight];
|
|
ScaleX = new int[Scale1.Length * AcreScale * AcreScale];
|
|
ScaleAcre = new Bitmap(l1.GridWidth * AcreScale, l1.GridHeight * AcreScale);
|
|
|
|
Map = new int[l1.MapWidth * l1.MapHeight * MapScale * MapScale];
|
|
MapReticle = new Bitmap(l1.MapWidth * MapScale, l1.MapHeight * MapScale);
|
|
|
|
Terrain = new TerrainManager(sav.GetTerrain());
|
|
PB_Map.BackgroundImage = TerrainSprite.CreateMap(Terrain, 2);
|
|
|
|
foreach (var acre in MapGrid.Acres)
|
|
CB_Acre.Items.Add(acre.Name);
|
|
|
|
PG_Tile.SelectedObject = new FieldItem();
|
|
CB_Acre.SelectedIndex = 0;
|
|
LoadGrid(X, Y);
|
|
}
|
|
|
|
private int AcreIndex => CB_Acre.SelectedIndex;
|
|
|
|
private void ChangeAcre(object sender, EventArgs e) => ChangeViewToAcre(AcreIndex);
|
|
|
|
private void ChangeViewToAcre(int acre)
|
|
{
|
|
Items.Layer1.GetViewAnchorCoordinates(acre, out X, out Y);
|
|
LoadGrid(X, Y);
|
|
}
|
|
|
|
private FieldItemLayer Layer => NUD_Layer.Value == 1 ? Items.Layer1 : Items.Layer2;
|
|
|
|
private void ReloadMap()
|
|
{
|
|
var transparency = TR_Transparency.Value / 100d;
|
|
var t = ((int)(0xFF * transparency) << 24) | 0x00FF_FFFF;
|
|
PB_Map.Image = FieldItemSpriteDrawer.GetBitmapLayer(Layer, X, Y, Map, MapReticle, t);
|
|
}
|
|
|
|
private void LoadGrid(int topX, int topY)
|
|
{
|
|
ReloadGrid(Layer, topX, topY);
|
|
ReloadBackground(topX, topY);
|
|
UpdateArrowVisibility();
|
|
ReloadMap();
|
|
}
|
|
|
|
private void ReloadBackground(int topX, int topY)
|
|
{
|
|
PB_Acre.BackgroundImage = TerrainSprite.GetAcre(topX/2, topY/2, Terrain, AcreScale * 2);
|
|
}
|
|
|
|
private void ReloadGrid(FieldItemLayer layer, int topX, int topY)
|
|
{
|
|
var transparency = TR_Transparency.Value / 100d;
|
|
var t = ((int) (0xFF * transparency) << 24) | 0x00FF_FFFF;
|
|
PB_Acre.Image = FieldItemSpriteDrawer.GetBitmapLayerAcre(layer, topX, topY, AcreScale, Scale1, ScaleX, ScaleAcre, t);
|
|
}
|
|
|
|
private void UpdateArrowVisibility()
|
|
{
|
|
B_Up.Enabled = Y != 0;
|
|
B_Down.Enabled = Y < Layer.MapHeight - Layer.GridHeight;
|
|
B_Left.Enabled = X != 0;
|
|
B_Right.Enabled = X < Layer.MapWidth - Layer.GridWidth;
|
|
}
|
|
|
|
private void PB_Acre_MouseClick(object sender, MouseEventArgs e)
|
|
{
|
|
var tile = GetTile(Layer, e);
|
|
switch (ModifierKeys)
|
|
{
|
|
default: ViewTile(tile); return;
|
|
case Keys.Shift: SetTile(tile); return;
|
|
case Keys.Alt: DeleteTile(tile); return;
|
|
}
|
|
}
|
|
|
|
private int HoverX;
|
|
private int HoverY;
|
|
|
|
private FieldItem GetTile(FieldItemLayer layer, MouseEventArgs e)
|
|
{
|
|
SetHoveredItem(e);
|
|
return layer.GetTile(X + HoverX, Y + HoverY);
|
|
}
|
|
|
|
private void SetHoveredItem(MouseEventArgs e)
|
|
{
|
|
HoverX = e.X / AcreScale;
|
|
HoverY = e.Y / AcreScale;
|
|
}
|
|
|
|
private void PB_Acre_MouseMove(object sender, MouseEventArgs e)
|
|
{
|
|
var oldTile = Layer.GetTile(X + HoverX, Y + HoverY);
|
|
var tile = GetTile(Layer, e);
|
|
if (tile == oldTile)
|
|
return;
|
|
var str = GameInfo.Strings;
|
|
var name = str.GetItemName(tile);
|
|
TT_Hover.SetToolTip(PB_Acre, name);
|
|
}
|
|
|
|
private void ViewTile(FieldItem tile)
|
|
{
|
|
var pgt = (FieldItem)PG_Tile.SelectedObject;
|
|
pgt.CopyFrom(tile);
|
|
PG_Tile.SelectedObject = pgt;
|
|
}
|
|
|
|
private void SetTile(FieldItem tile)
|
|
{
|
|
var pgt = (FieldItem)PG_Tile.SelectedObject;
|
|
tile.CopyFrom(pgt);
|
|
ReloadGrid(Layer, X, Y);
|
|
ReloadMap();
|
|
}
|
|
|
|
private void DeleteTile(FieldItem tile)
|
|
{
|
|
tile.Delete();
|
|
ReloadGrid(Layer, X, Y);
|
|
ReloadMap();
|
|
}
|
|
|
|
private void B_Cancel_Click(object sender, EventArgs e) => Close();
|
|
|
|
private void B_Save_Click(object sender, EventArgs e)
|
|
{
|
|
var all = ArrayUtil.ConcatAll(Items.Layer1.Tiles, Items.Layer2.Tiles);
|
|
SAV.SetFieldItems(all);
|
|
Close();
|
|
}
|
|
|
|
private void Menu_View_Click(object sender, EventArgs e)
|
|
{
|
|
var tile = Layer.GetTile(X + HoverX, Y + HoverY);
|
|
ViewTile(tile);
|
|
}
|
|
|
|
private void Menu_Set_Click(object sender, EventArgs e)
|
|
{
|
|
var tile = Layer.GetTile(X + HoverX, Y + HoverY);
|
|
SetTile(tile);
|
|
}
|
|
|
|
private void Menu_Reset_Click(object sender, EventArgs e)
|
|
{
|
|
var tile = Layer.GetTile(X + HoverX, Y + HoverY);
|
|
DeleteTile(tile);
|
|
}
|
|
|
|
private void B_Up_Click(object sender, EventArgs e)
|
|
{
|
|
if (ModifierKeys != Keys.Shift)
|
|
{
|
|
if (Y != 0)
|
|
LoadGrid(X, Y -= 2);
|
|
return;
|
|
}
|
|
|
|
CB_Acre.SelectedIndex -= MapGrid.AcreWidth;
|
|
}
|
|
|
|
private void B_Left_Click(object sender, EventArgs e)
|
|
{
|
|
if (ModifierKeys != Keys.Shift)
|
|
{
|
|
if (X != 0)
|
|
LoadGrid(X -= 2, Y);
|
|
return;
|
|
}
|
|
|
|
--CB_Acre.SelectedIndex;
|
|
}
|
|
|
|
private void B_Right_Click(object sender, EventArgs e)
|
|
{
|
|
if (ModifierKeys != Keys.Shift)
|
|
{
|
|
if (X != Layer.MapWidth - 2)
|
|
LoadGrid(X += 2, Y);
|
|
return;
|
|
}
|
|
|
|
++CB_Acre.SelectedIndex;
|
|
}
|
|
|
|
private void B_Down_Click(object sender, EventArgs e)
|
|
{
|
|
if (ModifierKeys != Keys.Shift)
|
|
{
|
|
if (Y != Layer.MapHeight - 1)
|
|
LoadGrid(X, ++Y);
|
|
return;
|
|
}
|
|
|
|
CB_Acre.SelectedIndex += MapGrid.AcreWidth;
|
|
}
|
|
|
|
private void B_DumpAcre_Click(object sender, EventArgs e)
|
|
{
|
|
var layer = Layer;
|
|
using var sfd = new SaveFileDialog
|
|
{
|
|
Filter = "New Horizons Field Item Layer (*.nhl)|*.nhl|All files (*.*)|*.*",
|
|
FileName = $"{CB_Acre.Text}-{NUD_Layer.Value}.nhl",
|
|
};
|
|
if (sfd.ShowDialog() != DialogResult.OK)
|
|
return;
|
|
|
|
var path = sfd.FileName;
|
|
var acre = AcreIndex;
|
|
var data = layer.DumpAcre(acre);
|
|
File.WriteAllBytes(path, data);
|
|
}
|
|
|
|
private void B_DumpAllAcres_Click(object sender, EventArgs e)
|
|
{
|
|
var layer = Layer;
|
|
using var sfd = new SaveFileDialog
|
|
{
|
|
Filter = "New Horizons Field Item Layer (*.nhl)|*.nhl|All files (*.*)|*.*",
|
|
FileName = "acres.nhl",
|
|
};
|
|
if (sfd.ShowDialog() != DialogResult.OK)
|
|
return;
|
|
|
|
var path = sfd.FileName;
|
|
var data = layer.DumpAllAcres();
|
|
File.WriteAllBytes(path, data);
|
|
}
|
|
|
|
private void B_ImportAcre_Click(object sender, EventArgs e)
|
|
{
|
|
var layer = Layer;
|
|
using var ofd = new OpenFileDialog
|
|
{
|
|
Filter = "New Horizons Field Item Layer (*.nhl)|*.nhl|All files (*.*)|*.*",
|
|
FileName = $"{CB_Acre.Text}-{NUD_Layer.Value}.nhl",
|
|
};
|
|
if (ofd.ShowDialog() != DialogResult.OK)
|
|
return;
|
|
|
|
var path = ofd.FileName;
|
|
var fi = new FileInfo(path);
|
|
|
|
int expect = layer.AcreTileCount * FieldItem.SIZE;
|
|
if (fi.Length != expect)
|
|
{
|
|
WinFormsUtil.Error(string.Format(MessageStrings.MsgDataSizeMismatchImport, fi.Length, expect));
|
|
return;
|
|
}
|
|
|
|
var data = File.ReadAllBytes(path);
|
|
layer.ImportAcre(AcreIndex, data);
|
|
ChangeViewToAcre(AcreIndex);
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
}
|
|
|
|
private void B_ImportAllAcres_Click(object sender, EventArgs e)
|
|
{
|
|
var layer = Layer;
|
|
using var ofd = new OpenFileDialog
|
|
{
|
|
Filter = "New Horizons Field Item Layer (*.nhl)|*.nhl|All files (*.*)|*.*",
|
|
FileName = "acres.nhl",
|
|
};
|
|
if (ofd.ShowDialog() != DialogResult.OK)
|
|
return;
|
|
|
|
var path = ofd.FileName;
|
|
var fi = new FileInfo(path);
|
|
|
|
int expect = layer.MapTileCount * FieldItem.SIZE;
|
|
if (fi.Length != expect)
|
|
{
|
|
WinFormsUtil.Error(string.Format(MessageStrings.MsgDataSizeMismatchImport, fi.Length, expect));
|
|
return;
|
|
}
|
|
|
|
var data = File.ReadAllBytes(path);
|
|
layer.ImportAllAcres(data);
|
|
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;
|
|
}
|
|
|
|
const string name = "map";
|
|
var bmp = FieldItemSpriteDrawer.GetBitmapLayer(Items.Layer1);
|
|
using var sfd = new SaveFileDialog
|
|
{
|
|
Filter = "png file (*.png)|*.png|All files (*.*)|*.*",
|
|
FileName = $"{name}.png",
|
|
};
|
|
if (sfd.ShowDialog() != DialogResult.OK)
|
|
return;
|
|
|
|
bmp.Save(sfd.FileName, ImageFormat.Png);
|
|
}
|
|
|
|
private void PB_Map_MouseDown(object sender, MouseEventArgs e) => ClickMapAt(e, true);
|
|
|
|
private void ClickMapAt(MouseEventArgs e, bool skipLagCheck)
|
|
{
|
|
var layer = Items.Layer1;
|
|
int mX = e.X;
|
|
int mY = e.Y;
|
|
bool centerReticle = CHK_SnapToAcre.Checked;
|
|
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(X - x);
|
|
var dy = Math.Abs(Y - y);
|
|
if (dx <= delta && dy <= delta && !sameAcre)
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!CHK_SnapToAcre.Checked)
|
|
{
|
|
LoadGrid(X = x, Y = y);
|
|
return;
|
|
}
|
|
|
|
if (!sameAcre)
|
|
CB_Acre.SelectedIndex = acre;
|
|
}
|
|
|
|
private static void GetCursorCoordinates(int mX, int mY, out int x, out int y)
|
|
{
|
|
x = mX / MapScale;
|
|
y = mY / MapScale;
|
|
}
|
|
|
|
private void GetViewAnchorCoordinates(int mX, int mY, out int x, out int y, bool centerReticle)
|
|
{
|
|
var layer = Items.Layer1;
|
|
GetCursorCoordinates(mX, mY, out x, out y);
|
|
layer.GetViewAnchorCoordinates(ref x, ref y, centerReticle);
|
|
}
|
|
|
|
private void PB_Map_MouseMove(object sender, MouseEventArgs e)
|
|
{
|
|
if (e.Button == MouseButtons.Left)
|
|
{
|
|
ClickMapAt(e, false);
|
|
}
|
|
else
|
|
{
|
|
int mX = e.X;
|
|
int mY = e.Y;
|
|
GetCursorCoordinates(mX, mY, out var x, out var y);
|
|
L_Coordinates.Text = $"({x:000},{y:000}) = (0x{x:X2},0x{y:X2})";
|
|
}
|
|
}
|
|
|
|
private void NUD_Layer_ValueChanged(object sender, EventArgs e) => LoadGrid(X, Y);
|
|
|
|
private void Remove(Control sender, Func<int, int, int, int, int> removal)
|
|
{
|
|
bool wholeMap = ModifierKeys == Keys.Shift;
|
|
|
|
string q = string.Format(MessageStrings.MsgFieldItemRemoveAsk, sender.Text);
|
|
var question = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, q);
|
|
if (question != DialogResult.Yes)
|
|
return;
|
|
|
|
var count = wholeMap
|
|
? removal(0, 0, Layer.MapWidth, Layer.MapHeight)
|
|
: removal(X, Y, Layer.GridWidth, Layer.GridHeight);
|
|
|
|
if (count == 0)
|
|
{
|
|
WinFormsUtil.Alert(MessageStrings.MsgFieldItemRemoveNone);
|
|
return;
|
|
}
|
|
LoadGrid(X, Y);
|
|
WinFormsUtil.Alert(string.Format(MessageStrings.MsgFieldItemRemoveCount, count));
|
|
}
|
|
|
|
private void B_RemoveAllWeeds_Click(object sender, EventArgs e) => Remove(B_RemoveAllWeeds, Layer.RemoveAllWeeds);
|
|
private void B_FillHoles_Click(object sender, EventArgs e) => Remove(B_FillHoles, Layer.RemoveAllHoles);
|
|
private void B_RemovePlants_Click(object sender, EventArgs e) => Remove(B_RemovePlants, Layer.RemoveAllPlants);
|
|
private void B_RemoveFences_Click(object sender, EventArgs e) => Remove(B_RemoveFences, Layer.RemoveAllFences);
|
|
private void B_RemoveObjects_Click(object sender, EventArgs e) => Remove(B_RemoveObjects, Layer.RemoveAllObjects);
|
|
private void B_RemoveAll_Click(object sender, EventArgs e) => Remove(B_RemoveAll, Layer.RemoveAll);
|
|
private void B_RemovePlacedItems_Click(object sender, EventArgs e) => Remove(B_RemovePlacedItems, Layer.RemoveAllPlacedItems);
|
|
|
|
private void PG_Tile_PropertyValueChanged(object s, PropertyValueChangedEventArgs e) => PG_Tile.SelectedObject = PG_Tile.SelectedObject;
|
|
|
|
private void TR_Transparency_Scroll(object sender, EventArgs e)
|
|
{
|
|
ReloadGrid(Layer, X, Y);
|
|
ReloadMap();
|
|
}
|
|
}
|
|
}
|