NHSE/NHSE.WinForms/Subforms/Map/PlayerHouseEditor.cs
Kurt b88c518d5c
Update FieldItemEditor for 3.0.0 (#716)
Updates the Field Item Editor to render layers based on the entire map, and the per-patch positioning of each layer.
Import/export will gracefully handle upgrade/downgrade, and viewport import/export will gracefully update tiles rather than a per-acre basis.

Performance has also been slightly improved; no allocation is done anymore when updating the image.
2026-01-25 16:55:38 -06:00

324 lines
9.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using NHSE.Core;
using NHSE.Sprites;
namespace NHSE.WinForms;
public partial class PlayerHouseEditor : Form
{
private readonly MainSave SAV;
private readonly IPlayerHouse[] Houses;
private readonly IReadOnlyList<Player> Players;
private RoomManager Manager;
private const int scale = 24;
private int Index = -1;
private int RoomIndex = -1;
public PlayerHouseEditor(IPlayerHouse[] houses, IReadOnlyList<Player> players, MainSave sav, int index)
{
InitializeComponent();
this.TranslateInterface(GameInfo.CurrentLanguage);
SAV = sav;
Houses = houses;
Players = players;
Manager = new RoomManager(houses[0].GetRoom(0));
var data = GameInfo.Strings.ItemDataSource;
ItemEdit.Initialize(data, true);
DialogResult = DialogResult.Cancel;
for (var i = 0; i < Houses.Length; i++)
{
var obj = Houses[i];
LB_Items.Items.Add(GetHouseSummary(players, obj, i));
}
LB_Items.SelectedIndex = index;
}
private void B_Cancel_Click(object sender, EventArgs e) => Close();
private void B_Save_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.OK;
SaveRoom();
Close();
}
private void SaveRoom()
{
if (RoomIndex < 0)
return;
Manager.Save();
var house = Houses[Index];
house.SetRoom(RoomIndex, Manager.Room);
}
private void LB_Items_SelectedIndexChanged(object sender, EventArgs e)
{
if (LB_Items.SelectedIndex < 0)
return;
SaveRoom();
var house = Houses[Index = LB_Items.SelectedIndex];
PG_Item.SelectedObject = house;
ChangeRoom(house);
}
private void PG_Item_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
{
LB_Items.Items[Index] = GetHouseSummary(Players, Houses[Index], Index);
}
public static string GetHouseSummary(IReadOnlyList<Player> players, IPlayerHouse house, int index)
{
var houseName = index >= players.Count ? $"House {index}" : $"{players[index].Personal.PlayerName}'s House";
return $"{houseName} (lv {house.HouseLevel})";
}
private void B_DumpHouse_Click(object sender, EventArgs e)
{
MiscDumpHelper.DumpHouse(Players, Houses, Index, ModifierKeys == Keys.Shift);
}
private void B_LoadHouse_Click(object sender, EventArgs e)
{
if (!MiscDumpHelper.LoadHouse(SAV.Offsets, Players, Houses, Index))
return;
RoomIndex = -1;
var house = Houses[Index];
PG_Item.SelectedObject = house;
ChangeRoom(house);
}
private void B_EditFlags_Click(object sender, EventArgs e)
{
var flags = Houses[Index].EventFlags;
var edit = flags.ToArray();
using var editor = new PlayerHouseFlagEditor(edit);
if (editor.ShowDialog() == DialogResult.OK)
edit.CopyTo(flags);
}
private int HoverX;
private int HoverY;
private void PB_Room_MouseMove(object sender, MouseEventArgs e)
{
var l = Current;
var oldTile = l.GetTile(HoverX, 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);
TT_Hover.SetToolTip(PB_Room, name);
SetCoordinateText(x, y, name);
}
private void SetCoordinateText(int x, int y, string name) => L_Coordinates.Text = $"({x:000},{y:000}) = {name}";
private Item GetTile(LayerItem layer, MouseEventArgs e, out int x, out int y)
{
SetHoveredItem(e);
return layer.GetTile(x = HoverX, y = HoverY);
}
private void SetHoveredItem(MouseEventArgs e)
{
GetCoordinates(e, out HoverX, out HoverY);
// Mouse event may fire with a slightly too large x/y; clamp just in case.
Manager.Layers[0].TileInfo.ClampInside(ref HoverX, ref HoverY);
}
private static void GetCoordinates(MouseEventArgs e, out int x, out int y)
{
x = e.X / scale;
y = e.Y / scale;
}
private void ChangeRoom(IPlayerHouse house)
{
RoomIndex = (int) NUD_Room.Value - 1;
ReloadManager(house);
DrawLayer();
}
private void ReloadManager(IPlayerHouse house)
{
var unsupported = Manager.GetUnsupportedTiles();
if (unsupported.Count != 0)
WinFormsUtil.Alert(MessageStrings.MsgFieldItemUnsupportedLayer2Tile);
var room = house.GetRoom(RoomIndex);
Manager = new RoomManager(room);
}
private void DrawLayer() => DrawRoom(Current);
private void DrawRoom(LayerItem layer)
{
var w = layer.TileInfo.TotalWidth;
var h = layer.TileInfo.TotalHeight;
Span<int> scale1 = stackalloc int[w * h];
var scaleX = new int[scale * scale * scale1.Length];
var bmp = new Bitmap(scale * w, scale * h);
// 10x10 items (2x2 tiles per item)
var cfg = LayerPositionConfig.Create(1, 1, 20, 1, 0, 0);
ItemLayerSprite.LoadViewport(bmp, layer, cfg, 0, 0, scale1, scaleX, scale, gridlineColor: 0x7F000000);
PB_Room.Image = bmp;
}
private void NUD_Room_ValueChanged(object sender, EventArgs e)
{
ChangeRoom(Houses[Index]);
}
private void NUD_Layer_ValueChanged(object sender, EventArgs e)
{
DrawLayer();
}
#region Item Editing
private void PlayerHouseEditor_Click(object sender, MouseEventArgs e)
{
var tile = GetTile(Current, e, out var x, out var y);
OmniTile(tile, x, y);
}
private void OmniTile(Item tile, int x, int y)
{
switch (ModifierKeys)
{
default:
ViewTile(tile, x, y);
return;
case Keys.Shift:
SetTile(tile, x, y);
return;
case Keys.Alt:
DeleteTile(tile, x, y);
return;
}
}
private void Menu_View_Click(object sender, EventArgs e)
{
var x = HoverX;
var y = HoverY;
var tile = Current.GetTile(x, y);
ViewTile(tile, x, y);
}
private void Menu_Set_Click(object sender, EventArgs e)
{
var x = HoverX;
var y = HoverY;
var tile = Current.GetTile(x, y);
SetTile(tile, x, y);
}
private void Menu_Reset_Click(object sender, EventArgs e)
{
var x = HoverX;
var y = HoverY;
var tile = Current.GetTile(x, y);
DeleteTile(tile, x, y);
}
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 = 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);
if (redir.IsRoot && redir.ItemId == tile.ExtensionItemId)
tile = redir;
}
ViewTile(tile);
}
private void ViewTile(Item tile)
{
ItemEdit.LoadItem(tile);
}
private void SetTile(Item tile, int x, int y)
{
var l = Current;
var pgt = new Item();
ItemEdit.SetItem(pgt);
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);
DrawLayer();
}
private void DeleteTile(Item tile, int x, int y)
{
if (CHK_AutoExtension.Checked)
{
if (!tile.IsRoot)
{
x -= tile.ExtensionX;
y -= tile.ExtensionY;
tile = Current.GetTile(x, y);
}
Current.DeleteExtensionTiles(tile, x, y);
}
tile.Delete();
DrawLayer();
}
#endregion
private void B_LoadRoom_Click(object sender, EventArgs e)
{
var room = Manager.Room;
MiscDumpHelper.LoadRoom(SAV.Offsets, ref room, RoomIndex);
var house = Houses[Index];
house.SetRoom(RoomIndex, room);
ReloadManager(house);
DrawLayer();
System.Media.SystemSounds.Asterisk.Play();
}
private void B_DumpRoom_Click(object sender, EventArgs e)
{
SaveRoom();
MiscDumpHelper.DumpRoom(Manager.Room, RoomIndex);
}
}