fix: complete retail parity stability pass
This commit is contained in:
parent
d3df4cb20a
commit
f7aa8e0eb7
131 changed files with 7765 additions and 1190 deletions
|
|
@ -1,3 +1,4 @@
|
|||
using System.Globalization;
|
||||
using AcDream.App.UI;
|
||||
using AcDream.Core.Chat;
|
||||
using AcDream.Core.Physics;
|
||||
|
|
@ -552,11 +553,355 @@ public sealed class ClientCommandControllerTests
|
|||
Assert.Equal("my chat log.txt", match.Arguments);
|
||||
}
|
||||
|
||||
// ── #361: retail's @day / @render ─────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Day_TogglesThePersistentOptionAndPrintsRetailsExactLines()
|
||||
{
|
||||
bool persistentDaylight = false;
|
||||
var values = new List<bool>();
|
||||
var messages = new List<string>();
|
||||
ClientCommandController controller = NewController(
|
||||
messages: messages,
|
||||
isPersistentDaylight: () => persistentDaylight,
|
||||
setPersistentDaylight: value =>
|
||||
{
|
||||
persistentDaylight = value;
|
||||
values.Add(value);
|
||||
});
|
||||
|
||||
controller.Execute(new ExecuteClientCommandCmd(
|
||||
ClientCommandId.TogglePersistentDaylight,
|
||||
"ignored exactly like retail"));
|
||||
controller.Execute(new ExecuteClientCommandCmd(
|
||||
ClientCommandId.TogglePersistentDaylight,
|
||||
string.Empty));
|
||||
|
||||
Assert.Equal([true, false], values);
|
||||
Assert.Equal(
|
||||
["Let there be light!", "Normality has been restored."],
|
||||
messages);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("radius 5", 5)]
|
||||
[InlineData("RADIUS 25 extra ignored", 25)]
|
||||
[InlineData("radius 12suffix", 12)]
|
||||
public void RenderRadius_AcceptsRetailRangeAndAtoiPrefix(
|
||||
string arguments,
|
||||
int expected)
|
||||
{
|
||||
var radii = new List<int>();
|
||||
var messages = new List<string>();
|
||||
ClientCommandController controller = NewController(
|
||||
messages: messages,
|
||||
setLandscapeRadius: radii.Add);
|
||||
|
||||
controller.Execute(new ExecuteClientCommandCmd(
|
||||
ClientCommandId.RenderOption,
|
||||
arguments));
|
||||
|
||||
Assert.Equal([expected], radii);
|
||||
Assert.Equal(["Landscape radius set"], messages);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("fov 10", 10f)]
|
||||
[InlineData("FOV 160 extra", 160f)]
|
||||
[InlineData("fov 91degrees", 91f)]
|
||||
public void RenderFov_AcceptsRetailRangeAndAtoiPrefix(
|
||||
string arguments,
|
||||
float expected)
|
||||
{
|
||||
var values = new List<float>();
|
||||
var messages = new List<string>();
|
||||
ClientCommandController controller = NewController(
|
||||
messages: messages,
|
||||
setFieldOfView: values.Add);
|
||||
|
||||
controller.Execute(new ExecuteClientCommandCmd(
|
||||
ClientCommandId.RenderOption,
|
||||
arguments));
|
||||
|
||||
Assert.Equal([expected], values);
|
||||
Assert.Equal(["Field of view set"], messages);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("radius", "Must specify a radius")]
|
||||
[InlineData("radius 4", "Radius must be between 5 and 25")]
|
||||
[InlineData("radius nope", "Radius must be between 5 and 25")]
|
||||
[InlineData("fov", "Must specify a field of view")]
|
||||
[InlineData("fov 161", "Field of view must be between 10 and 160")]
|
||||
public void Render_InvalidValuesPrintRetailsExactReply(
|
||||
string arguments,
|
||||
string expected)
|
||||
{
|
||||
var calls = new List<string>();
|
||||
var messages = new List<string>();
|
||||
ClientCommandController controller = NewController(
|
||||
calls,
|
||||
messages: messages);
|
||||
|
||||
controller.Execute(new ExecuteClientCommandCmd(
|
||||
ClientCommandId.RenderOption,
|
||||
arguments));
|
||||
|
||||
Assert.DoesNotContain(calls, call =>
|
||||
call.StartsWith("radius:", StringComparison.Ordinal)
|
||||
|| call.StartsWith("fov:", StringComparison.Ordinal));
|
||||
Assert.Equal([expected], messages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_UsageAndUnknownOptionMatchRetail()
|
||||
{
|
||||
var messages = new List<string>();
|
||||
ClientCommandController controller = NewController(messages: messages);
|
||||
|
||||
controller.Execute(new ExecuteClientCommandCmd(
|
||||
ClientCommandId.RenderOption,
|
||||
string.Empty));
|
||||
controller.Execute(new ExecuteClientCommandCmd(
|
||||
ClientCommandId.RenderOption,
|
||||
"usage"));
|
||||
controller.Execute(new ExecuteClientCommandCmd(
|
||||
ClientCommandId.RenderOption,
|
||||
"unknown 1"));
|
||||
|
||||
string usage = RetailCommandHelpTable.Render.TrimEnd('\n');
|
||||
Assert.Equal([usage, usage], messages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllegianceAdministration_ExecutesEveryRetailDispatcherBranch()
|
||||
{
|
||||
var calls = new List<string>();
|
||||
var system = new List<string>();
|
||||
var clientLocal = new List<string>();
|
||||
ClientCommandController controller = NewController(
|
||||
calls: calls,
|
||||
messages: system,
|
||||
clientLocalMessages: clientLocal);
|
||||
|
||||
Execute(ClientCommandId.AllegianceInfo, "Lord Bob");
|
||||
Execute(ClientCommandId.AllegianceBoot, "Lord Bob");
|
||||
Execute(ClientCommandId.AllegianceBoot, "-account Account Bob");
|
||||
// Retail validates non-empty BEFORE removing -account, so this odd
|
||||
// form deliberately sends an empty name with accountBoot=true.
|
||||
Execute(ClientCommandId.AllegianceBoot, "-account");
|
||||
Execute(ClientCommandId.AllegianceBan, "list ignored");
|
||||
Execute(ClientCommandId.AllegianceBan, "add Lord Bob");
|
||||
Execute(ClientCommandId.AllegianceBan, "remove Lord Bob");
|
||||
Execute(ClientCommandId.AllegianceChat, "on");
|
||||
Execute(ClientCommandId.AllegianceChat, "off");
|
||||
Execute(ClientCommandId.AllegianceChat, "kick Bob");
|
||||
Execute(ClientCommandId.AllegianceChat, "kick Bob, Bad manners");
|
||||
Execute(ClientCommandId.AllegianceChat, "kick ");
|
||||
Execute(ClientCommandId.AllegianceChat, "gag Lord Bob");
|
||||
Execute(ClientCommandId.AllegianceChat, "ungag Lord Bob");
|
||||
Execute(ClientCommandId.AllegianceBroadcast, "Hear ye");
|
||||
Execute(ClientCommandId.AllegianceOfficer, "");
|
||||
Execute(ClientCommandId.AllegianceOfficer, "clear ignored");
|
||||
Execute(ClientCommandId.AllegianceOfficer, "remove Lord Bob");
|
||||
Execute(ClientCommandId.AllegianceOfficer, "add 0x2 Lord Bob");
|
||||
Execute(ClientCommandId.AllegianceOfficer, "set 03 Aunt Alice");
|
||||
Execute(ClientCommandId.AllegianceOfficerTitle, "");
|
||||
Execute(ClientCommandId.AllegianceOfficerTitle, "clear ignored");
|
||||
Execute(ClientCommandId.AllegianceOfficerTitle, "set 0x2 High Regent");
|
||||
Execute(ClientCommandId.AllegianceOfficerTitle, "set 1");
|
||||
Execute(ClientCommandId.AllegianceName, "");
|
||||
Execute(ClientCommandId.AllegianceName, "set The Best Allegiance");
|
||||
Execute(ClientCommandId.AllegianceName, "set");
|
||||
Execute(ClientCommandId.AllegianceName, "clear ignored");
|
||||
Execute(ClientCommandId.AllegianceLock, "");
|
||||
Execute(ClientCommandId.AllegianceLock, "off");
|
||||
Execute(ClientCommandId.AllegianceLock, "on");
|
||||
Execute(ClientCommandId.AllegianceLock, "toggle");
|
||||
Execute(ClientCommandId.AllegianceLock, "check");
|
||||
Execute(ClientCommandId.AllegianceLock, "bypass");
|
||||
Execute(ClientCommandId.AllegianceLock, "bypass clear");
|
||||
Execute(ClientCommandId.AllegianceLock, "bypass Lord Bob");
|
||||
Execute(ClientCommandId.AllegianceHouse, "");
|
||||
Execute(ClientCommandId.AllegianceHouse, "guest open");
|
||||
Execute(ClientCommandId.AllegianceHouse, "guest close");
|
||||
Execute(ClientCommandId.AllegianceHouse, "storage open");
|
||||
Execute(ClientCommandId.AllegianceHouse, "storage close");
|
||||
Execute(ClientCommandId.AllegianceMotd, "");
|
||||
Execute(ClientCommandId.AllegianceMotd, "set Welcome everyone");
|
||||
Execute(ClientCommandId.AllegianceMotd, "set");
|
||||
Execute(ClientCommandId.AllegianceMotd, "clear ignored");
|
||||
|
||||
Assert.Equal(
|
||||
[
|
||||
"alleginfo:Lord Bob",
|
||||
"allegboot:Lord Bob:False",
|
||||
"allegboot:Account Bob:True",
|
||||
"allegboot::True",
|
||||
"allegban:list",
|
||||
"allegban:add:Lord Bob",
|
||||
"allegban:remove:Lord Bob",
|
||||
"charoption:27:True",
|
||||
"charoption:27:False",
|
||||
"allegchatboot:Bob:No reason given.",
|
||||
"allegchatboot:Bob:Bad manners",
|
||||
"allegchatboot::No reason given.",
|
||||
"allegchatgag:Lord Bob:True",
|
||||
"allegchatgag:Lord Bob:False",
|
||||
"allegbroadcast:Hear ye",
|
||||
"allegofficer:list",
|
||||
"allegofficer:clear",
|
||||
"allegofficer:remove:Lord Bob",
|
||||
"allegofficer:set:2:Lord Bob",
|
||||
"allegofficer:set:3:Aunt Alice",
|
||||
"allegtitle:list",
|
||||
"allegtitle:clear",
|
||||
"allegtitle:set:2:High Regent",
|
||||
"allegtitle:set:1:",
|
||||
"allegname:query",
|
||||
"allegname:set:The Best Allegiance",
|
||||
"allegname:set:",
|
||||
"allegname:clear",
|
||||
"alleglock:4",
|
||||
"alleglock:1",
|
||||
"alleglock:2",
|
||||
"alleglock:3",
|
||||
"alleglock:4",
|
||||
"alleglock:5",
|
||||
"alleglock:6",
|
||||
"alleglock:bypass:Lord Bob",
|
||||
"alleghouse:1",
|
||||
"alleghouse:2",
|
||||
"alleghouse:3",
|
||||
"alleghouse:4",
|
||||
"alleghouse:5",
|
||||
"motd:query",
|
||||
"motd:set:Welcome everyone",
|
||||
"motd:set:",
|
||||
"motd:clear",
|
||||
],
|
||||
calls);
|
||||
Assert.Equal(
|
||||
[
|
||||
"Attempting to boot Lord Bob...",
|
||||
"Attempting to boot Account Bob (Account)...",
|
||||
"Attempting to boot (Account)...",
|
||||
],
|
||||
system);
|
||||
Assert.Empty(clientLocal);
|
||||
|
||||
void Execute(ClientCommandId command, string arguments) =>
|
||||
controller.Execute(new ExecuteClientCommandCmd(command, arguments));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HouseAdministration_ExecutesEveryRetailDispatcherBranch()
|
||||
{
|
||||
var calls = new List<string>();
|
||||
ClientCommandController controller = NewController(calls: calls);
|
||||
|
||||
Execute(ClientCommandId.HouseOpenStatus, "open");
|
||||
Execute(ClientCommandId.HouseOpenStatus, "close");
|
||||
Execute(ClientCommandId.HouseGuests, "add Lord Bob");
|
||||
Execute(ClientCommandId.HouseGuests, "remove Lord Bob");
|
||||
Execute(ClientCommandId.HouseGuests, "remove_all ignored");
|
||||
Execute(ClientCommandId.HouseGuests, "list ignored");
|
||||
Execute(ClientCommandId.HouseGuests, "show ignored");
|
||||
Execute(ClientCommandId.HouseGuests, "add_allegiance ignored");
|
||||
Execute(ClientCommandId.HouseGuests, "remove_allegiance ignored");
|
||||
Execute(ClientCommandId.HouseStorage, "add Lord Bob");
|
||||
Execute(ClientCommandId.HouseStorage, "remove Lord Bob");
|
||||
Execute(ClientCommandId.HouseStorage, "add -all");
|
||||
Execute(ClientCommandId.HouseStorage, "remove -all");
|
||||
Execute(ClientCommandId.HouseStorage, "remove_all ignored");
|
||||
Execute(ClientCommandId.HouseStorage, "list ignored");
|
||||
Execute(ClientCommandId.HouseStorage, "show ignored");
|
||||
Execute(ClientCommandId.HouseStorage, "add_allegiance ignored");
|
||||
Execute(ClientCommandId.HouseStorage, "remove_allegiance ignored");
|
||||
Execute(ClientCommandId.HouseBoot, "Lord Bob");
|
||||
Execute(ClientCommandId.HouseBoot, "-all");
|
||||
Execute(ClientCommandId.HouseBootAll, "ignored");
|
||||
Execute(ClientCommandId.HouseHooks, "on ignored");
|
||||
Execute(ClientCommandId.HouseHooks, "off ignored");
|
||||
|
||||
Assert.Equal(
|
||||
[
|
||||
"houseopen:True",
|
||||
"houseopen:False",
|
||||
"houseguest:add:Lord Bob",
|
||||
"houseguest:remove:Lord Bob",
|
||||
"houseguest:remove_all",
|
||||
"houseguest:list",
|
||||
"houseguest:list",
|
||||
"houseguest:allegiance:True",
|
||||
"houseguest:allegiance:False",
|
||||
"housestorage:True:Lord Bob",
|
||||
"housestorage:False:Lord Bob",
|
||||
"housestorage:add_all",
|
||||
"housestorage:remove_all",
|
||||
"housestorage:remove_all",
|
||||
"houseguest:list",
|
||||
"houseguest:list",
|
||||
"housestorage:allegiance:True",
|
||||
"housestorage:allegiance:False",
|
||||
"houseboot:Lord Bob",
|
||||
"houseboot:all",
|
||||
"houseboot:all",
|
||||
"househooks:True",
|
||||
"househooks:False",
|
||||
],
|
||||
calls);
|
||||
|
||||
void Execute(ClientCommandId command, string arguments) =>
|
||||
controller.Execute(new ExecuteClientCommandCmd(command, arguments));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ClientCommandId.AllegianceInfo, "", "Please specify an actual name.")]
|
||||
[InlineData(ClientCommandId.AllegianceBoot, "", "Please specify an actual name.")]
|
||||
[InlineData(ClientCommandId.AllegianceBan, "add", "Please specify an actual name.")]
|
||||
[InlineData(ClientCommandId.AllegianceChat, "gag", "Please specify an actual name.")]
|
||||
[InlineData(ClientCommandId.AllegianceBroadcast, "", "Please see @help Allegiance for more information on how to use this command.")]
|
||||
[InlineData(ClientCommandId.AllegianceOfficer, "remove", "Please specify the name of an allegiance member.")]
|
||||
[InlineData(ClientCommandId.AllegianceOfficer, "add nope Bob", "Please specify a valid officer level as a number between 1 and 3. Check the game help files for more information on officer levels.")]
|
||||
[InlineData(ClientCommandId.AllegianceOfficer, "add 2", "Please specify the name of an allegiance member.")]
|
||||
[InlineData(ClientCommandId.AllegianceOfficerTitle, "set 4 Regent", "Please specify a valid officer level as a number between 1 and 3.")]
|
||||
[InlineData(ClientCommandId.AllegianceName, "nope", "Please see @help Allegiance for more information on how to use this command.")]
|
||||
[InlineData(ClientCommandId.AllegianceLock, "nope", "Please see @help Allegiance for more information on how to use this command.")]
|
||||
[InlineData(ClientCommandId.AllegianceHouse, "guest nope", "Please see @help Allegiance for more information on how to use this command.")]
|
||||
[InlineData(ClientCommandId.AllegianceMotd, "nope", "Please see @help Allegiance for more information on how to use this command.")]
|
||||
[InlineData(ClientCommandId.AllegianceUnrecognizedSubcommand, "nope", "Please see @help Allegiance for more information on how to use this command.")]
|
||||
[InlineData(ClientCommandId.HouseGuests, "add", "Please specify the guest's name.")]
|
||||
[InlineData(ClientCommandId.HouseStorage, "add", "Please specify an actual name.")]
|
||||
[InlineData(ClientCommandId.HouseBoot, "", "Please see @help House for more information on how to use this command.")]
|
||||
[InlineData(ClientCommandId.HouseHooks, "maybe", "Please see @help House for more information on how to use this command.")]
|
||||
[InlineData(ClientCommandId.HouseUnrecognizedSubcommand, "nope", "Please see @help House for more information on how to use this command.")]
|
||||
public void AdministrationInvalidForms_EmitExactRetailClientLocalText(
|
||||
ClientCommandId command,
|
||||
string arguments,
|
||||
string expected)
|
||||
{
|
||||
var calls = new List<string>();
|
||||
var system = new List<string>();
|
||||
var clientLocal = new List<string>();
|
||||
ClientCommandController controller = NewController(
|
||||
calls: calls,
|
||||
messages: system,
|
||||
clientLocalMessages: clientLocal);
|
||||
|
||||
controller.Execute(new ExecuteClientCommandCmd(command, arguments));
|
||||
|
||||
Assert.Empty(calls);
|
||||
Assert.Empty(system);
|
||||
Assert.Equal([expected], clientLocal);
|
||||
}
|
||||
|
||||
private static ClientCommandController NewController(
|
||||
List<string>? calls = null,
|
||||
List<uint>? errors = null,
|
||||
uint? playerBitfield = 0x02000028u,
|
||||
List<string>? messages = null,
|
||||
List<string>? clientLocalMessages = null,
|
||||
bool isAway = false,
|
||||
bool acceptsLootPermits = true,
|
||||
FriendsState? friends = null,
|
||||
|
|
@ -569,11 +914,16 @@ public sealed class ClientCommandControllerTests
|
|||
// Defaults to "always accept" so every pre-existing single-stage
|
||||
// test (Die, etc.) keeps its original behavior unchanged.
|
||||
Queue<bool>? confirmationResponses = null,
|
||||
Func<string, AcDream.Core.Chat.ChatLogResult>? chatLog = null)
|
||||
Func<string, AcDream.Core.Chat.ChatLogResult>? chatLog = null,
|
||||
Func<bool>? isPersistentDaylight = null,
|
||||
Action<bool>? setPersistentDaylight = null,
|
||||
Action<int>? setLandscapeRadius = null,
|
||||
Action<float>? setFieldOfView = null)
|
||||
{
|
||||
calls ??= [];
|
||||
errors ??= [];
|
||||
messages ??= [];
|
||||
clientLocalMessages ??= messages;
|
||||
return new ClientCommandController(new ClientCommandController.Bindings(
|
||||
() => calls.Add("ls"),
|
||||
() => calls.Add("mp"),
|
||||
|
|
@ -586,6 +936,7 @@ public sealed class ClientCommandControllerTests
|
|||
() => calls.Add("fps"),
|
||||
() => calls.Add("lock"),
|
||||
messages.Add,
|
||||
clientLocalMessages.Add,
|
||||
errors.Add,
|
||||
() => playerBitfield,
|
||||
() => "1.2.3",
|
||||
|
|
@ -653,6 +1004,61 @@ public sealed class ClientCommandControllerTests
|
|||
channelId => calls.Add("off:" + channelId),
|
||||
() => calls.Add("alh"),
|
||||
name => calls.Add("alleginfo:" + name),
|
||||
() => calls.Add("houseabandon")));
|
||||
() => calls.Add("houseabandon"),
|
||||
NewAdministrationBindings(calls),
|
||||
isPersistentDaylight ?? (() => false),
|
||||
setPersistentDaylight ?? (value => calls.Add("day:" + value)),
|
||||
setLandscapeRadius ?? (value => calls.Add("radius:" + value)),
|
||||
setFieldOfView ?? (value => calls.Add(
|
||||
"fov:" + value.ToString(CultureInfo.InvariantCulture)))));
|
||||
}
|
||||
|
||||
private static ClientCommandController.AdministrationBindings
|
||||
NewAdministrationBindings(List<string> calls) => new(
|
||||
BreakAllegianceBoot: (name, account) =>
|
||||
calls.Add($"allegboot:{name}:{account}"),
|
||||
AllegianceChatBoot: (name, reason) =>
|
||||
calls.Add($"allegchatboot:{name}:{reason}"),
|
||||
AllegianceChatGag: (name, enabled) =>
|
||||
calls.Add($"allegchatgag:{name}:{enabled}"),
|
||||
AllegianceBroadcast: text => calls.Add("allegbroadcast:" + text),
|
||||
ListAllegianceBans: () => calls.Add("allegban:list"),
|
||||
AddAllegianceBan: name => calls.Add("allegban:add:" + name),
|
||||
RemoveAllegianceBan: name => calls.Add("allegban:remove:" + name),
|
||||
ListAllegianceOfficers: () => calls.Add("allegofficer:list"),
|
||||
ClearAllegianceOfficers: () => calls.Add("allegofficer:clear"),
|
||||
SetAllegianceOfficer: (name, level) =>
|
||||
calls.Add($"allegofficer:set:{level}:{name}"),
|
||||
RemoveAllegianceOfficer: name =>
|
||||
calls.Add("allegofficer:remove:" + name),
|
||||
ListAllegianceOfficerTitles: () => calls.Add("allegtitle:list"),
|
||||
ClearAllegianceOfficerTitles: () => calls.Add("allegtitle:clear"),
|
||||
SetAllegianceOfficerTitle: (level, title) =>
|
||||
calls.Add($"allegtitle:set:{level}:{title}"),
|
||||
QueryAllegianceName: () => calls.Add("allegname:query"),
|
||||
SetAllegianceName: name => calls.Add("allegname:set:" + name),
|
||||
ClearAllegianceName: () => calls.Add("allegname:clear"),
|
||||
AllegianceLockAction: action => calls.Add("alleglock:" + action),
|
||||
SetAllegianceApprovedVassal: name =>
|
||||
calls.Add("alleglock:bypass:" + name),
|
||||
AllegianceHouseAction: action => calls.Add("alleghouse:" + action),
|
||||
QueryMotd: () => calls.Add("motd:query"),
|
||||
SetMotd: text => calls.Add("motd:set:" + text),
|
||||
ClearMotd: () => calls.Add("motd:clear"),
|
||||
SetOpenHouseStatus: open => calls.Add("houseopen:" + open),
|
||||
AddPermanentGuest: name => calls.Add("houseguest:add:" + name),
|
||||
RemovePermanentGuest: name => calls.Add("houseguest:remove:" + name),
|
||||
RemoveAllPermanentGuests: () => calls.Add("houseguest:remove_all"),
|
||||
ChangeStoragePermission: (name, enabled) =>
|
||||
calls.Add($"housestorage:{enabled}:{name}"),
|
||||
AddAllStoragePermission: () => calls.Add("housestorage:add_all"),
|
||||
RemoveAllStoragePermission: () => calls.Add("housestorage:remove_all"),
|
||||
RequestFullGuestList: () => calls.Add("houseguest:list"),
|
||||
BootSpecificHouseGuest: name => calls.Add("houseboot:" + name),
|
||||
BootEveryone: () => calls.Add("houseboot:all"),
|
||||
SetHooksVisibility: visible => calls.Add("househooks:" + visible),
|
||||
ModifyAllegianceGuestPermission: enabled =>
|
||||
calls.Add("houseguest:allegiance:" + enabled),
|
||||
ModifyAllegianceStoragePermission: enabled =>
|
||||
calls.Add("housestorage:allegiance:" + enabled));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue