## Summary
- **Add a self-referencing `InterpolationHandler` to `ValueStringBuilder`** that writes directly into the builder's buffer — zero intermediate allocation, works with `stackalloc`-backed builders
- **Replace all `System.Text.StringBuilder` usage** across the codebase with `ValueStringBuilder`
- **Convert `ValueStringBuilder.Create()` to `stackalloc`** at 10 sites where output length is provably bounded
- **Convert manual `Dispose()` to `using var`** where possible, and hoist loop-scoped builders outside loops with `Reset()`
- **Convert verbose `Append()` chains to `Append($"...")`** interpolation for readability
- **Add comprehensive documentation** for string handling patterns
## InterpolationHandler Design
`ValueStringBuilder` is a `ref struct`, which creates challenges for C#'s interpolated string handler pattern:
- **`ref` fields to ref structs are not allowed** (CS9050)
- **`[InterpolatedStringHandlerArgument("")]` passes struct receivers by value**, not by ref
- **`ISelfInterpolatedStringHandler` requires boxing** ref structs into interface fields
**Solution: Copy-and-reconcile pattern.** The handler receives a value copy of the builder. The copy shares the same underlying `char` buffer (`Span` points to the same `stackalloc`/pooled memory), so writes go to the original buffer. `Append()` reconciles by `this = handler._builder`, updating `_length` and any buffer references changed by `Grow()`.
This is safe because:
- The game loop is single-threaded — no concurrent access between handler construction and reconciliation
- If `Grow()` occurs in the copy, the original's stale buffer isn't accessed until `Append()` replaces it
- `Dispose()` correctly returns the reconciled buffer to the pool
## Changes by Category
### ValueStringBuilder (`Projects/Server/Buffers/ValueStringBuilder.cs`)
- Added nested `InterpolationHandler` ref struct with copy-and-reconcile pattern
- Added `Append([InterpolatedStringHandlerArgument("")] scoped ref InterpolationHandler)` method
- Removed `RawInterpolatedStringHandler` overloads (new handler replaces them)
- All `AppendFormatted` overloads delegate to existing `Append` methods (no code duplication)
- Alignment support via direct private field access (nested type privilege)
### StringBuilder → ValueStringBuilder (15 files)
Replaced all `new StringBuilder()` with `ValueStringBuilder.Create()` or `stackalloc`:
- ConPVP games: KingOfTheHill, DoubleDom, CTF, BombingRun, TourneyMatch
- ConPVP infrastructure: Tournament, Participant, TourneyParticipant
- ConPVP gumps: ArenaGump, TournamentBracketGump, AcceptTeamGump, ConfirmSignupGump
- Commands: Handlers, Logging, Add
- Other: TownCrier, SpeechLogGump, TestCenter
Key patterns:
- `sb = new StringBuilder()` reassignment → `sb.Reset()`
- `sb.AppendFormat("{0:N0}", value)` → `sb.Append($"{value:N0}")`
- `sb.Append(x).Append(y)` chains → separate statements (VSB returns void)
### Create() → stackalloc (10 files)
Converted heap-allocated builders to stackalloc where output is bounded:
- ClientVersion (32), MapSelection (160), HouseRaffleStone (48)
- HolySense (96), UnholySense (96), ClientVerification (192)
- AcceptTeamGump (64), ConfirmSignupGump (64)
- BaseWeapon (160), BaseArmor (128)
### Loop optimizations (2 files)
Hoisted `ValueStringBuilder` creation outside loops with `Reset()` per iteration:
- TourneyMatch.cs: `using var` inside for loop → stackalloc before loop
- ArenaGump.cs: `Create()` + `Dispose()` per iteration → stackalloc before loop
### Append chain → interpolation (5 files)
Converted multi-line `Append()` chains to `Append($"...")`:
- BountyMessage.cs: title switch (6 cases), paragraph (15→1 Append), description lines, closing
- AcceptTeamGump, ConfirmSignupGump, TournamentBracketGump: tournament type strings
- AdminGump: comment/tag formatting in loops
### Documentation
- `dev-docs/string-handling.md`: Full reference — construction, interpolation, disposal, decision guide
- `dev-docs/claude-skills/modernuo-string-handling.md`: Claude skill with quick reference
- `CLAUDE.md`: Added rule 17 (no StringBuilder), dev-docs table entry, skills table entry
- `dev-docs/code-standards.md`: Updated memory management section
## Test Plan
- [x] `dotnet build` — 0 errors, 0 warnings
- [x] `dotnet test` — 940/940 tests pass
- [x] 28 ValueStringBuilder tests covering all reconciliation scenarios:
- Stackalloc no-grow, stackalloc with grow (→pool transition)
- Heap no-grow, heap with grow, heap double grow
- Pre-existing content with and without grow
- Sequential multiple `Append($"...")` calls
- Mixed plain + interpolated Append
- Empty interpolation, literal-only, format specifiers
- Null string holes, ISpanFormattable types
- Dispose after stackalloc→pool grow
762 lines
22 KiB
C#
762 lines
22 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Reflection;
|
|
using Server.Gumps;
|
|
using Server.Items;
|
|
using Server.Text;
|
|
using static Server.Attributes;
|
|
using static Server.Types;
|
|
|
|
namespace Server.Commands;
|
|
|
|
public static class Add
|
|
{
|
|
public static void Configure()
|
|
{
|
|
CommandSystem.Register("Tile", AccessLevel.GameMaster, Tile_OnCommand);
|
|
CommandSystem.Register("TileRXYZ", AccessLevel.GameMaster, TileRXYZ_OnCommand);
|
|
CommandSystem.Register("TileXYZ", AccessLevel.GameMaster, TileXYZ_OnCommand);
|
|
CommandSystem.Register("TileZ", AccessLevel.GameMaster, TileZ_OnCommand);
|
|
CommandSystem.Register("TileAvg", AccessLevel.GameMaster, TileAvg_OnCommand);
|
|
|
|
CommandSystem.Register("Outline", AccessLevel.GameMaster, Outline_OnCommand);
|
|
CommandSystem.Register("OutlineRXYZ", AccessLevel.GameMaster, OutlineRXYZ_OnCommand);
|
|
CommandSystem.Register("OutlineXYZ", AccessLevel.GameMaster, OutlineXYZ_OnCommand);
|
|
CommandSystem.Register("OutlineZ", AccessLevel.GameMaster, OutlineZ_OnCommand);
|
|
CommandSystem.Register("OutlineAvg", AccessLevel.GameMaster, OutlineAvg_OnCommand);
|
|
}
|
|
|
|
public static void Invoke(
|
|
Mobile from, Point3D start, Point3D end, string[] args, List<Container> packs = null,
|
|
bool outline = false, bool mapAvg = false
|
|
)
|
|
{
|
|
var type = AddGump.ExactMatch(args[0]);
|
|
|
|
if (type == null)
|
|
{
|
|
from.SendMessage("No type with that name was found.");
|
|
return;
|
|
}
|
|
|
|
Invoke(from, start, end, type, args.AsSpan(1), packs, outline, mapAvg);
|
|
}
|
|
|
|
public static void Invoke(Mobile from, Point3D start, Point3D end, ConstructorInfo ctor)
|
|
{
|
|
using var sb = ValueStringBuilder.Create();
|
|
|
|
sb.Append($"{from.AccessLevel} {CommandLogging.Format(from)} building ");
|
|
|
|
if (start == end)
|
|
{
|
|
sb.Append($"at {start} in {from.Map}");
|
|
}
|
|
else
|
|
{
|
|
sb.Append($"from {start} to {end} in {from.Map}");
|
|
}
|
|
|
|
sb.Append($": {ctor.DeclaringType!.Name}");
|
|
|
|
CommandLogging.WriteLine(from, sb.ToString());
|
|
|
|
var watch = new Stopwatch();
|
|
watch.Start();
|
|
|
|
Utility.FixPoints(ref start, ref end);
|
|
|
|
// Handle optional parameters
|
|
var paramList = ctor.GetParameters();
|
|
var parms = paramList.Length == 0 ? Array.Empty<object>() : new object[paramList.Length];
|
|
for (var i = 0; i < parms.Length; i++)
|
|
{
|
|
parms[i] = Type.Missing;
|
|
}
|
|
|
|
var built = Build(from, start, end, ctor, parms, null, null, null);
|
|
|
|
if (built <= 0)
|
|
{
|
|
SendUsage(ctor, from);
|
|
return;
|
|
}
|
|
|
|
watch.Stop();
|
|
|
|
if (built == 1)
|
|
{
|
|
from.SendMessage($"{built} object generated in {watch.Elapsed.TotalSeconds:F2} seconds.");
|
|
}
|
|
else
|
|
{
|
|
from.SendMessage($"{built} objects generated in {watch.Elapsed.TotalSeconds:F2} seconds.");
|
|
}
|
|
}
|
|
|
|
public static void Invoke(
|
|
Mobile from, Point3D start, Point3D end, Type type,
|
|
ReadOnlySpan<string> args = default, List<Container> packs = null, bool outline = false, bool mapAvg = false
|
|
)
|
|
{
|
|
using var sb = ValueStringBuilder.Create();
|
|
|
|
sb.Append($"{from.AccessLevel} {CommandLogging.Format(from)} building ");
|
|
|
|
if (start == end)
|
|
{
|
|
sb.Append($"at {start} in {from.Map}");
|
|
}
|
|
else
|
|
{
|
|
sb.Append($"from {start} to {end} in {from.Map}");
|
|
}
|
|
|
|
sb.Append($": {type.Name}");
|
|
|
|
string[,] props = null;
|
|
|
|
var setArgsIndex = -1;
|
|
for (var i = 0; i < args.Length; ++i)
|
|
{
|
|
sb.Append($" \"{args[i]}\"");
|
|
|
|
if (setArgsIndex != -1 || !args[i].InsensitiveEquals("set"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
setArgsIndex = i;
|
|
var remains = args.Length - i - 1;
|
|
|
|
if (remains < 2)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
props = new string[remains / 2, 2];
|
|
|
|
remains /= 2;
|
|
|
|
for (var j = 0; j < remains; ++j)
|
|
{
|
|
props[j, 0] = args[i + j * 2 + 1];
|
|
props[j, 1] = args[i + j * 2 + 2];
|
|
}
|
|
}
|
|
|
|
CommandLogging.WriteLine(from, sb.ToString());
|
|
|
|
ReadOnlySpan<string> ctorArgs = setArgsIndex != -1 ? args[..setArgsIndex] : args;
|
|
|
|
var watch = new Stopwatch();
|
|
watch.Start();
|
|
|
|
var built = BuildObjects(from, type, start, end, ctorArgs, props, packs, outline, mapAvg);
|
|
|
|
if (built <= 0)
|
|
{
|
|
SendUsage(type, from);
|
|
return;
|
|
}
|
|
|
|
watch.Stop();
|
|
|
|
if (built == 1)
|
|
{
|
|
from.SendMessage($"{built} object generated in {watch.Elapsed.TotalSeconds:F2} seconds.");
|
|
}
|
|
else
|
|
{
|
|
from.SendMessage($"{built} objects generated in {watch.Elapsed.TotalSeconds:F2} seconds.");
|
|
}
|
|
}
|
|
|
|
public static int BuildObjects(
|
|
Mobile from, Type type, Point3D start, Point3D end, ReadOnlySpan<string> args, string[,] props,
|
|
List<Container> packs, bool outline = false, bool mapAvg = false
|
|
)
|
|
{
|
|
Utility.FixPoints(ref start, ref end);
|
|
|
|
PropertyInfo[] realProps = null;
|
|
|
|
if (props != null)
|
|
{
|
|
realProps = new PropertyInfo[props.GetLength(0)];
|
|
|
|
var allProps =
|
|
type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public);
|
|
|
|
for (var i = 0; i < realProps.Length; ++i)
|
|
{
|
|
var propName = props[i, 0];
|
|
var thisProp = Properties.GetPropertyInfoByName(from, allProps, propName, PropertyAccess.Write, out var failReason);
|
|
|
|
if (failReason == null)
|
|
{
|
|
realProps[i] = thisProp;
|
|
}
|
|
else
|
|
{
|
|
from.SendMessage(failReason);
|
|
}
|
|
}
|
|
}
|
|
|
|
var ctors = type.GetConstructors();
|
|
|
|
for (var i = 0; i < ctors.Length; ++i)
|
|
{
|
|
var ctor = ctors[i];
|
|
|
|
if (!IsConstructible(ctor, from.AccessLevel))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Handle optional constructors
|
|
var paramList = ctor.GetParameters();
|
|
var totalParams = 0;
|
|
for (var j = 0; j < paramList.Length; j++)
|
|
{
|
|
if (!paramList[j].HasDefaultValue)
|
|
{
|
|
totalParams++;
|
|
}
|
|
}
|
|
|
|
if (args.Length >= totalParams && args.Length <= paramList.Length)
|
|
{
|
|
var paramValues = ParseValues(paramList, args);
|
|
|
|
if (paramValues == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var built = Build(from, start, end, ctor, paramValues, props, realProps, packs, outline, mapAvg);
|
|
|
|
if (built > 0)
|
|
{
|
|
return built;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public static object[] ParseValues(ParameterInfo[] paramList, ReadOnlySpan<string> args)
|
|
{
|
|
var values = new object[paramList.Length];
|
|
|
|
for (int i = 0, a = 0; i < paramList.Length; i++)
|
|
{
|
|
var param = paramList[i];
|
|
TryParse(param.ParameterType, a < args.Length ? args[a++] : null, out var value);
|
|
|
|
if (value != null)
|
|
{
|
|
values[i] = value;
|
|
}
|
|
else if (param.HasDefaultValue)
|
|
{
|
|
values[i] = Type.Missing;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
public static IEntity Build(
|
|
Mobile from, ConstructorInfo ctor, object[] values, string[,] props,
|
|
PropertyInfo[] realProps, ref bool sendError
|
|
)
|
|
{
|
|
var built = ctor.Invoke(values);
|
|
|
|
if (realProps != null)
|
|
{
|
|
var hadError = false;
|
|
|
|
for (var i = 0; i < realProps.Length; ++i)
|
|
{
|
|
if (realProps[i] == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var result =
|
|
Properties.InternalSetValue(from, built, built, realProps[i], props[i, 1], props[i, 1], false);
|
|
|
|
if (result != "Property has been set.")
|
|
{
|
|
if (sendError)
|
|
{
|
|
from.SendMessage(result);
|
|
}
|
|
|
|
hadError = true;
|
|
}
|
|
}
|
|
|
|
if (hadError)
|
|
{
|
|
sendError = false;
|
|
}
|
|
}
|
|
|
|
return (IEntity)built;
|
|
}
|
|
|
|
public static int Build(
|
|
Mobile from, Point3D start, Point3D end, ConstructorInfo ctor, object[] values,
|
|
string[,] props, PropertyInfo[] realProps, List<Container> packs, bool outline = false, bool mapAvg = false
|
|
)
|
|
{
|
|
try
|
|
{
|
|
var map = from.Map;
|
|
|
|
var width = end.X - start.X + 1;
|
|
var height = end.Y - start.Y + 1;
|
|
|
|
if (outline && (width < 3 || height < 3))
|
|
{
|
|
outline = false;
|
|
}
|
|
|
|
int objectCount;
|
|
|
|
if (packs != null)
|
|
{
|
|
objectCount = packs.Count;
|
|
}
|
|
else if (outline)
|
|
{
|
|
objectCount = (width + height - 2) * 2;
|
|
}
|
|
else
|
|
{
|
|
objectCount = width * height;
|
|
}
|
|
|
|
if (objectCount >= 20)
|
|
{
|
|
from.SendMessage($"Constructing {objectCount} objects, please wait.");
|
|
}
|
|
|
|
var sendError = true;
|
|
|
|
using var sb = ValueStringBuilder.Create();
|
|
sb.Append("Serials: ");
|
|
|
|
if (packs != null)
|
|
{
|
|
for (var i = 0; i < packs.Count; ++i)
|
|
{
|
|
var built = Build(from, ctor, values, props, realProps, ref sendError);
|
|
|
|
sb.Append($"{built.Serial}; ");
|
|
|
|
if (built is Item item)
|
|
{
|
|
packs[i].DropItem(item);
|
|
}
|
|
else if (built is Mobile m)
|
|
{
|
|
m.MoveToWorld(new Point3D(start.X, start.Y, start.Z), map);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var z = start.Z;
|
|
|
|
for (var x = start.X; x <= end.X; ++x)
|
|
{
|
|
for (var y = start.Y; y <= end.Y; ++y)
|
|
{
|
|
if (outline && x != start.X && x != end.X && y != start.Y && y != end.Y)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (mapAvg)
|
|
{
|
|
z = map.GetAverageZ(x, y);
|
|
}
|
|
|
|
var built = Build(from, ctor, values, props, realProps, ref sendError);
|
|
|
|
sb.Append($"{built.Serial}; ");
|
|
|
|
if (built is Item item)
|
|
{
|
|
item.MoveToWorld(new Point3D(x, y, z), map);
|
|
}
|
|
else if (built is Mobile m)
|
|
{
|
|
m.MoveToWorld(new Point3D(x, y, z), map);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CommandLogging.WriteLine(from, sb.ToString());
|
|
|
|
return objectCount;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine(ex);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public static void SendUsage(Type type, Mobile from)
|
|
{
|
|
var ctors = type.GetConstructors();
|
|
var foundCtor = false;
|
|
|
|
for (var i = 0; i < ctors.Length; ++i)
|
|
{
|
|
var ctor = ctors[i];
|
|
|
|
if (!IsConstructible(ctor, from.AccessLevel))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!foundCtor)
|
|
{
|
|
foundCtor = true;
|
|
from.SendMessage("Usage:");
|
|
}
|
|
|
|
SendCtor(ctor, from);
|
|
}
|
|
|
|
if (!foundCtor)
|
|
{
|
|
from.SendMessage("That type is not marked constructible.");
|
|
}
|
|
}
|
|
|
|
public static void SendUsage(ConstructorInfo ctor, Mobile from)
|
|
{
|
|
if (!IsConstructible(ctor, from.AccessLevel))
|
|
{
|
|
from.SendMessage("That type is not marked constructible.");
|
|
return;
|
|
}
|
|
|
|
from.SendMessage("Usage:");
|
|
SendCtor(ctor, from);
|
|
}
|
|
|
|
public static void SendCtor(ConstructorInfo ctor, Mobile from)
|
|
{
|
|
var paramList = ctor.GetParameters();
|
|
|
|
using var sb = ValueStringBuilder.Create();
|
|
|
|
sb.Append(ctor.DeclaringType!.Name);
|
|
|
|
for (var i = 0; i < paramList.Length; ++i)
|
|
{
|
|
if (i != 0)
|
|
{
|
|
sb.Append(',');
|
|
}
|
|
|
|
sb.Append(' ');
|
|
|
|
sb.Append(paramList[i].ParameterType.Name);
|
|
sb.Append(' ');
|
|
sb.Append(paramList[i].Name);
|
|
}
|
|
|
|
from.SendMessage(sb.ToString());
|
|
}
|
|
|
|
private static void TileBox_Callback(Mobile from, Point3D start, Point3D end, TileState ts)
|
|
{
|
|
var mapAvg = false;
|
|
|
|
switch (ts.m_ZType)
|
|
{
|
|
case TileZType.Fixed:
|
|
{
|
|
start.Z = end.Z = ts.m_FixedZ;
|
|
break;
|
|
}
|
|
case TileZType.MapAverage:
|
|
{
|
|
mapAvg = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Invoke(from, start, end, ts.m_Args, null, ts.m_Outline, mapAvg);
|
|
}
|
|
|
|
private static void Internal_OnCommand(CommandEventArgs e, bool outline)
|
|
{
|
|
var from = e.Mobile;
|
|
|
|
if (e.Length >= 1)
|
|
{
|
|
BoundingBoxPicker.Begin(
|
|
from,
|
|
(_, start, end) =>
|
|
TileBox_Callback(from, start, end, new TileState(TileZType.Start, 0, e.Arguments, outline))
|
|
);
|
|
}
|
|
else
|
|
{
|
|
if (outline)
|
|
{
|
|
from.SendMessage("Format: Outline <type> [params] [set {<propertyName> <value> ...}]");
|
|
}
|
|
else
|
|
{
|
|
from.SendMessage("Format: Tile <type> [params] [set {<propertyName> <value> ...}]");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void InternalRXYZ_OnCommand(CommandEventArgs e, bool outline)
|
|
{
|
|
if (e.Length >= 6)
|
|
{
|
|
var p = new Point3D(e.Mobile.X + e.GetInt32(0), e.Mobile.Y + e.GetInt32(1), e.Mobile.Z + e.GetInt32(4));
|
|
var p2 = new Point3D(p.X + e.GetInt32(2) - 1, p.Y + e.GetInt32(3) - 1, p.Z);
|
|
|
|
var subArgs = new string[e.Length - 5];
|
|
|
|
for (var i = 0; i < subArgs.Length; ++i)
|
|
{
|
|
subArgs[i] = e.Arguments[i + 5];
|
|
}
|
|
|
|
Invoke(e.Mobile, p, p2, subArgs, null, outline);
|
|
}
|
|
else
|
|
{
|
|
if (outline)
|
|
{
|
|
e.Mobile.SendMessage("Format: OutlineRXYZ <x> <y> <w> <h> <z> <type> [params] [set {<propertyName> <value> ...}]");
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage("Format: TileRXYZ <x> <y> <w> <h> <z> <type> [params] [set {<propertyName> <value> ...}]");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void InternalXYZ_OnCommand(CommandEventArgs e, bool outline)
|
|
{
|
|
if (e.Length >= 6)
|
|
{
|
|
var p = new Point3D(e.GetInt32(0), e.GetInt32(1), e.GetInt32(4));
|
|
var p2 = new Point3D(p.X + e.GetInt32(2) - 1, p.Y + e.GetInt32(3) - 1, e.GetInt32(4));
|
|
|
|
var subArgs = new string[e.Length - 5];
|
|
|
|
for (var i = 0; i < subArgs.Length; ++i)
|
|
{
|
|
subArgs[i] = e.Arguments[i + 5];
|
|
}
|
|
|
|
Invoke(e.Mobile, p, p2, subArgs, null, outline);
|
|
}
|
|
else
|
|
{
|
|
if (outline)
|
|
{
|
|
e.Mobile.SendMessage("Format: OutlineXYZ <x> <y> <w> <h> <z> <type> [params] [set {<propertyName> <value> ...}]" );
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage("Format: TileXYZ <x> <y> <w> <h> <z> <type> [params] [set {<propertyName> <value> ...}]");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void InternalZ_OnCommand(CommandEventArgs e, bool outline)
|
|
{
|
|
var from = e.Mobile;
|
|
|
|
if (e.Length >= 2)
|
|
{
|
|
var subArgs = new string[e.Length - 1];
|
|
|
|
for (var i = 0; i < subArgs.Length; ++i)
|
|
{
|
|
subArgs[i] = e.Arguments[i + 1];
|
|
}
|
|
|
|
BoundingBoxPicker.Begin(
|
|
from,
|
|
(_, start, end) =>
|
|
TileBox_Callback(from, start, end, new TileState(TileZType.Fixed, e.GetInt32(0), subArgs, outline))
|
|
);
|
|
}
|
|
else
|
|
{
|
|
if (outline)
|
|
{
|
|
from.SendMessage("Format: OutlineZ <z> <type> [params] [set {<propertyName> <value> ...}]");
|
|
}
|
|
else
|
|
{
|
|
from.SendMessage("Format: TileZ <z> <type> [params] [set {<propertyName> <value> ...}]");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void InternalAvg_OnCommand(CommandEventArgs e, bool outline)
|
|
{
|
|
var from = e.Mobile;
|
|
|
|
if (e.Length >= 1)
|
|
{
|
|
BoundingBoxPicker.Begin(
|
|
from,
|
|
(_, start, end) =>
|
|
TileBox_Callback(from, start, end, new TileState(TileZType.MapAverage, 0, e.Arguments, outline))
|
|
);
|
|
}
|
|
else
|
|
{
|
|
if (outline)
|
|
{
|
|
from.SendMessage("Format: OutlineAvg <type> [params] [set {<propertyName> <value> ...}]");
|
|
}
|
|
else
|
|
{
|
|
from.SendMessage("Format: TileAvg <type> [params] [set {<propertyName> <value> ...}]");
|
|
}
|
|
}
|
|
}
|
|
|
|
[Usage("Tile <name> [params] [set {<propertyName> <value> ...}]")]
|
|
[Description(
|
|
"Tiles an item or npc by name into a targeted bounding box. Optional constructor parameters. Optional set property list."
|
|
)]
|
|
public static void Tile_OnCommand(CommandEventArgs e)
|
|
{
|
|
Internal_OnCommand(e, false);
|
|
}
|
|
|
|
[Usage("TileRXYZ <x> <y> <w> <h> <z> <name> [params] [set {<propertyName> <value> ...}]")]
|
|
[Description(
|
|
"Tiles an item or npc by name into a given bounding box, (x, y) parameters are relative to your characters position. Optional constructor parameters. Optional set property list."
|
|
)]
|
|
public static void TileRXYZ_OnCommand(CommandEventArgs e)
|
|
{
|
|
InternalRXYZ_OnCommand(e, false);
|
|
}
|
|
|
|
[Usage("TileXYZ <x> <y> <w> <h> <z> <name> [params] [set {<propertyName> <value> ...}]")]
|
|
[Description(
|
|
"Tiles an item or npc by name into a given bounding box. Optional constructor parameters. Optional set property list."
|
|
)]
|
|
public static void TileXYZ_OnCommand(CommandEventArgs e)
|
|
{
|
|
InternalXYZ_OnCommand(e, false);
|
|
}
|
|
|
|
[Usage("TileZ <z> <name> [params] [set {<propertyName> <value> ...}]")]
|
|
[Description(
|
|
"Tiles an item or npc by name into a targeted bounding box at a fixed Z location. Optional constructor parameters. Optional set property list."
|
|
)]
|
|
public static void TileZ_OnCommand(CommandEventArgs e)
|
|
{
|
|
InternalZ_OnCommand(e, false);
|
|
}
|
|
|
|
[Usage("TileAvg <name> [params] [set {<propertyName> <value> ...}]")]
|
|
[Description(
|
|
"Tiles an item or npc by name into a targeted bounding box on the map's average Z elevation. Optional constructor parameters. Optional set property list."
|
|
)]
|
|
public static void TileAvg_OnCommand(CommandEventArgs e)
|
|
{
|
|
InternalAvg_OnCommand(e, false);
|
|
}
|
|
|
|
[Usage("Outline <name> [params] [set {<propertyName> <value> ...}]")]
|
|
[Description(
|
|
"Tiles an item or npc by name around a targeted bounding box. Optional constructor parameters. Optional set property list."
|
|
)]
|
|
public static void Outline_OnCommand(CommandEventArgs e)
|
|
{
|
|
Internal_OnCommand(e, true);
|
|
}
|
|
|
|
[Usage("OutlineRXYZ <x> <y> <w> <h> <z> <name> [params] [set {<propertyName> <value> ...}]")]
|
|
[Description(
|
|
"Tiles an item or npc by name around a given bounding box, (x, y) parameters are relative to your characters position. Optional constructor parameters. Optional set property list."
|
|
)]
|
|
public static void OutlineRXYZ_OnCommand(CommandEventArgs e)
|
|
{
|
|
InternalRXYZ_OnCommand(e, true);
|
|
}
|
|
|
|
[Usage("OutlineXYZ <x> <y> <w> <h> <z> <name> [params] [set {<propertyName> <value> ...}]")]
|
|
[Description(
|
|
"Tiles an item or npc by name around a given bounding box. Optional constructor parameters. Optional set property list."
|
|
)]
|
|
public static void OutlineXYZ_OnCommand(CommandEventArgs e)
|
|
{
|
|
InternalXYZ_OnCommand(e, true);
|
|
}
|
|
|
|
[Usage("OutlineZ <z> <name> [params] [set {<propertyName> <value> ...}]")]
|
|
[Description(
|
|
"Tiles an item or npc by name around a targeted bounding box at a fixed Z location. Optional constructor parameters. Optional set property list."
|
|
)]
|
|
public static void OutlineZ_OnCommand(CommandEventArgs e)
|
|
{
|
|
InternalZ_OnCommand(e, true);
|
|
}
|
|
|
|
[Usage("OutlineAvg <name> [params] [set {<propertyName> <value> ...}]")]
|
|
[Description(
|
|
"Tiles an item or npc by name around a targeted bounding box on the map's average Z elevation. Optional constructor parameters. Optional set property list."
|
|
)]
|
|
public static void OutlineAvg_OnCommand(CommandEventArgs e)
|
|
{
|
|
InternalAvg_OnCommand(e, true);
|
|
}
|
|
|
|
private enum TileZType
|
|
{
|
|
Start,
|
|
Fixed,
|
|
MapAverage
|
|
}
|
|
|
|
private class TileState
|
|
{
|
|
public readonly string[] m_Args;
|
|
public readonly int m_FixedZ;
|
|
public readonly bool m_Outline;
|
|
public readonly TileZType m_ZType;
|
|
|
|
public TileState(TileZType zType, int fixedZ, string[] args, bool outline)
|
|
{
|
|
m_ZType = zType;
|
|
m_FixedZ = fixedZ;
|
|
m_Args = args;
|
|
m_Outline = outline;
|
|
}
|
|
}
|
|
}
|