NHSE/NHSE.WinForms/Subforms/Map/BulkSpawn.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

194 lines
6.1 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using NHSE.Core;
namespace NHSE.WinForms;
public partial class BulkSpawn : Form
{
private readonly IItemLayerEditor Editor;
private string NHIFilePath = "";
public BulkSpawn(IItemLayerEditor editor, int x = 0, int y = 0)
{
InitializeComponent();
this.TranslateInterface(GameInfo.CurrentLanguage);
Editor = editor;
CB_SpawnType.Items.AddRange(Enum.GetNames<SpawnType>());
CB_SpawnArrange.Items.AddRange(Enum.GetNames<SpawnArrangement>());
CB_SpawnType.SelectedIndex = 0;
CB_SpawnArrange.SelectedIndex = 0;
NUD_SpawnX.Value = x;
NUD_SpawnY.Value = y;
}
private void B_Apply_Click(object sender, EventArgs e)
{
var type = (SpawnType)CB_SpawnType.SelectedIndex;
var arrange = (SpawnArrangement)CB_SpawnArrange.SelectedIndex;
var x = (int)NUD_SpawnX.Value;
var y = (int)NUD_SpawnY.Value;
var count = (int)NUD_SpawnCount.Value;
IReadOnlyList<Item> items;
int sizeX;
int sizeY;
if (type is SpawnType.SequentialDIY or SpawnType.AlphabeticalDIY)
{
var min = (ushort)NUD_DIYStart.Value;
var max = (ushort)NUD_DIYStop.Value;
var diy = RecipeList.Recipes
.Where(z => z.Key is not (RecipeList.BridgeConstructionKit or RecipeList.CampsiteConstructionKit) && min <= z.Key && z.Key <= max)
.Select(z => z.Key)
.Select(z => new Item(Item.DIYRecipe) {FreeParam = z});
if (type == SpawnType.AlphabeticalDIY)
diy = diy.OrderBy(z => GameInfo.Strings.GetItemName(z));
items = Enumerable.Repeat(diy, count).SelectMany(z => z).ToArray();
sizeX = sizeY = 2;
}
else if (type == SpawnType.ItemsFromNHI)
{
if (string.IsNullOrEmpty(NHIFilePath) || !File.Exists(NHIFilePath))
{
WinFormsUtil.Alert(MessageStrings.MsgFieldItemNoNHI);
return;
}
// read non-empty slots into item array
var data = File.ReadAllBytes(NHIFilePath);
var array = data.GetArray<Item>(Item.SIZE).Where(item => !item.IsNone);
items = Enumerable.Repeat(array, count).SelectMany(z => z).ToArray();
// set flag0 = 0x20 for each item to ensure it gets dropped
// this also forces a 2x2 item size
foreach (var item in items)
item.SystemParam = 0x20;
sizeX = sizeY = 2;
}
else
{
var item = Editor.ItemProvider.SetItem(new Item());
items = Enumerable.Repeat(item, count).ToArray();
var size = ItemInfo.GetItemSize(item);
sizeX = size.Width;
sizeY = size.Height;
}
if (sizeX % 2 == 1)
sizeX++;
if (sizeY % 2 == 1)
sizeY++;
var ctr = SpawnItems(Editor.Spawn, items, x, y, arrange, sizeX, sizeY, true);
if (ctr == 0)
{
WinFormsUtil.Alert(MessageStrings.MsgFieldItemModifyNone);
return;
}
Editor.ReloadItems();
WinFormsUtil.Alert(string.Format(MessageStrings.MsgFieldItemModifyCount, count));
}
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;
var newline = arrange switch
{
SpawnArrangement.Square => (int)Math.Sqrt(items.Count * sizeX * sizeY),
SpawnArrangement.Vertical => 1 * sizeY,
_ => items.Count * sizeX // Horizontal
};
int ctr = 0;
for (var i = 0; i < items.Count; i++)
{
var item = items[i];
var permission = layer.IsOccupied(item, x, y);
switch (permission)
{
case PlacedItemPermission.OutOfBounds when y >= layer.TileInfo.TotalHeight:
return ctr;
case PlacedItemPermission.OutOfBounds:
case PlacedItemPermission.Collision when noOverwrite:
i--;
break;
default:
var exist = layer.GetTile(x, y);
layer.DeleteExtensionTiles(exist, x, y);
// Set new placed data
layer.SetExtensionTiles(item, x, y);
exist.CopyFrom(item);
ctr++;
break;
}
x += sizeX;
if (x - x0 >= newline)
{
y += sizeY;
x = x0;
}
}
return ctr;
}
private void CB_SpawnType_SelectedIndexChanged(object sender, EventArgs e)
{
var index = (SpawnType)CB_SpawnType.SelectedIndex;
L_DIYStart.Visible = L_DIYStop.Visible = NUD_DIYStart.Visible = NUD_DIYStop.Visible = index is SpawnType.SequentialDIY or SpawnType.AlphabeticalDIY;
L_NHI.Visible = L_NHIFileName.Visible = index == SpawnType.ItemsFromNHI;
NUD_SpawnCount.Value = index switch
{
SpawnType.SequentialDIY => 1,
SpawnType.AlphabeticalDIY => 1,
SpawnType.ItemsFromNHI => 1,
_ => 8,
};
if (index == SpawnType.ItemsFromNHI)
{
L_NHIFileName_Click(sender, e);
}
}
private void L_NHIFileName_Click(object sender, EventArgs e)
{
using var ofd = new OpenFileDialog();
ofd.Filter = "New Horizons Inventory (*.nhi)|*.nhi|All files (*.*)|*.*";
ofd.FileName = "items.nhi";
if (ofd.ShowDialog() != DialogResult.OK)
return;
L_NHIFileName.Text = ofd.SafeFileName;
this.NHIFilePath = ofd.FileName;
}
private enum SpawnType
{
ItemFromEditor,
SequentialDIY,
AlphabeticalDIY,
ItemsFromNHI,
}
private enum SpawnArrangement
{
Square,
Horizontal,
Vertical,
}
}