feat: Adds AdvancedSearch (XmlFind) (#1649)
### Summary Adds Advanced Search (XmlFind) using `[AS` command. Notable differences from XmlFind: * No save to file. * No "Display from" option (might add that later). * No advanced XmlSpawner property test syntax. Only basic property operator comparisons. * This version will freeze your shard, but fans out to all processors just like world saves. (So it should be stupid fast, especially if you have lots of cores) * Does not support spawn entry/type/error searching. We have a spawn search command to do that already. * I haven't set up correct "defaults" for the options when you first open it. I'll do that later. ### Screenshots <img width="575" alt="image" src="https://github.com/modernuo/ModernUO/assets/3953314/2b08e586-0ea6-41c4-b7cd-f70e2ee62b86"> <img width="575" alt="image" src="https://github.com/modernuo/ModernUO/assets/3953314/da3f16b9-6a9e-4d2c-9d93-6cdfa67b4ddc"> <img width="572" alt="image" src="https://github.com/modernuo/ModernUO/assets/3953314/ae1d0a3f-a30d-497c-99ea-2cc988147f8f">
This commit is contained in:
parent
fe6e9dfe5c
commit
fbbc407954
10 changed files with 2129 additions and 0 deletions
962
Projects/UOContent/Engines/Advanced Search/AdvancedSearchGump.cs
Normal file
962
Projects/UOContent/Engines/Advanced Search/AdvancedSearchGump.cs
Normal file
|
|
@ -0,0 +1,962 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using CommunityToolkit.HighPerformance;
|
||||
using Server.Commands;
|
||||
using Server.Commands.Generic;
|
||||
using Server.Engines.Spawners;
|
||||
using Server.Gumps;
|
||||
using Server.Network;
|
||||
using Server.Saves;
|
||||
|
||||
namespace Server.Engines.AdvancedSearch;
|
||||
|
||||
[Flags]
|
||||
public enum AdvancedSearchGumpOptions : long
|
||||
{
|
||||
None,
|
||||
SortDescending = 0x00000001,
|
||||
SortByType = 0x00000002,
|
||||
SortByName = 0x00000004,
|
||||
SortByRange = 0x00000008,
|
||||
SortByMap = 0x00000010,
|
||||
SortBySelected = 0x00000020,
|
||||
AllSelected = 0x00000040
|
||||
}
|
||||
|
||||
public class AdvancedSearchGump : Gump
|
||||
{
|
||||
private const int MaxEntries = 18;
|
||||
|
||||
private static int _threadId;
|
||||
private static AdvancedSearchThreadWorker[] _threadWorkers;
|
||||
|
||||
private static void Configure()
|
||||
{
|
||||
EventSink.Shutdown += Shutdown;
|
||||
EventSink.ServerCrashed += OnCrashed;
|
||||
}
|
||||
|
||||
private static void OnCrashed(ServerCrashedEventArgs obj)
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
private static void Shutdown()
|
||||
{
|
||||
if (_threadWorkers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _threadWorkers.Length; i++)
|
||||
{
|
||||
_threadWorkers[i].Exit();
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly AdvancedSearchFilter DefaultFilter = new()
|
||||
{
|
||||
FilterType = true,
|
||||
FilterFelucca = true,
|
||||
HideValidInternalMap = true,
|
||||
Type = typeof(Spawner),
|
||||
};
|
||||
|
||||
private AdvancedSearchGumpOptions _options;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool GetOptionsFlag(AdvancedSearchGumpOptions option) => (_options & option) != 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetOptionsFlag(AdvancedSearchGumpOptions option, bool value) =>
|
||||
_options = value ? _options | option : _options & ~option;
|
||||
|
||||
public bool SortDescending
|
||||
{
|
||||
get => GetOptionsFlag(AdvancedSearchGumpOptions.SortDescending);
|
||||
set => SetOptionsFlag(AdvancedSearchGumpOptions.SortDescending, value);
|
||||
}
|
||||
|
||||
public bool SortByType
|
||||
{
|
||||
get => GetOptionsFlag(AdvancedSearchGumpOptions.SortByType);
|
||||
set => SetOptionsFlag(AdvancedSearchGumpOptions.SortByType, value);
|
||||
}
|
||||
|
||||
public bool SortByName
|
||||
{
|
||||
get => GetOptionsFlag(AdvancedSearchGumpOptions.SortByName);
|
||||
set => SetOptionsFlag(AdvancedSearchGumpOptions.SortByName, value);
|
||||
}
|
||||
|
||||
public bool SortByRange
|
||||
{
|
||||
get => GetOptionsFlag(AdvancedSearchGumpOptions.SortByRange);
|
||||
set => SetOptionsFlag(AdvancedSearchGumpOptions.SortByRange, value);
|
||||
}
|
||||
|
||||
public bool SortByMap
|
||||
{
|
||||
get => GetOptionsFlag(AdvancedSearchGumpOptions.SortByMap);
|
||||
set => SetOptionsFlag(AdvancedSearchGumpOptions.SortByMap, value);
|
||||
}
|
||||
|
||||
public bool SortBySelected
|
||||
{
|
||||
get => GetOptionsFlag(AdvancedSearchGumpOptions.SortBySelected);
|
||||
set => SetOptionsFlag(AdvancedSearchGumpOptions.SortBySelected, value);
|
||||
}
|
||||
|
||||
public bool AllSelected
|
||||
{
|
||||
get => GetOptionsFlag(AdvancedSearchGumpOptions.AllSelected);
|
||||
set => SetOptionsFlag(AdvancedSearchGumpOptions.AllSelected, value);
|
||||
}
|
||||
|
||||
public AdvancedSearchFilter Filter { get; set; } = DefaultFilter with {};
|
||||
|
||||
public AdvancedSearchResult[] SearchResults { get; set; }
|
||||
public int DisplayFrom { get; set; }
|
||||
public string CommandString { get; set; }
|
||||
|
||||
public AdvancedSearchGump(Mobile from) : base(50, 50) => Build(from);
|
||||
|
||||
private void Build(Mobile from)
|
||||
{
|
||||
const int height = 500;
|
||||
var haveResults = SearchResults?.Length > 0;
|
||||
var width = haveResults ? 755 : 170;
|
||||
|
||||
AddBackground(0, 0, width, height, 5054);
|
||||
AddAlphaRegion(0, 0, width, height);
|
||||
|
||||
/// Sorting
|
||||
|
||||
var y = 5;
|
||||
|
||||
AddButton(5, y, 0xFAB, 0xFAD, 700);
|
||||
AddLabel(38, y, 0x384, "Sort");
|
||||
|
||||
if (SortDescending)
|
||||
{
|
||||
AddButton(75, y + 3, 0x15E2, 0x15E6, 701);
|
||||
AddLabel(95, y, 0x384, "Desc");
|
||||
}
|
||||
else
|
||||
{
|
||||
AddButton(75, y + 3, 0x15E0, 0x15E4, 701);
|
||||
AddLabel(95, y, 0x384, "Asc");
|
||||
}
|
||||
|
||||
y += 22;
|
||||
AddRadio(5, y, 0xD2, 0xD3, SortByType, 0);
|
||||
AddLabel(28, y, 0x384, "Type");
|
||||
|
||||
AddRadio(75, y, 0xD2, 0xD3, SortByName, 1);
|
||||
AddLabel(98, y, 0x384, "Name");
|
||||
|
||||
y += 20;
|
||||
AddRadio(5, y, 0xD2, 0xD3, SortByRange, 2);
|
||||
AddLabel(28, y, 0x384, "Range");
|
||||
|
||||
AddRadio(75, y, 0xD2, 0xD3, SortByMap, 3);
|
||||
AddLabel(98, y, 0x384, "Map");
|
||||
|
||||
y += 20;
|
||||
AddRadio(5, y, 0xD2, 0xD3, SortBySelected, 4);
|
||||
AddLabel(28, y, 0x384, "Select");
|
||||
|
||||
/// Searching
|
||||
|
||||
y = 85;
|
||||
AddButton(5, y, 0xFA8, 0xFAA, 3);
|
||||
AddLabel(38, y, 0x384, "Search");
|
||||
|
||||
y += 20;
|
||||
AddCheck(5, y, 0xD2, 0xD3, Filter!.FilterInternalMap, 312);
|
||||
AddLabel(28, y, 0x384, "Internal");
|
||||
AddCheck(75, y, 0xD2, 0xD3, Filter.FilterNullMap, 314);
|
||||
AddLabel(98, y, 0x384, "Null");
|
||||
|
||||
y += 20;
|
||||
AddCheck(5, y, 0xD2, 0xD3, Filter.FilterFelucca, 308);
|
||||
AddLabel(28, y, 0x384, "Fel");
|
||||
AddCheck(75, y, 0xD2, 0xD3, Filter.FilterTrammel, 309);
|
||||
AddLabel(98, y, 0x384, "Tram");
|
||||
|
||||
y += 20;
|
||||
AddCheck(5, y, 0xD2, 0xD3, Filter.FilterMalas, 310);
|
||||
AddLabel(28, y, 0x384, "Mal");
|
||||
AddCheck(75, y, 0xD2, 0xD3, Filter.FilterIlshenar, 311);
|
||||
AddLabel(98, y, 0x384, "Ilsh");
|
||||
|
||||
y += 20;
|
||||
AddCheck(5, y, 0xD2, 0xD3, Filter.FilterTokuno, 318);
|
||||
AddLabel(28, y, 0x384, "Tok");
|
||||
AddCheck(75, y, 0xD2, 0xD3, Filter.FilterTerMur, 320);
|
||||
AddLabel(98, y, 0x384, "Ter");
|
||||
|
||||
y += 20;
|
||||
AddCheck(5, y, 0xD2, 0xD3, Filter.HideValidInternalMap, 316);
|
||||
AddLabel(28, y, 0x384, "Hide valid internal");
|
||||
|
||||
/// Filter
|
||||
|
||||
y = height - 295;
|
||||
|
||||
AddLabel(28, y, 0x384, "Region");
|
||||
AddImageTiled(70, y, 68, 19, 0xBBC);
|
||||
AddTextEntry(70, y, 250, 19, 0, 106, Filter.RegionName);
|
||||
AddCheck(5, y, 0xD2, 0xD3, Filter.FilterRegion, 319);
|
||||
|
||||
y += 20;
|
||||
AddLabel(28, y, 0x384, "Age");
|
||||
AddImageTiled(70, y, 68, 19, 0xBBC);
|
||||
AddTextEntry(70, y, 250, 19, 0, 105, Filter.Age.ToString());
|
||||
AddCheck(5, y, 0xD2, 0xD3, Filter.FilterAge, 303);
|
||||
AddCheck(50, y + 2, 0x1467, 0x1468, Filter.FilterAgeDirection, 302);
|
||||
|
||||
y += 20;
|
||||
AddLabel(28, y, 0x384, "Range");
|
||||
AddImageTiled(70, y, 45, 19, 0xBBC);
|
||||
AddTextEntry(70, y, 45, 19, 0, 100, Filter.Range.ToString());
|
||||
AddCheck(5, y, 0xD2, 0xD3, Filter.FilterRange, 304);
|
||||
|
||||
y += 20;
|
||||
AddLabel(28, y, 0x384, "Type");
|
||||
AddCheck(5, y, 0xD2, 0xD3, Filter.FilterType, 305);
|
||||
AddImageTiled(6, y + 20, 132, 19, 0xBBC);
|
||||
AddTextEntry(6, y + 20, 250, 19, 0, 101, Filter.Type?.Name);
|
||||
|
||||
y += 41;
|
||||
AddLabel(28, y, 0x384, "Property Test");
|
||||
AddCheck(5, y, 0xD2, 0xD3, Filter.FilterPropertyTest, 315);
|
||||
AddImageTiled(6, y + 20, 132, 19, 0xBBC);
|
||||
AddTextEntry(6, y + 20, 500, 19, 0, 104, Filter.PropertyTest);
|
||||
|
||||
y += 41;
|
||||
AddLabel(28, y, 0x384, "Name");
|
||||
AddCheck(5, y, 0xD2, 0xD3, Filter.FilterName, 306);
|
||||
AddImageTiled(6, y + 20, 132, 19, 0xBBC);
|
||||
AddTextEntry(6, y + 20, 250, 19, 0, 102, Filter.Name);
|
||||
|
||||
if (!haveResults)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/// Control Buttons
|
||||
|
||||
y = height - 25;
|
||||
|
||||
AddButton(150, y, 0xFB1, 0xFB3, 156);
|
||||
AddLabel(183, y, 0x384, "Delete");
|
||||
|
||||
// For spawners in the list
|
||||
AddButton(230, y, 0xFA2, 0xFA3, 157);
|
||||
AddLabel(263, y, 0x384, "Reset");
|
||||
|
||||
AddButton(310, y, 0xFA8, 0xFAA, 158);
|
||||
AddLabel(343, y, 0x384, "Respawn");
|
||||
|
||||
AddButton(5, y, 0xFAE, 0xFAF, 154);
|
||||
AddLabel(38, y, 0x384, "Bring");
|
||||
|
||||
// AddButton(150, y - 25, 0xFA8, 0xFAA, 159);
|
||||
// AddLabel(183, y - 25, 0x384, "Save to file:");
|
||||
//
|
||||
// AddImageTiled(270, y - 25, 180, 19, 0xBBC);
|
||||
// AddTextEntry(270, y - 25, 180, 19, 0, 300, SaveFilename);
|
||||
|
||||
AddButton(470, y - 25, 0xFA8, 0xFAA, 160);
|
||||
AddLabel(503, y - 25, 0x384, "Command:");
|
||||
|
||||
AddImageTiled(560, y - 25, 180, 19, 0xBBC);
|
||||
AddTextEntry(560, y - 25, 180, 19, 0, 301, CommandString);
|
||||
|
||||
if (DisplayFrom > 0)
|
||||
{
|
||||
AddButton(395, height - 25, 0x15E3, 0x15E7, 202); // backward
|
||||
}
|
||||
|
||||
if (DisplayFrom + MaxEntries < SearchResults.Length)
|
||||
{
|
||||
AddButton(415 + 25, height - 25, 0x15E1, 0x15E5, 201); // forward
|
||||
}
|
||||
|
||||
if (SearchResults?.Length > 0)
|
||||
{
|
||||
var maxEntryIndex = Math.Min(DisplayFrom + MaxEntries, SearchResults.Length - 1);
|
||||
|
||||
/// Headers
|
||||
AddLabel(143, 5, 0x384, "Gump");
|
||||
AddLabel(178, 5, 0x384, "Prop");
|
||||
AddLabel(210, 5, 0x384, "Goto");
|
||||
AddLabel(250, 5, 0x384, "Name");
|
||||
AddLabel(365, 5, 0x384, "Type");
|
||||
AddLabel(460, 5, 0x384, "Location");
|
||||
AddLabel(578, 5, 0x384, "Map");
|
||||
AddLabel(650, 5, 0x384, "Owner");
|
||||
|
||||
AddLabel(180, y - 50, 68, $"Found {SearchResults.Length} items/mobiles");
|
||||
AddLabel(400, y - 50, 68,
|
||||
$"Displaying {DisplayFrom + 1}-{maxEntryIndex + 1}"
|
||||
);
|
||||
|
||||
// Count the number of selected objects
|
||||
int count = 0;
|
||||
foreach (AdvancedSearchResult e in SearchResults)
|
||||
{
|
||||
if (e.Selected)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
AddLabel(600, y - 50, 33, $"Selected {count}");
|
||||
|
||||
AddLabel(610, y, 0x384, "Select All");
|
||||
|
||||
// display the select-all toggle
|
||||
AddButton(670, y, AllSelected ? 0xD3 : 0xD2, AllSelected ? 0xD2 : 0xD3, 1);
|
||||
|
||||
var allDisplayedSelected = true;
|
||||
for (int i = 0; i < MaxEntries; i++)
|
||||
{
|
||||
var index = i + DisplayFrom;
|
||||
if (index >= SearchResults.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var entry = SearchResults[index];
|
||||
|
||||
if (!entry.Selected)
|
||||
{
|
||||
allDisplayedSelected = false;
|
||||
}
|
||||
|
||||
AddImageTiled(235, 22 * i + 30, 386, 23, 0x52);
|
||||
AddImageTiled(236, 22 * i + 31, 384, 21, 0xBBC);
|
||||
|
||||
if (entry.Entity is BaseSpawner)
|
||||
{
|
||||
AddButton(145, 22 * i + 30, 0xFBD, 0xFBE, 2000 + i);
|
||||
}
|
||||
|
||||
// Goto button
|
||||
AddButton(205, 22 * i + 30, 0xFAE, 0xFAF, 1000 + i);
|
||||
|
||||
// Interface button
|
||||
AddButton(175, 22 * i + 30, 0xFAB, 0xFAD, 3000 + i);
|
||||
|
||||
var textHue = 0;
|
||||
var parentName = "";
|
||||
var deleted = entry.Entity?.Deleted != false;
|
||||
if (deleted)
|
||||
{
|
||||
entry.Entity = null; // Release this so we don't have hanging references
|
||||
}
|
||||
|
||||
var loc = entry.GetLocation();
|
||||
var map = entry.GetMap();
|
||||
|
||||
if (entry.Parent is Mobile parentMob)
|
||||
{
|
||||
textHue = parentMob.Player ? 44 : 24;
|
||||
parentName = parentMob.Name;
|
||||
}
|
||||
else if (entry.Parent is Item parentItem)
|
||||
{
|
||||
textHue = 5;
|
||||
parentName = parentItem.Name ?? parentItem.ItemData.Name;
|
||||
}
|
||||
|
||||
// Name
|
||||
AddLabelCropped(248, 22 * i + 31, 110, 21, deleted ? 5 : 0, entry.Name);
|
||||
|
||||
// Type
|
||||
AddImageTiled(360, 22 * i + 31, 90, 21, 0xBBC);
|
||||
AddLabelCropped(360, 22 * i + 31, 90, 21, 0, entry.Type.Name);
|
||||
|
||||
// Location
|
||||
AddImageTiled(450, 22 * i + 31, 137, 21, 0xBBC);
|
||||
AddLabel(450, 22 * i + 31, 0, loc.ToString());
|
||||
|
||||
// Map
|
||||
AddImageTiled(571, 22 * i + 31, 70, 21, 0xBBC);
|
||||
AddLabel(571, 22 * i + 31, 0, map?.Name ?? "(-null-)");
|
||||
|
||||
// Parent
|
||||
AddImageTiled(640, 22 * i + 31, 90, 21, 0xBBC);
|
||||
AddLabelCropped(640, 22 * i + 31, 90, 21, textHue, parentName);
|
||||
|
||||
// display the selection button
|
||||
AddButton(730, 22 * i + 32, entry.Selected ? 0xD3 : 0xD2, entry.Selected ? 0xD2 : 0xD3, 4000 + i);
|
||||
}
|
||||
|
||||
AddButton(730, 5, allDisplayedSelected ? 0xD3 : 0xD2, allDisplayedSelected ? 0xD2 : 0xD3, 2); // Select all displayed
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState state, RelayInfo info)
|
||||
{
|
||||
var from = state.Mobile;
|
||||
if (from == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetSortSwitches(info.Switches.Length > 0 ? info.Switches[0] : -1);
|
||||
|
||||
Filter ??= new AdvancedSearchFilter();
|
||||
|
||||
Filter.FilterAgeDirection = info.IsSwitched(302);
|
||||
Filter.FilterAge = info.IsSwitched(303);
|
||||
Filter.FilterRange = info.IsSwitched(304);
|
||||
Filter.FilterType = info.IsSwitched(305);
|
||||
Filter.FilterName = info.IsSwitched(306);
|
||||
Filter.FilterFelucca = info.IsSwitched(308);
|
||||
Filter.FilterTrammel = info.IsSwitched(309);
|
||||
Filter.FilterMalas = info.IsSwitched(310);
|
||||
Filter.FilterIlshenar = info.IsSwitched(311);
|
||||
Filter.FilterInternalMap = info.IsSwitched(312);
|
||||
Filter.FilterNullMap = info.IsSwitched(314);
|
||||
Filter.FilterPropertyTest = info.IsSwitched(315);
|
||||
Filter.HideValidInternalMap = info.IsSwitched(316);
|
||||
Filter.FilterTokuno = info.IsSwitched(318);
|
||||
Filter.FilterRegion = info.IsSwitched(319);
|
||||
Filter.FilterTerMur = info.IsSwitched(320);
|
||||
|
||||
var rangeText = info.GetTextEntry(100)?.Text;
|
||||
Filter.Range = rangeText != null ? Utility.ToInt32(rangeText) : null;
|
||||
|
||||
var filterText = info.GetTextEntry(101)?.Text;
|
||||
Filter.Type = filterText != null ? AssemblyHandler.FindTypeByName(filterText) : null;
|
||||
|
||||
Filter.Name = info.GetTextEntry(102)?.Text;
|
||||
Filter.PropertyTest = info.GetTextEntry(104)?.Text;
|
||||
|
||||
var ageText = info.GetTextEntry(105)?.Text;
|
||||
Filter.Age = ageText != null ? Utility.ToTimeSpan(ageText) : null;
|
||||
|
||||
Filter.RegionName = info.GetTextEntry(106)?.Text;
|
||||
|
||||
CommandString = info.GetTextEntry(301)?.Text;
|
||||
|
||||
var buttonId = info.ButtonID;
|
||||
|
||||
switch (buttonId)
|
||||
{
|
||||
case 0: // Close
|
||||
{
|
||||
return;
|
||||
}
|
||||
case 1: // Select all toggle
|
||||
{
|
||||
AllSelected = !AllSelected;
|
||||
|
||||
if (SearchResults?.Length > 0)
|
||||
{
|
||||
for (var i = 0; i < SearchResults.Length; i++)
|
||||
{
|
||||
SearchResults[i].Selected = AllSelected;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 2: // Select all displayed
|
||||
{
|
||||
if (SearchResults?.Length > 0)
|
||||
{
|
||||
var allSelected = true;
|
||||
var max = DisplayFrom + MaxEntries;
|
||||
for (var i = DisplayFrom; i < max; i++)
|
||||
{
|
||||
if (i >= SearchResults.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var entry = SearchResults[i];
|
||||
if (!entry.Selected)
|
||||
{
|
||||
allSelected = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = DisplayFrom; i < max; i++)
|
||||
{
|
||||
if (i >= SearchResults.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var entry = SearchResults[i];
|
||||
entry.Selected = !allSelected;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: // Search
|
||||
{
|
||||
DoSearch(from);
|
||||
break;
|
||||
}
|
||||
case 154: // Bring
|
||||
{
|
||||
if (SearchResults?.Length > 0)
|
||||
{
|
||||
var count = 0;
|
||||
for (var i = 0; i < SearchResults.Length; i++)
|
||||
{
|
||||
if (SearchResults[i].Selected)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
from.SendGump(new AdvancedSearchConfirmBringGump(this, count));
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case 156: // Delete
|
||||
{
|
||||
if (SearchResults?.Length > 0)
|
||||
{
|
||||
var count = 0;
|
||||
for (var i = 0; i < SearchResults.Length; i++)
|
||||
{
|
||||
if (SearchResults[i].Selected)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
from.SendGump(new AdvancedSearchConfirmDeleteGump(this, count));
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case 157: // Reset
|
||||
{
|
||||
if (SearchResults?.Length > 0)
|
||||
{
|
||||
for (var i = 0; i < SearchResults.Length; i++)
|
||||
{
|
||||
var entry = SearchResults[i];
|
||||
|
||||
if (entry.Selected)
|
||||
{
|
||||
(entry.Entity as Spawner)?.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 158: // Respawn
|
||||
{
|
||||
if (SearchResults?.Length > 0)
|
||||
{
|
||||
for (var i = 0; i < SearchResults.Length; i++)
|
||||
{
|
||||
var entry = SearchResults[i];
|
||||
|
||||
if (entry.Selected)
|
||||
{
|
||||
(entry.Entity as Spawner)?.Respawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 160: // Command
|
||||
{
|
||||
ExecuteCommand(from, CommandString);
|
||||
break;
|
||||
}
|
||||
case 201: // Forward
|
||||
{
|
||||
if (SearchResults?.Length > 0)
|
||||
{
|
||||
var maxEntryIndex = Math.Min(DisplayFrom + MaxEntries, SearchResults.Length - 1);
|
||||
|
||||
if (maxEntryIndex < SearchResults.Length - 1)
|
||||
{
|
||||
DisplayFrom = maxEntryIndex;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 202: // Backward
|
||||
{
|
||||
if (SearchResults?.Length > 0)
|
||||
{
|
||||
DisplayFrom = Math.Clamp(DisplayFrom - MaxEntries, 0, SearchResults.Length - 1);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 700: // Sort
|
||||
{
|
||||
Sort(from);
|
||||
break;
|
||||
}
|
||||
case 701: // Change sort order
|
||||
{
|
||||
SortDescending = !SortDescending;
|
||||
|
||||
var radioSwitch = info.Switches.Length > 0 ? info.Switches[0] : -1;
|
||||
if (radioSwitch is < 0 or > 4)
|
||||
{
|
||||
Array.Reverse(SearchResults);
|
||||
}
|
||||
else
|
||||
{
|
||||
Sort(from);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case >= 1000 and <= 1999: // Goto
|
||||
{
|
||||
var index = buttonId - 1000 + DisplayFrom;
|
||||
|
||||
if (!(SearchResults?.Length > 0) || index >= SearchResults.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var entry = SearchResults[index];
|
||||
var loc = entry.GetLocation();
|
||||
var map = entry.GetMap();
|
||||
|
||||
if (map == null || map == Map.Internal)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
from.MoveToWorld(loc, map);
|
||||
break;
|
||||
}
|
||||
case <= 2999: // Open Spawner
|
||||
{
|
||||
var index = buttonId - 2000 + DisplayFrom;
|
||||
|
||||
if (!(SearchResults?.Length > 0) || index >= SearchResults.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var entry = SearchResults[index];
|
||||
if (entry.Entity?.Deleted != false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (entry.Entity.Map == null || entry.Entity.Map == Map.Internal)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Resend(from);
|
||||
// Open the spawner
|
||||
(entry.Entity as BaseSpawner)?.OnDoubleClick(from);
|
||||
|
||||
return;
|
||||
}
|
||||
case <= 3999: // Props
|
||||
{
|
||||
var index = buttonId - 3000 + DisplayFrom;
|
||||
|
||||
if (!(SearchResults?.Length > 0) || index >= SearchResults.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var entry = SearchResults[index];
|
||||
var entity = entry.Entity;
|
||||
|
||||
if (entity?.Deleted != false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!BaseCommand.IsAccessible(from, entity))
|
||||
{
|
||||
from.SendLocalizedMessage(500447); // That is not accessible.
|
||||
break;
|
||||
}
|
||||
|
||||
Resend(from);
|
||||
from.SendGump(new PropertiesGump(from, entry.Entity));
|
||||
|
||||
return;
|
||||
}
|
||||
case <= 4999: // Select
|
||||
{
|
||||
var index = buttonId - 4000 + DisplayFrom;
|
||||
|
||||
if (!(SearchResults?.Length > 0) || index >= SearchResults.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var entry = SearchResults[index];
|
||||
entry.Selected = !entry.Selected;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Resend(from);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void PushToWorkers(IEntity e)
|
||||
{
|
||||
_threadWorkers[_threadId++].Push(e);
|
||||
if (_threadId == _threadWorkers.Length)
|
||||
{
|
||||
_threadId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void DoSearch(Mobile from)
|
||||
{
|
||||
if (!World.Running)
|
||||
{
|
||||
from.SendMessage("You cannot search while the world is saving.");
|
||||
return;
|
||||
}
|
||||
|
||||
var autoSave = AutoSave.SavesEnabled;
|
||||
if (autoSave)
|
||||
{
|
||||
AutoSave.SavesEnabled = false;
|
||||
}
|
||||
|
||||
_threadWorkers ??= new AdvancedSearchThreadWorker[Math.Max(Environment.ProcessorCount - 1, 1)];
|
||||
|
||||
var ignoreQueue = new ConcurrentQueue<IEntity>();
|
||||
var results = new ConcurrentQueue<AdvancedSearchResult>();
|
||||
var worldLocation = new WorldLocation(from.Location, from.Map);
|
||||
|
||||
for (var i = 0; i < _threadWorkers.Length; i++)
|
||||
{
|
||||
(_threadWorkers[i] ??= new AdvancedSearchThreadWorker()).Wake(worldLocation, Filter, results, ignoreQueue);
|
||||
}
|
||||
|
||||
var type = Filter.FilterType ? Filter.Type : null;
|
||||
|
||||
// Push the entities
|
||||
foreach (var item in World.Items.Values)
|
||||
{
|
||||
if (type == null || type.IsInstanceOfType(item))
|
||||
{
|
||||
PushToWorkers(item);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var m in World.Mobiles.Values)
|
||||
{
|
||||
if (type == null || type.IsInstanceOfType(m))
|
||||
{
|
||||
PushToWorkers(m);
|
||||
}
|
||||
}
|
||||
|
||||
// Block until everything is processed
|
||||
for (var i = 0; i < _threadWorkers.Length; i++)
|
||||
{
|
||||
_threadWorkers[i].Sleep();
|
||||
}
|
||||
|
||||
var ignoredEntities = new HashSet<IEntity>(ignoreQueue);
|
||||
|
||||
// Force the GC to collect the ignored entities
|
||||
ignoreQueue.Clear();
|
||||
|
||||
var resultsList = new List<AdvancedSearchResult>(results.Count);
|
||||
foreach (var result in results)
|
||||
{
|
||||
if (!ignoredEntities.Contains(result.Entity))
|
||||
{
|
||||
resultsList.Add(result);
|
||||
}
|
||||
}
|
||||
|
||||
SearchResults = resultsList.ToArray();
|
||||
|
||||
// Force the GC to collect the results
|
||||
resultsList.Clear();
|
||||
|
||||
AutoSave.SavesEnabled = autoSave;
|
||||
}
|
||||
|
||||
private void SetSortSwitches(int radioSwitch)
|
||||
{
|
||||
SortByType = radioSwitch == 0;
|
||||
SortByName = radioSwitch == 1;
|
||||
SortByRange = radioSwitch == 2;
|
||||
SortByMap = radioSwitch == 3;
|
||||
SortBySelected = radioSwitch == 4;
|
||||
}
|
||||
|
||||
public void Sort(Mobile from)
|
||||
{
|
||||
if (SearchResults == null || SearchResults.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IComparer<AdvancedSearchResult> comparer;
|
||||
if (SortByType)
|
||||
{
|
||||
comparer = SortDescending
|
||||
? AdvancedSearchResultTypeComparer.InstanceReverse
|
||||
: AdvancedSearchResultTypeComparer.Instance;
|
||||
}
|
||||
else if (SortByName)
|
||||
{
|
||||
comparer = SortDescending
|
||||
? AdvancedSearchResultNameComparer.InstanceReverse
|
||||
: AdvancedSearchResultNameComparer.Instance;
|
||||
}
|
||||
else if (SortByRange)
|
||||
{
|
||||
comparer = new AdvancedSearchRangeComparer(from, SortDescending);
|
||||
}
|
||||
else if (SortByMap)
|
||||
{
|
||||
comparer = SortDescending
|
||||
? AdvancedSearchResultMapComparer.InstanceReverse
|
||||
: AdvancedSearchResultMapComparer.Instance;
|
||||
}
|
||||
else if (SortBySelected)
|
||||
{
|
||||
comparer = SortDescending
|
||||
? AdvancedSearchResultSelectedComparer.InstanceReverse
|
||||
: AdvancedSearchResultSelectedComparer.Instance;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Array.Sort(SearchResults, comparer);
|
||||
}
|
||||
|
||||
private void ExecuteCommand(Mobile from, string commandString)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(commandString))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var list = new List<object>();
|
||||
|
||||
if (SearchResults?.Length > 0)
|
||||
{
|
||||
for (var i = 0; i < SearchResults.Length; i++)
|
||||
{
|
||||
var entry = SearchResults[i];
|
||||
|
||||
if (entry.Selected && entry.Entity != null)
|
||||
{
|
||||
list.Add(entry.Entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (list.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var commandSpan = commandString.AsSpan().Trim();
|
||||
|
||||
string command = null;
|
||||
string[] commandArgs = new string[commandSpan.Count(" ")];
|
||||
|
||||
var index = 0;
|
||||
foreach (var part in commandSpan.Tokenize(' '))
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
command = part.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
commandArgs[index - 1] = part.ToString();
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
BaseCommand cmd = null;
|
||||
|
||||
foreach (var c in TargetCommands.AllCommands)
|
||||
{
|
||||
for (var i = 0; i < c.Commands.Length; i++)
|
||||
{
|
||||
if (command.InsensitiveEquals(c.Commands[i]))
|
||||
{
|
||||
cmd = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd == null)
|
||||
{
|
||||
from.SendMessage($"Invalid command: {commandSpan}");
|
||||
return;
|
||||
}
|
||||
|
||||
CommandEventArgs cmdEventArgs = new CommandEventArgs(from, command, commandString, commandArgs);
|
||||
|
||||
bool flushToLog = false;
|
||||
|
||||
// execute the command on the objects in the list
|
||||
|
||||
if (list.Count > 20)
|
||||
{
|
||||
CommandLogging.Enabled = false;
|
||||
}
|
||||
|
||||
cmd.ExecuteList(cmdEventArgs, list);
|
||||
|
||||
if (list.Count > 20)
|
||||
{
|
||||
flushToLog = true;
|
||||
CommandLogging.Enabled = true;
|
||||
}
|
||||
|
||||
cmd.Flush(from, flushToLog);
|
||||
}
|
||||
|
||||
public void Resend(Mobile m)
|
||||
{
|
||||
TextEntries = 0;
|
||||
Switches = 0;
|
||||
Entries.Clear();
|
||||
Strings.Clear();
|
||||
Build(m);
|
||||
m.SendGump(this);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue