Add batch editor

Similar to PKHeX's batch editor, probably with some stubbed functionality.

Example to change Oak Trees to apple trees:
=ItemId=60000
.ItemId=60001
;
=ExtensionItemId=60000
.ExtensionItemId=60001

Example to unbury all items:
=IsBuried=True
.IsBuried=False
.IsDropped=True
This commit is contained in:
Kurt 2021-03-21 11:51:57 -07:00
parent c9a4554beb
commit 0798aa5a97
18 changed files with 1115 additions and 35 deletions

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace NHSE.Core
{
public abstract class BatchMutator<T> where T : class
{
protected const string CONST_RAND = "$rand";
public abstract ModifyResult Modify(T item, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications);
}
}

View File

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace NHSE.Core
{
/// <summary>
/// Carries out a batch edit and contains information summarizing the results.
/// </summary>
public abstract class BatchProcessor<T> where T : class
{
private int Modified { get; set; }
private int Iterated { get; set; }
private int Failed { get; set; }
protected readonly BatchMutator<T> Mutator;
protected BatchProcessor(BatchMutator<T> mut) => Mutator = mut;
protected abstract bool CanModify(T item);
protected abstract bool Finalize(T item);
/// <summary>
/// Tries to modify the <see cref="item"/>.
/// </summary>
/// <param name="item">Object to modify.</param>
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
/// <param name="modifications">Modifications to perform on the item.</param>
/// <returns>Result of the attempted modification.</returns>
public bool Process(T item, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
{
if (!CanModify(item))
return false;
var result = Mutator.Modify(item, filters, modifications);
if (result != ModifyResult.Invalid)
Iterated++;
if (result == ModifyResult.Error)
Failed++;
if (result != ModifyResult.Modified)
return false;
Finalize(item);
Modified++;
return true;
}
/// <summary>
/// Gets a message indicating the overall result of all modifications performed across multiple Batch Edit jobs.
/// </summary>
/// <param name="sets">Collection of modifications.</param>
/// <returns>Friendly (multi-line) string indicating the result of the batch edits.</returns>
public string GetEditorResults(ICollection<StringInstructionSet> sets)
{
if (sets.Count == 0)
return "No instructions present.";
int ctr = Modified / sets.Count;
int len = Iterated / sets.Count;
string maybe = sets.Count == 1 ? string.Empty : "~";
string result = $"Success: {maybe}{ctr}/{len}";
if (Failed > 0)
result += Environment.NewLine + maybe + $"Failed: {Failed} not processed.";
return result;
}
public void Execute(IList<string> lines, IEnumerable<T> data)
{
var sets = StringInstructionSet.GetBatchSets(lines).ToArray();
foreach (var pk in data)
{
foreach (var set in sets)
Process(pk, set.Filters, set.Instructions);
}
}
}
}

View File

@ -0,0 +1,215 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
namespace NHSE.Core
{
public class ItemMutator : BatchMutator<Item>
{
public readonly ItemReflection Reflect = ItemReflection.Default;
public override ModifyResult Modify(Item item, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
{
var pi = Reflect.Props[Array.IndexOf(Reflect.Types, item.GetType())];
foreach (var cmd in filters)
{
try
{
if (!IsFilterMatch(cmd, item, pi))
return ModifyResult.Filtered;
}
#pragma warning disable CA1031 // Do not catch general exception types
// Swallow any error because this can be malformed user input.
catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{
Debug.WriteLine($"Failed to compare: {ex.Message} - {cmd.PropertyName} {cmd.PropertyValue}");
return ModifyResult.Error;
}
}
ModifyResult result = ModifyResult.Modified;
foreach (var cmd in modifications)
{
try
{
var tmp = SetProperty(cmd, item, pi);
if (tmp != ModifyResult.Modified)
result = tmp;
}
#pragma warning disable CA1031 // Do not catch general exception types
// Swallow any error because this can be malformed user input.
catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{
Debug.WriteLine($"Failed to modify: {ex.Message} - {cmd.PropertyName} {cmd.PropertyValue}");
}
}
return result;
}
/// <summary>
/// Sets the if the <see cref="Item"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="item">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache (optional)</param>
/// <returns>True if filtered, else false.</returns>
private static ModifyResult SetProperty(StringInstruction cmd, Item item, IReadOnlyDictionary<string, PropertyInfo> props)
{
if (SetComplexProperty(item, cmd))
return ModifyResult.Modified;
if (!props.TryGetValue(cmd.PropertyName, out var pi))
return ModifyResult.Error;
if (!pi.CanWrite)
return ModifyResult.Error;
object val = cmd.Random ? (object)cmd.RandomValue : cmd.PropertyValue;
ReflectUtil.SetValue(pi, item, val);
return ModifyResult.Modified;
}
private static bool SetComplexProperty(Item item, StringInstruction cmd)
{
// Zeroed out item?
if (cmd.PropertyName == nameof(Item.ItemId))
{
if (!int.TryParse(cmd.PropertyValue, out var val))
return false;
if (val is not 0 or 0xFFFE)
return false;
item.Delete();
return true;
}
return false;
}
/// <summary>
/// Tries to fetch the <see cref="Item"/> property from the cache of available properties.
/// </summary>
/// <param name="item">Pokémon to check</param>
/// <param name="name">Property Name to check</param>
/// <param name="pi">Property Info retrieved (if any).</param>
/// <returns>True if has property, false if does not.</returns>
public bool TryGetHasProperty(Item item, string name, [NotNullWhen(true)] out PropertyInfo? pi)
{
var type = item.GetType();
return TryGetHasProperty(type, name, out pi);
}
/// <summary>
/// Tries to fetch the <see cref="Item"/> property from the cache of available properties.
/// </summary>
/// <param name="type">Type to check</param>
/// <param name="name">Property Name to check</param>
/// <param name="pi">Property Info retrieved (if any).</param>
/// <returns>True if has property, false if does not.</returns>
public bool TryGetHasProperty(Type type, string name, [NotNullWhen(true)] out PropertyInfo? pi)
{
var index = Array.IndexOf(Reflect.Types, type);
if (index < 0)
{
pi = null;
return false;
}
var props = Reflect.Props[index];
return props.TryGetValue(name, out pi);
}
/// <summary>
/// Gets the type of the <see cref="Item"/> property using the saved cache of properties.
/// </summary>
/// <param name="propertyName">Property Name to fetch the type for</param>
/// <param name="typeIndex">Type index. Leave empty (0) for a nonspecific format.</param>
/// <returns>Short name of the property's type.</returns>
public string? GetPropertyType(string propertyName, int typeIndex = 0)
{
if (typeIndex == 0) // Any
{
foreach (var p in Reflect.Props)
{
if (p.TryGetValue(propertyName, out var pi))
return pi.PropertyType.Name;
}
return null;
}
int index = typeIndex - 1 >= Reflect.Props.Length ? 0 : typeIndex - 1; // All vs Specific
var pr = Reflect.Props[index];
if (!pr.TryGetValue(propertyName, out var info))
return null;
return info.PropertyType.Name;
}
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <param name="filters">Filters which must be satisfied.</param>
/// <param name="item">Object to check.</param>
/// <returns>True if <see cref="item"/> matches all filters.</returns>
public bool IsFilterMatch(IEnumerable<StringInstruction> filters, Item item) => filters.All(z => IsFilterMatch(z, item, Reflect.Props[Array.IndexOf(Reflect.Types, item.GetType())]));
/// <summary>
/// Checks if the <see cref="Item"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="item">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache (optional)</param>
/// <returns>True if filter matches, else false.</returns>
private static bool IsFilterMatch(StringInstruction cmd, Item item, IReadOnlyDictionary<string, PropertyInfo> props)
{
return IsPropertyFiltered(cmd, item, props);
}
/// <summary>
/// Checks if the <see cref="Item"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="item">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache</param>
/// <returns>True if filtered, else false.</returns>
private static bool IsPropertyFiltered(StringInstruction cmd, Item item, IReadOnlyDictionary<string, PropertyInfo> props)
{
if (!props.TryGetValue(cmd.PropertyName, out var pi))
return false;
if (!pi.CanRead)
return false;
return pi.IsValueEqual(item, cmd.PropertyValue) == cmd.Evaluator;
}
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <param name="filters">Filters which must be satisfied.</param>
/// <param name="obj">Object to check.</param>
/// <returns>True if <see cref="obj"/> matches all filters.</returns>
public static bool IsFilterMatch(IEnumerable<StringInstruction> filters, object obj)
{
foreach (var cmd in filters)
{
if (!ReflectUtil.HasProperty(obj, cmd.PropertyName, out var pi))
return false;
try
{
if (pi.IsValueEqual(obj, cmd.PropertyValue) == cmd.Evaluator)
continue;
}
#pragma warning disable CA1031 // Do not catch general exception types
// User provided inputs can mismatch the type's required value format, and fail to be compared.
catch (Exception e)
#pragma warning restore CA1031 // Do not catch general exception types
{
Debug.WriteLine($"Unable to compare {cmd.PropertyName} to {cmd.PropertyValue}.");
Debug.WriteLine(e.Message);
}
return false;
}
return true;
}
}
}

View File

@ -0,0 +1,12 @@
namespace NHSE.Core
{
public class ItemProcessor : BatchProcessor<Item>
{
public ItemProcessor(BatchMutator<Item> mut) : base(mut)
{
}
protected override bool CanModify(Item item) => true;
protected override bool Finalize(Item item) => true;
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace NHSE.Core
{
public class ItemReflection
{
public static ItemReflection Default { get; } = new();
public readonly Type[] Types = { typeof(Item), typeof(VillagerItem) };
public readonly Dictionary<string, PropertyInfo>[] Props;
public readonly string[][] Properties;
public ItemReflection()
{
Props = Types
.Select(z => ReflectUtil.GetAllPropertyInfoPublic(z)
.GroupBy(p => p.Name)
.Select(g => g.First())
.ToDictionary(p => p.Name))
.ToArray();
Properties = GetPropArray();
}
public string[][] GetPropArray()
{
var p = new string[Types.Length][];
for (int i = 0; i < p.Length; i++)
{
var pz = ReflectUtil.GetPropertiesPublic(Types[i]);
p[i] = pz.OrderBy(a => a).ToArray();
}
// Properties for any
var any = ReflectUtil.GetPropertiesPublic(typeof(Item)).Union(p.SelectMany(a => a)).OrderBy(a => a).ToArray();
// Properties shared by all
var all = p.Aggregate(new HashSet<string>(p[0]), (h, e) => { h.IntersectWith(e); return h; }).OrderBy(a => a).ToArray();
var p1 = new string[Types.Length + 2][];
Array.Copy(p, 0, p1, 1, p.Length);
p1[0] = any;
p1[p1.Length - 1] = all;
return p1;
}
}
}

View File

@ -0,0 +1,28 @@
namespace NHSE.Core
{
/// <summary>
/// Batch Editor Modification result for an individual item.
/// </summary>
public enum ModifyResult
{
/// <summary>
/// The data has invalid data and is not a suitable candidate for modification.
/// </summary>
Invalid,
/// <summary>
/// An error was occurred while iterating modifications for this data.
/// </summary>
Error,
/// <summary>
/// The data was skipped due to a matching Filter.
/// </summary>
Filtered,
/// <summary>
/// The data was modified.
/// </summary>
Modified,
}
}

View File

@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace NHSE.Core
{
/// <summary>
/// Batch Editing instruction
/// </summary>
/// <remarks>
/// Can be a filter (skip), or a modification instruction (modify)
/// </remarks>
/// <see cref="Exclude"/>
/// <see cref="Require"/>
/// <see cref="Apply"/>
public sealed class StringInstruction
{
public string PropertyName { get; }
public string PropertyValue { get; private set; }
public bool Evaluator { get; private init; }
public StringInstruction(string name, string value)
{
PropertyName = name;
PropertyValue = value;
}
public void SetScreenedValue(string[] arr)
{
int index = Array.IndexOf(arr, PropertyValue);
PropertyValue = index > -1 ? index.ToString() : PropertyValue;
}
public static readonly IReadOnlyList<char> Prefixes = new[] { Apply, Require, Exclude };
private const char Exclude = '!';
private const char Require = '=';
private const char Apply = '.';
private const char SplitRange = ',';
/// <summary>
/// Character which divides a property and a value.
/// </summary>
/// <remarks>
/// Example:
/// =Species=1
/// The second = is the split.
/// </remarks>
public const char SplitInstruction = '=';
// Extra Functionality
private int RandomMinimum, RandomMaximum;
public bool Random { get; private set; }
public int RandomValue => RandUtil.Rand.Next(RandomMinimum, RandomMaximum + 1);
public void SetRandRange(string pv)
{
string str = pv.Substring(1);
var split = str.Split(SplitRange);
int.TryParse(split[0], out RandomMinimum);
int.TryParse(split[1], out RandomMaximum);
if (RandomMinimum == RandomMaximum)
{
PropertyValue = RandomMinimum.ToString();
Debug.WriteLine(PropertyName + " randomization range Min/Max same?");
}
else
{
Random = true;
}
}
public static IEnumerable<StringInstruction> GetFilters(IEnumerable<string> lines)
{
var raw = GetRelevantStrings(lines, Exclude, Require);
return from line in raw
let eval = line[0] == Require
let split = line.Substring(1).Split(SplitInstruction)
where split.Length == 2 && !string.IsNullOrWhiteSpace(split[0])
select new StringInstruction(split[0], split[1]) { Evaluator = eval };
}
public static IEnumerable<StringInstruction> GetInstructions(IEnumerable<string> lines)
{
var raw = GetRelevantStrings(lines, Apply).Select(line => line.Substring(1));
return from line in raw
select line.Split(SplitInstruction) into split
where split.Length == 2
select new StringInstruction(split[0], split[1]);
}
/// <summary>
/// Weeds out invalid lines and only returns those with a valid first character.
/// </summary>
private static IEnumerable<string> GetRelevantStrings(IEnumerable<string> lines, params char[] pieces)
{
return lines.Where(line => !string.IsNullOrEmpty(line) && pieces.Any(z => z == line[0]));
}
}
}

View File

@ -0,0 +1,38 @@
using System.Collections.Generic;
using System.Linq;
namespace NHSE.Core
{
/// <summary>
/// Processes input of strings into a list of valid Filters and Instructions.
/// </summary>
public sealed class StringInstructionSet
{
public readonly IReadOnlyList<StringInstruction> Filters;
public readonly IReadOnlyList<StringInstruction> Instructions;
private const string SetSeparator = ";";
public StringInstructionSet(IReadOnlyList<StringInstruction> filters, IReadOnlyList<StringInstruction> instructions)
{
Filters = filters;
Instructions = instructions;
}
public StringInstructionSet(ICollection<string> set)
{
Filters = StringInstruction.GetFilters(set).ToList();
Instructions = StringInstruction.GetInstructions(set).ToList();
}
public static IEnumerable<StringInstructionSet> GetBatchSets(IList<string> lines)
{
int start = 0;
while (start < lines.Count)
{
var list = lines.Skip(start).TakeWhile(_ => !lines[start++].StartsWith(SetSeparator)).ToList();
yield return new StringInstructionSet(list);
}
}
}
}

View File

@ -264,8 +264,10 @@ public static Item GetItem(string itemName, string lang = "en")
return parsedItem;
if (gStrings.HasAssociatedItems(itemName, out var items))
if (items != null && items.Count == 1)
{
if (items?.Count == 1)
return new Item((ushort)items[0].Value);
}
return Item.NO_ITEM;
}

View File

@ -7,7 +7,9 @@ public abstract class ItemLayer : MapGrid
{
public readonly Item[] Tiles;
protected ItemLayer(Item[] tiles, int w, int h) : this(tiles, w, h, w, h) { }
protected ItemLayer(Item[] tiles, int w, int h) : this(tiles, w, h, w, h)
{
}
protected ItemLayer(Item[] tiles, int w, int h, int gw, int gh) : base(gw, gh, w, h)
{
@ -52,6 +54,7 @@ public int RemoveAll(in int xmin, in int ymin, in int width, in int height, Func
count++;
}
}
return count;
}
@ -169,7 +172,47 @@ public int ReplaceAll(Item oldItem, Item newItem, in int xmin, in int ymin, in i
count++;
}
}
return count;
}
public int ClearDanglingExtensions(in int xmin, in int ymin, in int width, in int height)
{
int count = 0;
for (int x = xmin; x < xmin + width; x++)
{
for (int y = ymin; y < ymin + height; y++)
{
var t = GetTile(x, y);
if (IsValidExtension(t, x, y))
continue;
t.Delete();
count++;
}
}
return count;
}
private bool IsValidExtension(Item t, int x, int y)
{
if (!t.IsExtension)
return true;
var parentX = x - t.ExtensionX;
var parentY = y - t.ExtensionY;
try
{
var parent = GetTile(parentX, parentY);
if (parent.ItemId == t.ExtensionItemId)
return true;
}
#pragma warning disable CA1031 // Do not catch general exception types
catch
{
// corrupt?
}
#pragma warning restore CA1031 // Do not catch general exception types
return false;
}
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
@ -113,7 +114,7 @@ public static IEnumerable<TypeInfo> GetAllTypeInfo(this TypeInfo? typeInfo)
}
}
public static bool HasProperty(object obj, string name, out PropertyInfo? pi) => (pi = GetPropertyInfo(obj.GetType().GetTypeInfo(), name)) != null;
public static bool HasProperty(object obj, string name, [NotNullWhen(true)] out PropertyInfo? pi) => (pi = GetPropertyInfo(obj.GetType().GetTypeInfo(), name)) != null;
public static PropertyInfo? GetPropertyInfo(this TypeInfo typeInfo, string name)
{

View File

@ -97,6 +97,9 @@
<Compile Update="Subforms\Player\AchievementEditor.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="Subforms\SysBot\BatchEditor.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="Subforms\SysBot\SysBotRAMEdit.cs">
<SubType>Form</SubType>
</Compile>

View File

@ -140,13 +140,14 @@ private void InitializeComponent()
this.B_SetAllTerrain = new System.Windows.Forms.ToolStripMenuItem();
this.B_SetAllRoadTiles = new System.Windows.Forms.ToolStripMenuItem();
this.B_ClearPlacedDesigns = new System.Windows.Forms.ToolStripMenuItem();
this.B_ImportPlacedDesigns = new System.Windows.Forms.ToolStripMenuItem();
this.B_ExportPlacedDesigns = new System.Windows.Forms.ToolStripMenuItem();
this.RB_Item = new System.Windows.Forms.RadioButton();
this.RB_Terrain = new System.Windows.Forms.RadioButton();
this.L_TileMode = new System.Windows.Forms.Label();
this.CHK_RedirectExtensionLoad = new System.Windows.Forms.CheckBox();
this.CHK_FieldItemSnap = new System.Windows.Forms.CheckBox();
this.B_ImportPlacedDesigns = new System.Windows.Forms.ToolStripMenuItem();
this.B_ExportPlacedDesigns = new System.Windows.Forms.ToolStripMenuItem();
this.Menu_Batch = new System.Windows.Forms.ToolStripMenuItem();
this.CM_Click.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.PB_Map)).BeginInit();
this.CM_Picture.SuspendLayout();
@ -472,104 +473,105 @@ private void InitializeComponent()
this.B_RemoveAll,
this.toolStripSeparator1,
this.B_WaterFlowers,
this.Menu_Spawn});
this.Menu_Spawn,
this.Menu_Batch});
this.CM_Remove.Name = "CM_Picture";
this.CM_Remove.ShowImageMargin = false;
this.CM_Remove.Size = new System.Drawing.Size(124, 296);
this.CM_Remove.Size = new System.Drawing.Size(156, 340);
//
// B_RemoveAllWeeds
//
this.B_RemoveAllWeeds.Name = "B_RemoveAllWeeds";
this.B_RemoveAllWeeds.Size = new System.Drawing.Size(123, 22);
this.B_RemoveAllWeeds.Size = new System.Drawing.Size(155, 22);
this.B_RemoveAllWeeds.Text = "Weeds";
this.B_RemoveAllWeeds.Click += new System.EventHandler(this.B_RemoveAllWeeds_Click);
//
// B_RemoveAllTrees
//
this.B_RemoveAllTrees.Name = "B_RemoveAllTrees";
this.B_RemoveAllTrees.Size = new System.Drawing.Size(123, 22);
this.B_RemoveAllTrees.Size = new System.Drawing.Size(155, 22);
this.B_RemoveAllTrees.Text = "Trees";
this.B_RemoveAllTrees.Click += new System.EventHandler(this.B_RemoveAllTrees_Click);
//
// B_RemovePlants
//
this.B_RemovePlants.Name = "B_RemovePlants";
this.B_RemovePlants.Size = new System.Drawing.Size(123, 22);
this.B_RemovePlants.Size = new System.Drawing.Size(155, 22);
this.B_RemovePlants.Text = "Plants";
this.B_RemovePlants.Click += new System.EventHandler(this.B_RemovePlants_Click);
//
// B_RemoveObjects
//
this.B_RemoveObjects.Name = "B_RemoveObjects";
this.B_RemoveObjects.Size = new System.Drawing.Size(123, 22);
this.B_RemoveObjects.Size = new System.Drawing.Size(155, 22);
this.B_RemoveObjects.Text = "Objects";
this.B_RemoveObjects.Click += new System.EventHandler(this.B_RemoveObjects_Click);
//
// B_RemovePlacedItems
//
this.B_RemovePlacedItems.Name = "B_RemovePlacedItems";
this.B_RemovePlacedItems.Size = new System.Drawing.Size(123, 22);
this.B_RemovePlacedItems.Size = new System.Drawing.Size(155, 22);
this.B_RemovePlacedItems.Text = "Placed Items";
this.B_RemovePlacedItems.Click += new System.EventHandler(this.B_RemovePlacedItems_Click);
//
// B_RemoveFences
//
this.B_RemoveFences.Name = "B_RemoveFences";
this.B_RemoveFences.Size = new System.Drawing.Size(123, 22);
this.B_RemoveFences.Size = new System.Drawing.Size(155, 22);
this.B_RemoveFences.Text = "Fences";
this.B_RemoveFences.Click += new System.EventHandler(this.B_RemoveFences_Click);
//
// B_RemoveBranches
//
this.B_RemoveBranches.Name = "B_RemoveBranches";
this.B_RemoveBranches.Size = new System.Drawing.Size(123, 22);
this.B_RemoveBranches.Size = new System.Drawing.Size(155, 22);
this.B_RemoveBranches.Text = "Branches";
this.B_RemoveBranches.Click += new System.EventHandler(this.B_RemoveBranches_Click);
//
// B_RemoveShells
//
this.B_RemoveShells.Name = "B_RemoveShells";
this.B_RemoveShells.Size = new System.Drawing.Size(123, 22);
this.B_RemoveShells.Size = new System.Drawing.Size(155, 22);
this.B_RemoveShells.Text = "Shells";
this.B_RemoveShells.Click += new System.EventHandler(this.B_RemoveShells_Click);
//
// B_RemoveFlowers
//
this.B_RemoveFlowers.Name = "B_RemoveFlowers";
this.B_RemoveFlowers.Size = new System.Drawing.Size(123, 22);
this.B_RemoveFlowers.Size = new System.Drawing.Size(155, 22);
this.B_RemoveFlowers.Text = "Flowers";
this.B_RemoveFlowers.Click += new System.EventHandler(this.B_RemoveFlowers_Click);
//
// B_FillHoles
//
this.B_FillHoles.Name = "B_FillHoles";
this.B_FillHoles.Size = new System.Drawing.Size(123, 22);
this.B_FillHoles.Size = new System.Drawing.Size(155, 22);
this.B_FillHoles.Text = "Holes";
this.B_FillHoles.Click += new System.EventHandler(this.B_FillHoles_Click);
//
// B_RemoveAll
//
this.B_RemoveAll.Name = "B_RemoveAll";
this.B_RemoveAll.Size = new System.Drawing.Size(123, 22);
this.B_RemoveAll.Size = new System.Drawing.Size(155, 22);
this.B_RemoveAll.Text = "All";
this.B_RemoveAll.Click += new System.EventHandler(this.B_RemoveAll_Click);
//
// toolStripSeparator1
//
this.toolStripSeparator1.Name = "toolStripSeparator1";
this.toolStripSeparator1.Size = new System.Drawing.Size(120, 6);
this.toolStripSeparator1.Size = new System.Drawing.Size(152, 6);
//
// B_WaterFlowers
//
this.B_WaterFlowers.Name = "B_WaterFlowers";
this.B_WaterFlowers.Size = new System.Drawing.Size(123, 22);
this.B_WaterFlowers.Size = new System.Drawing.Size(155, 22);
this.B_WaterFlowers.Text = "Water Flowers";
this.B_WaterFlowers.Click += new System.EventHandler(this.B_WaterFlowers_Click);
//
// Menu_Spawn
//
this.Menu_Spawn.Name = "Menu_Spawn";
this.Menu_Spawn.Size = new System.Drawing.Size(123, 22);
this.Menu_Spawn.Size = new System.Drawing.Size(155, 22);
this.Menu_Spawn.Text = "Spawn...";
this.Menu_Spawn.Click += new System.EventHandler(this.Menu_Spawn_Click);
//
@ -1262,7 +1264,7 @@ private void InitializeComponent()
this.B_ExportPlacedDesigns});
this.CM_Terrain.Name = "CM_Picture";
this.CM_Terrain.ShowImageMargin = false;
this.CM_Terrain.Size = new System.Drawing.Size(225, 158);
this.CM_Terrain.Size = new System.Drawing.Size(225, 136);
//
// B_ZeroElevation
//
@ -1292,6 +1294,20 @@ private void InitializeComponent()
this.B_ClearPlacedDesigns.Text = "Clear all Placed Designs";
this.B_ClearPlacedDesigns.Click += new System.EventHandler(this.B_ClearPlacedDesigns_Click);
//
// B_ImportPlacedDesigns
//
this.B_ImportPlacedDesigns.Name = "B_ImportPlacedDesigns";
this.B_ImportPlacedDesigns.Size = new System.Drawing.Size(224, 22);
this.B_ImportPlacedDesigns.Text = "Import all Placed Design Choices";
this.B_ImportPlacedDesigns.Click += new System.EventHandler(this.B_ImportPlacedDesigns_Click);
//
// B_ExportPlacedDesigns
//
this.B_ExportPlacedDesigns.Name = "B_ExportPlacedDesigns";
this.B_ExportPlacedDesigns.Size = new System.Drawing.Size(224, 22);
this.B_ExportPlacedDesigns.Text = "Export all Placed Design Choices";
this.B_ExportPlacedDesigns.Click += new System.EventHandler(this.B_ExportPlacedDesigns_Click);
//
// RB_Item
//
this.RB_Item.CheckAlign = System.Drawing.ContentAlignment.MiddleRight;
@ -1349,19 +1365,12 @@ private void InitializeComponent()
this.CHK_FieldItemSnap.Text = "Snap Field Items to Grid on Set";
this.CHK_FieldItemSnap.UseVisualStyleBackColor = true;
//
// B_ImportPlacedDesigns
// Menu_Batch
//
this.B_ImportPlacedDesigns.Name = "B_ImportPlacedDesigns";
this.B_ImportPlacedDesigns.Size = new System.Drawing.Size(224, 22);
this.B_ImportPlacedDesigns.Text = "Import all Placed Design Choices";
this.B_ImportPlacedDesigns.Click += new System.EventHandler(this.B_ImportPlacedDesigns_Click);
//
// B_ExportPlacedDesigns
//
this.B_ExportPlacedDesigns.Name = "B_ExportPlacedDesigns";
this.B_ExportPlacedDesigns.Size = new System.Drawing.Size(224, 22);
this.B_ExportPlacedDesigns.Text = "Export all Placed Design Choices";
this.B_ExportPlacedDesigns.Click += new System.EventHandler(this.B_ExportPlacedDesigns_Click);
this.Menu_Batch.Name = "Menu_Batch";
this.Menu_Batch.Size = new System.Drawing.Size(155, 22);
this.Menu_Batch.Text = "Batch Editor";
this.Menu_Batch.Click += new System.EventHandler(this.Menu_Bulk_Click);
//
// FieldItemEditor
//
@ -1554,5 +1563,6 @@ private void InitializeComponent()
private System.Windows.Forms.ToolStripMenuItem B_RemoveAllTrees;
private System.Windows.Forms.ToolStripMenuItem B_ImportPlacedDesigns;
private System.Windows.Forms.ToolStripMenuItem B_ExportPlacedDesigns;
private System.Windows.Forms.ToolStripMenuItem Menu_Batch;
}
}

View File

@ -987,6 +987,14 @@ private void B_ImportPlacedDesigns_Click(object sender, EventArgs e)
}
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();
}
}
public interface IItemLayerEditor

View File

@ -159,7 +159,7 @@ private void PlayerItemEditor_DragDrop(object sender, DragEventArgs e)
}
}
public void EnableDragDrop(Control parent, DragEventHandler enter, DragEventHandler drop)
public static void EnableDragDrop(Control parent, DragEventHandler enter, DragEventHandler drop)
{
parent.AllowDrop = true;
parent.DragEnter += enter;

View File

@ -0,0 +1,192 @@
namespace NHSE.WinForms
{
partial class BatchEditor
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.L_PropValue = new System.Windows.Forms.Label();
this.L_PropType = new System.Windows.Forms.Label();
this.B_Add = new System.Windows.Forms.Button();
this.CB_Require = new System.Windows.Forms.ComboBox();
this.CB_Property = new System.Windows.Forms.ComboBox();
this.CB_Format = new System.Windows.Forms.ComboBox();
this.PB_Show = new System.Windows.Forms.ProgressBar();
this.B_Go = new System.Windows.Forms.Button();
this.RTB_Instructions = new System.Windows.Forms.RichTextBox();
this.b = new System.ComponentModel.BackgroundWorker();
this.toolTip1 = new System.Windows.Forms.ToolTip(this.components);
this.toolTip2 = new System.Windows.Forms.ToolTip(this.components);
this.toolTip3 = new System.Windows.Forms.ToolTip(this.components);
this.SuspendLayout();
//
// L_PropValue
//
this.L_PropValue.AutoSize = true;
this.L_PropValue.Location = new System.Drawing.Point(205, 36);
this.L_PropValue.Name = "L_PropValue";
this.L_PropValue.Size = new System.Drawing.Size(73, 13);
this.L_PropValue.TabIndex = 22;
this.L_PropValue.Text = "PropertyValue";
//
// L_PropType
//
this.L_PropType.AutoSize = true;
this.L_PropType.Location = new System.Drawing.Point(59, 36);
this.L_PropType.Name = "L_PropType";
this.L_PropType.Size = new System.Drawing.Size(70, 13);
this.L_PropType.TabIndex = 21;
this.L_PropType.Text = "PropertyType";
//
// B_Add
//
this.B_Add.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.B_Add.Location = new System.Drawing.Point(325, 11);
this.B_Add.Name = "B_Add";
this.B_Add.Size = new System.Drawing.Size(57, 23);
this.B_Add.TabIndex = 20;
this.B_Add.Text = "Add";
this.B_Add.UseVisualStyleBackColor = true;
this.B_Add.Click += new System.EventHandler(this.B_Add_Click);
//
// CB_Require
//
this.CB_Require.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.CB_Require.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.CB_Require.FormattingEnabled = true;
this.CB_Require.Items.AddRange(new object[] {
"Set Equal To",
"Require Equals",
"Require Not Equals"});
this.CB_Require.Location = new System.Drawing.Point(208, 12);
this.CB_Require.Name = "CB_Require";
this.CB_Require.Size = new System.Drawing.Size(111, 21);
this.CB_Require.TabIndex = 19;
//
// CB_Property
//
this.CB_Property.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.CB_Property.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend;
this.CB_Property.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.ListItems;
this.CB_Property.DropDownWidth = 200;
this.CB_Property.FormattingEnabled = true;
this.CB_Property.Location = new System.Drawing.Point(62, 12);
this.CB_Property.Name = "CB_Property";
this.CB_Property.Size = new System.Drawing.Size(140, 21);
this.CB_Property.TabIndex = 18;
this.CB_Property.SelectedIndexChanged += new System.EventHandler(this.CB_Property_SelectedIndexChanged);
//
// CB_Format
//
this.CB_Format.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.CB_Format.FormattingEnabled = true;
this.CB_Format.Location = new System.Drawing.Point(12, 12);
this.CB_Format.Name = "CB_Format";
this.CB_Format.Size = new System.Drawing.Size(44, 21);
this.CB_Format.TabIndex = 17;
this.CB_Format.SelectedIndexChanged += new System.EventHandler(this.CB_Format_SelectedIndexChanged);
//
// PB_Show
//
this.PB_Show.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.PB_Show.Location = new System.Drawing.Point(13, 220);
this.PB_Show.Name = "PB_Show";
this.PB_Show.Size = new System.Drawing.Size(307, 21);
this.PB_Show.TabIndex = 16;
//
// B_Go
//
this.B_Go.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.B_Go.Location = new System.Drawing.Point(326, 219);
this.B_Go.Name = "B_Go";
this.B_Go.Size = new System.Drawing.Size(57, 23);
this.B_Go.TabIndex = 15;
this.B_Go.Text = "Run";
this.B_Go.UseVisualStyleBackColor = true;
this.B_Go.Click += new System.EventHandler(this.B_Go_Click);
//
// RTB_Instructions
//
this.RTB_Instructions.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.RTB_Instructions.Location = new System.Drawing.Point(13, 56);
this.RTB_Instructions.Name = "RTB_Instructions";
this.RTB_Instructions.Size = new System.Drawing.Size(370, 158);
this.RTB_Instructions.TabIndex = 14;
this.RTB_Instructions.Text = "";
//
// b
//
this.b.WorkerReportsProgress = true;
//
// BatchEditor
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(394, 254);
this.Controls.Add(this.L_PropValue);
this.Controls.Add(this.L_PropType);
this.Controls.Add(this.B_Add);
this.Controls.Add(this.CB_Require);
this.Controls.Add(this.CB_Property);
this.Controls.Add(this.CB_Format);
this.Controls.Add(this.PB_Show);
this.Controls.Add(this.B_Go);
this.Controls.Add(this.RTB_Instructions);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.Icon = global::NHSE.WinForms.Properties.Resources.icon;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "BatchEditor";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Batch Editor";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.SysBotRAMEdit_FormClosing);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label L_PropValue;
private System.Windows.Forms.Label L_PropType;
private System.Windows.Forms.Button B_Add;
private System.Windows.Forms.ComboBox CB_Require;
private System.Windows.Forms.ComboBox CB_Property;
private System.Windows.Forms.ComboBox CB_Format;
private System.Windows.Forms.ProgressBar PB_Show;
private System.Windows.Forms.Button B_Go;
private System.Windows.Forms.RichTextBox RTB_Instructions;
private System.ComponentModel.BackgroundWorker b;
private System.Windows.Forms.ToolTip toolTip1;
private System.Windows.Forms.ToolTip toolTip2;
private System.Windows.Forms.ToolTip toolTip3;
}
}

View File

@ -0,0 +1,159 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using NHSE.Core;
namespace NHSE.WinForms
{
public partial class BatchEditor : Form
{
private readonly ItemMutator Mutator;
private readonly ItemProcessor Processor;
private readonly IReadOnlyList<Item> Items;
private readonly Item item;
private int currentFormat = -1;
public BatchEditor(IReadOnlyList<Item> items, Item item)
{
InitializeComponent();
Items = items;
this.item = item;
Mutator = new ItemMutator();
Processor = new ItemProcessor(Mutator);
CB_Format.Items.Clear();
CB_Format.Items.Add("Any");
foreach (Type t in Mutator.Reflect.Types)
CB_Format.Items.Add(t.Name.ToLower());
CB_Format.Items.Add("All");
CB_Format.SelectedIndex = CB_Require.SelectedIndex = 0;
toolTip1.SetToolTip(CB_Property, "Name");
toolTip2.SetToolTip(L_PropType, "Type");
toolTip3.SetToolTip(L_PropValue, "Value");
}
private void SysBotRAMEdit_FormClosing(object sender, FormClosingEventArgs e)
{
}
private void B_Go_Click(object sender, EventArgs e)
{
RunBackgroundWorker();
}
private void B_Add_Click(object sender, EventArgs e)
{
if (CB_Property.SelectedIndex < 0)
{ WinFormsUtil.Alert("Invalid Property"); return; }
var prefix = StringInstruction.Prefixes;
string s = prefix[CB_Require.SelectedIndex] + CB_Property.Items[CB_Property.SelectedIndex].ToString() + StringInstruction.SplitInstruction;
if (RTB_Instructions.Lines.Length != 0 && RTB_Instructions.Lines.Last().Length > 0)
s = Environment.NewLine + s;
RTB_Instructions.AppendText(s);
}
private void CB_Format_SelectedIndexChanged(object sender, EventArgs e)
{
if (currentFormat == CB_Format.SelectedIndex)
return;
int format = CB_Format.SelectedIndex;
CB_Property.Items.Clear();
CB_Property.Items.AddRange(Mutator.Reflect.Properties[format]);
CB_Property.SelectedIndex = 0;
currentFormat = format;
}
private void CB_Property_SelectedIndexChanged(object sender, EventArgs e)
{
L_PropType.Text = Mutator.GetPropertyType(CB_Property.Text, CB_Format.SelectedIndex);
if (Mutator.TryGetHasProperty(item, CB_Property.Text, out var pi))
{
L_PropValue.Text = pi.GetValue(item)?.ToString();
L_PropType.ForeColor = L_PropValue.ForeColor; // reset color
}
else // no property, flag
{
L_PropValue.Text = string.Empty;
L_PropType.ForeColor = Color.Red;
}
}
private void RunBackgroundWorker()
{
if (RTB_Instructions.Lines.Any(line => line.Length == 0))
{ WinFormsUtil.Error("Invalid instruction detected."); return; }
var sets = StringInstructionSet.GetBatchSets(RTB_Instructions.Lines).ToArray();
if (sets.Any(s => s.Filters.Any(z => string.IsNullOrWhiteSpace(z.PropertyValue))))
{ WinFormsUtil.Error("Filter empty."); return; }
if (sets.Any(z => z.Instructions.Count == 0))
{ WinFormsUtil.Error("No instructions."); return; }
var emptyVal = sets.SelectMany(s => s.Instructions.Where(z => string.IsNullOrWhiteSpace(z.PropertyValue))).ToArray();
if (emptyVal.Length > 0)
{
string props = string.Join(", ", emptyVal.Select(z => z.PropertyName));
string invalid = "Property empty." + Environment.NewLine + props;
if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, invalid, "Continue?"))
return;
}
RTB_Instructions.Enabled = B_Go.Enabled = false;
RunBatchEdit(sets);
}
private void RunBatchEdit(StringInstructionSet[] sets)
{
bool finished = false, displayed = false; // hack cuz DoWork event isn't cleared after completion
b.DoWork += (sender, e) =>
{
if (finished)
return;
foreach (var s in sets)
{
foreach (var i in Items)
Processor.Process(i, s.Filters, s.Instructions);
}
finished = true;
};
b.ProgressChanged += (sender, e) => SetProgressBar(e.ProgressPercentage);
b.RunWorkerCompleted += (sender, e) =>
{
string result = Processor.GetEditorResults(sets);
if (!displayed) WinFormsUtil.Alert(result);
displayed = true;
RTB_Instructions.Enabled = B_Go.Enabled = true;
SetupProgressBar(0);
};
b.RunWorkerAsync();
}
// Progress Bar
private void SetupProgressBar(int count)
{
MethodInvoker mi = () => { PB_Show.Minimum = 0; PB_Show.Step = 1; PB_Show.Value = 0; PB_Show.Maximum = count; };
if (PB_Show.InvokeRequired)
PB_Show.Invoke(mi);
else
mi.Invoke();
}
private void SetProgressBar(int i)
{
if (PB_Show.InvokeRequired)
PB_Show.Invoke((MethodInvoker)(() => PB_Show.Value = i));
else
PB_Show.Value = i;
}
}
}

View File

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="b.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>367, 17</value>
</metadata>
<metadata name="toolTip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>426, 17</value>
</metadata>
<metadata name="toolTip2.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>523, 17</value>
</metadata>
<metadata name="toolTip3.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>620, 17</value>
</metadata>
</root>