8 using Microsoft.Xna.Framework;
9 using Microsoft.Xna.Framework.Input;
11 using System.Collections.Generic;
12 using System.Collections.Immutable;
13 using System.Diagnostics;
14 using System.Globalization;
17 using System.Threading.Tasks;
18 using System.Xml.Linq;
23 static partial class DebugConsole
37 if (!allowCheats && !CheatsEnabled &&
IsCheat)
39 NewMessage(
"You need to enable cheats using the command \"enablecheats\" before you can use the command \"" +
Names[0] +
"\".", Color.Red);
40 NewMessage(
"Enabling cheats will disable achievements during this play session.", Color.Red);
55 private static bool isOpen;
56 public static bool IsOpen
59 set => isOpen = value;
62 public static bool Paused =
false;
64 private static GUITextBlock activeQuestionText;
66 private static GUIFrame frame;
67 private static GUIListBox listBox;
68 private static GUITextBox textBox;
70 private const int maxLength = 100000;
72 private const int maxLength = 1000;
75 public static GUITextBox TextBox => textBox;
77 private static readonly ChatManager chatManager =
new ChatManager(
true, 64);
79 public static void Init()
83 frame =
new GUIFrame(
new RectTransform(
new Vector2(0.5f, 0.45f), GUI.Canvas) { MinSize = new Point(400, 300), AbsoluteOffset = new Point(10, 10) },
84 color:
new Color(0.4f, 0.4f, 0.4f, 0.8f));
86 var paddedFrame =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.95f, 0.9f), frame.RectTransform,
Anchor.Center)) { RelativeSpacing = 0.01f };
88 var toggleText =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.05f), paddedFrame.RectTransform,
Anchor.TopLeft), TextManager.Get(
"DebugConsoleHelpText"), Color.GreenYellow, GUIStyle.SmallFont, Alignment.CenterLeft, style:
null);
90 var closeButton =
new GUIButton(
new RectTransform(
new Vector2(0.025f, 1.0f), toggleText.RectTransform,
Anchor.TopRight),
"X", style:
null)
92 Color = Color.DarkRed,
93 HoverColor = Color.Red,
94 TextColor = Color.White,
95 OutlineColor = Color.Red
97 closeButton.OnClicked += (btn, userdata) =>
100 GUI.ForceMouseOn(
null);
105 listBox =
new GUIListBox(
new RectTransform(
new Point(paddedFrame.Rect.Width, paddedFrame.Rect.Height - 60), paddedFrame.RectTransform,
Anchor.Center)
108 }, color: Color.Black * 0.9f) { ScrollBarVisible =
true };
110 textBox =
new GUITextBox(
new RectTransform(
new Point(paddedFrame.Rect.Width, 30), paddedFrame.RectTransform,
Anchor.BottomLeft)
114 textBox.MaxTextLength = maxLength;
115 textBox.OnKeyHit += (sender, key) =>
117 if (key != Keys.Tab && key != Keys.LeftShift)
123 ChatManager.RegisterKeys(textBox, chatManager);
126 public static void AddToGUIUpdateList()
130 frame.AddToGUIUpdateList(order: 1);
134 public static void Update(
float deltaTime)
136 while (queuedMessages.TryDequeue(out var newMsg))
140 if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
142 unsavedMessages.Add(newMsg);
143 if (unsavedMessages.Count >= messagesPerFile)
146 unsavedMessages.Clear();
151 if (!IsOpen && GUI.KeyboardDispatcher.Subscriber ==
null)
157 ExecuteCommand(command);
162 activeQuestionText?.SetAsLastChild();
164 if (PlayerInput.KeyHit(Keys.F3) && !PlayerInput.KeyDown(Keys.LeftControl) && !PlayerInput.KeyDown(Keys.RightControl))
168 else if (isOpen && PlayerInput.KeyHit(Keys.Escape))
171 GUI.ForceMouseOn(
null);
178 frame.UpdateManually(deltaTime);
182 if (PlayerInput.KeyHit(Keys.Tab) && !textBox.IsIMEActive)
184 int increment = PlayerInput.KeyDown(Keys.LeftShift) ? -1 : 1;
185 textBox.Text = AutoComplete(textBox.Text, increment:
string.IsNullOrEmpty(currentAutoCompletedCommand) ? 0 : increment );
188 if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))
190 if ((PlayerInput.KeyDown(Keys.C) || PlayerInput.KeyDown(Keys.D) || PlayerInput.KeyDown(Keys.Z)) && activeQuestionCallback !=
null)
192 activeQuestionCallback =
null;
193 activeQuestionText =
null;
194 NewMessage(PlayerInput.KeyDown(Keys.C) ?
"^C" : PlayerInput.KeyDown(Keys.D) ?
"^D" :
"^Z", Color.White,
true);
198 if (PlayerInput.KeyHit(Keys.Enter))
200 chatManager.Store(textBox.Text);
201 ExecuteCommand(textBox.Text);
207 public static void Toggle()
212 textBox.Select(ignoreSelectSound:
true);
213 AddToGUIUpdateList();
217 GUI.ForceMouseOn(
null);
223 private static bool IsCommandPermitted(Identifier command,
GameClient client)
225 if (GameMain.LuaCs.Game.IsCustomCommandPermitted(command)) {
return true; }
227 switch (command.Value.ToLowerInvariant())
244 case "toggleupperhud":
245 case "togglecharacternames":
249 case "findentityids":
250 case "setfreecamspeed":
251 case "togglevoicechatfilters":
255 case "wikiimage_character":
256 case "wikiimage_sub":
259 case "eosLoginEpicViaSteam":
266 public static void DequeueMessages()
268 while (queuedMessages.TryDequeue(out var newMsg))
273 Messages.Add(newMsg);
280 if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
282 unsavedMessages.Add(newMsg);
287 private static void AddMessage(ColoredText msg)
290 if (listBox ==
null)
return;
292 if (listBox.Content.CountChildren > MaxMessages)
294 listBox.RemoveChild(listBox.Content.Children.First());
298 if (Messages.Count > MaxMessages)
300 Messages.RemoveRange(0, Messages.Count - MaxMessages);
307 var textContainer =
new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.0f), listBox.Content.RectTransform), style:
"InnerFrame", color: Color.White)
310 OnSecondaryClicked = (component, data) =>
312 GUIContextMenu.CreateContextMenu(
new ContextMenuOption(
"editor.copytoclipboard",
true, () => { Clipboard.SetText(msg.Text); }));
316 var textBlock =
new GUITextBlock(
new RectTransform(
new Point(listBox.Content.Rect.Width - 5, 0), textContainer.RectTransform,
Anchor.TopLeft) { AbsoluteOffset = new Point(2, 2) },
317 RichString.Rich(msg.Text), textAlignment: Alignment.TopLeft, font: GUIStyle.SmallFont, wrap:
true)
319 CanBeFocused =
false,
320 TextColor = msg.Color
322 textContainer.RectTransform.NonScaledSize =
new Point(textContainer.RectTransform.NonScaledSize.X, textBlock.RectTransform.NonScaledSize.Y + 5);
323 textBlock.SetTextPos();
327 var textBlock =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), listBox.Content.RectTransform),
328 RichString.Rich(msg.Text), font: GUIStyle.SmallFont, wrap:
true)
330 CanBeFocused =
false,
331 TextColor = msg.Color
335 listBox.UpdateScrollBarSize();
336 listBox.BarScroll = 1.0f;
340 ThrowError(
"Failed to add a message to the debug console.", e);
346 static partial
void ShowHelpMessage(Command command)
348 if (listBox.Content.CountChildren > MaxMessages)
350 listBox.RemoveChild(listBox.Content.Children.First());
353 var textContainer =
new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.0f), listBox.Content.RectTransform),
354 style:
"InnerFrame", color: Color.White * 0.6f)
358 var textBlock =
new GUITextBlock(
new RectTransform(
new Point(listBox.Content.Rect.Width - 170, 0), textContainer.RectTransform,
Anchor.TopRight) { AbsoluteOffset = new Point(20, 0) },
359 command.Help, textAlignment: Alignment.TopLeft, font: GUIStyle.SmallFont, wrap:
true)
361 CanBeFocused =
false,
362 TextColor = Color.White
364 textContainer.RectTransform.NonScaledSize =
new Point(textContainer.RectTransform.NonScaledSize.X, textBlock.RectTransform.NonScaledSize.Y + 5);
365 textBlock.SetTextPos();
366 new GUITextBlock(
new RectTransform(
new Point(150, textContainer.Rect.Height), textContainer.RectTransform),
367 command.Names[0].Value, textAlignment: Alignment.TopLeft);
369 listBox.UpdateScrollBarSize();
370 listBox.BarScroll = 1.0f;
375 private static void AssignOnClientExecute(
string names, Action<
string[]> onClientExecute)
377 Command command = commands.Find(c => c.Names.Intersect(names.Split(
'|').ToIdentifiers()).Any());
380 throw new Exception(
"AssignOnClientExecute failed. Command matching the name(s) \"" + names +
"\" not found.");
384 command.OnClientExecute = onClientExecute;
385 command.RelayToServer =
false;
389 private static void AssignRelayToServer(
string names,
bool relay)
391 Command command = commands.Find(c => c.Names.Intersect(names.Split(
'|').ToIdentifiers()).Any());
394 DebugConsole.Log(
"Could not assign to relay to server: " + names);
397 command.RelayToServer = relay;
400 private static void InitProjectSpecific()
402 commands.Add(
new Command(
"eosStat",
"Query and display all logged in EOS users. Normally this is at most two users, but in a developer environment it could be more.", args =>
404 if (!EosInterface.Core.IsInitialized)
406 NewMessage(
"EOS not initialized");
410 var loggedInUsers = EosInterface.IdQueries.GetLoggedInPuids();
411 if (!loggedInUsers.Any())
413 NewMessage(
"EOS user not logged in");
417 NewMessage(
"Logged in EOS users:");
418 foreach (var puid
in loggedInUsers)
421 $
"eosStat -> {puid}",
422 EosInterface.IdQueries.GetSelfExternalAccountIds(puid),
425 if (!t.TryGetResult(out Result<ImmutableArray<AccountId>, EosInterface.IdQueries.GetSelfExternalIdError> result)) { return; }
426 NewMessage($
" - {puid}");
428 if (result.TryUnwrapSuccess(out var ids))
430 foreach (var id in ids)
432 NewMessage($
" - {id}");
433 if (id is EpicAccountId eaid)
435 async Task gameOwnershipTokenTest()
437 var tokenOption = await EosInterface.Ownership.GetGameOwnershipToken(eaid);
438 var verified = await tokenOption.Bind(t => t.Verify());
439 NewMessage($
"Ownership token verify result: {verified}");
441 _ = gameOwnershipTokenTest();
442 EosInterface.Login.TestEosSessionTimeoutRecovery(puid);
448 NewMessage($
" - Failed to get external IDs linked to {puid}: {result}");
453 AssignRelayToServer(
"eosStat",
false);
455 commands.Add(
new Command(
"eosUnlink",
"Unlink the primary logged in external account ID from its corresponding EOS Product User ID and close the game. This is meant to be used to test the EOS consent flow.", args =>
457 var userId = EosInterface.IdQueries.GetLoggedInPuids().FirstOrDefault();
458 NewMessage($
"Unlinking external account from PUID {userId}");
459 GameSettings.SetCurrentConfig(GameSettings.CurrentConfig with { CrossplayChoice = Eos.EosSteamPrimaryLogin.CrossplayChoice.Unknown });
460 GameSettings.SaveCurrentConfig();
461 TaskPool.Add(
"unlinkTask", EosInterface.Login.UnlinkExternalAccount(userId), _ =>
463 GameMain.Instance.Exit();
466 AssignRelayToServer(
"eosUnlink",
false);
468 commands.Add(
new Command(
"eosLoginEpicViaSteam",
"Log into an Epic account via a link to the currently logged in Steam account",
471 TaskPool.Add(
"eosLoginEpicViaSteam",
472 Eos.EosEpicSecondaryLogin.LoginToLinkedEpicAccount(),
473 TaskPool.IgnoredCallback);
475 AssignRelayToServer(
"eosLoginEpicViaSteam",
false);
477 commands.Add(
new Command(
"resetgameanalyticsconsent",
"Reset whether you've given your consent for the game to send statistics to GameAnalytics. After executing the command, the game should ask for your consent again on relaunch.", args =>
479 GameAnalyticsManager.ResetConsent();
481 AssignRelayToServer(
"resetgameanalyticsconsent",
false);
483 commands.Add(
new Command(
"copyitemnames",
"", (
string[] args) =>
485 StringBuilder sb =
new StringBuilder();
486 foreach (ItemPrefab mp
in ItemPrefab.Prefabs)
488 sb.AppendLine(mp.Name.Value);
490 Clipboard.SetText(sb.ToString());
493 commands.Add(
new Command(
"autohull",
"", (
string[] args) =>
495 if (Screen.Selected != GameMain.SubEditorScreen)
return;
497 if (MapEntity.MapEntityList.Any(e => e is Hull || e is Gap))
499 ShowQuestionPrompt(
"This submarine already has hulls and/or gaps. This command will delete them. Do you want to continue? Y/N",
502 ShowQuestionPrompt(
"The automatic hull generation may not work correctly if your submarine uses curved walls. Do you want to continue? Y/N",
505 if (option2.ToLowerInvariant() ==
"y") { GameMain.SubEditorScreen.AutoHull(); }
511 ShowQuestionPrompt(
"The automatic hull generation may not work correctly if your submarine uses curved walls. Do you want to continue? Y/N",
512 (option) => {
if (option.ToLowerInvariant() ==
"y") GameMain.SubEditorScreen.AutoHull(); });
516 commands.Add(
new Command(
"enablecheats",
"enablecheats: Enables cheat commands and disables achievements during this play session.", (
string[] args) =>
518 CheatsEnabled =
true;
519 AchievementManager.CheatsEnabled =
true;
520 if (GameMain.GameSession?.Campaign is CampaignMode campaign)
522 campaign.CheatsEnabled =
true;
524 NewMessage(
"Enabled cheat commands.", Color.Red);
525 NewMessage(
"Achievements have been disabled during this play session.", Color.Red);
527 AssignRelayToServer(
"enablecheats",
true);
529 commands.Add(
new Command(
"mainmenu|menu",
"mainmenu/menu: Go to the main menu.", (
string[] args) =>
531 GameMain.GameSession =
null;
533 List<Character> characters =
new List<Character>(
Character.CharacterList);
534 foreach (Character c
in characters)
539 GameMain.MainMenuScreen.Select();
542 commands.Add(
new Command(
"game",
"gamescreen/game: Go to the \"in-game\" view.", (
string[] args) =>
544 if (Screen.Selected == GameMain.SubEditorScreen)
546 NewMessage(
"WARNING: Switching directly from the submarine editor to the game view may cause bugs and crashes. Use with caution.", Color.Orange);
547 Entity.Spawner ??=
new EntitySpawner();
549 GameMain.GameScreen.Select();
552 commands.Add(
new Command(
"editsubs|subeditor",
"editsubs/subeditor: Switch to the Submarine Editor to create or edit submarines.", (
string[] args) =>
556 var subInfo =
new SubmarineInfo(
string.Join(
" ", args));
559 GameMain.SubEditorScreen.Select(enableAutoSave: Screen.Selected != GameMain.GameScreen);
560 Entity.Spawner?.Remove();
561 Entity.Spawner =
null;
564 commands.Add(
new Command(
"editparticles|particleeditor",
"editparticles/particleeditor: Switch to the Particle Editor to edit particle effects.", (
string[] args) =>
566 GameMain.ParticleEditorScreen.Select();
569 commands.Add(
new Command(
"editlevels|leveleditor",
"editlevels/leveleditor: Switch to the Level Editor to edit levels.", (
string[] args) =>
571 GameMain.LevelEditorScreen.Select();
574 commands.Add(
new Command(
"editsprites|spriteeditor",
"editsprites/spriteeditor: Switch to the Sprite Editor to edit the source rects and origins of sprites.", (
string[] args) =>
576 GameMain.SpriteEditorScreen.Select();
579 commands.Add(
new Command(
"editevents|eventeditor",
"editevents/eventeditor: Switch to the Event Editor to edit scripted events.", (
string[] args) =>
581 GameMain.EventEditorScreen.Select();
584 commands.Add(
new Command(
"editcharacters|charactereditor",
"editcharacters/charactereditor: Switch to the Character Editor to edit/create the ragdolls and animations of characters.", (
string[] args) =>
586 if (Screen.Selected == GameMain.GameScreen)
588 NewMessage(
"WARNING: Switching between the character editor and the game view may cause odd behaviour or bugs. Use with caution.", Color.Orange);
590 GameMain.CharacterEditorScreen.Select();
593 commands.Add(
new Command(
"quickstart",
"Starts a singleplayer sandbox", (
string[] args) =>
595 if (Screen.Selected != GameMain.MainMenuScreen)
597 ThrowError(
"This command can only be executed from the main menu.");
601 Identifier subName = (args.Length > 0 ? args[0] :
"").ToIdentifier();
604 ThrowError(
"No submarine specified.");
608 float difficulty = 40;
611 float.TryParse(args[1], out difficulty);
614 LevelGenerationParams levelGenerationParams =
null;
617 string levelGenerationIdentifier = args[2];
618 levelGenerationParams = LevelGenerationParams.LevelParams.FirstOrDefault(p => p.Identifier == levelGenerationIdentifier);
621 if (SubmarineInfo.SavedSubmarines.None(s => s.Name == subName))
623 ThrowError($
"Cannot find a sub that matches the name \"{subName}\".");
627 bool luaCsEnabled =
true;
630 bool.TryParse(args[3], out luaCsEnabled);
633 if (luaCsEnabled) { GameMain.LuaCs.Initialize(); }
635 GameMain.MainMenuScreen.QuickStart(fixedSeed:
false, subName, difficulty, levelGenerationParams);
637 }, getValidArgs: () =>
new[] { SubmarineInfo.SavedSubmarines.Select(s => s.Name).Distinct().OrderBy(s => s).ToArray() }));
639 commands.Add(
new Command(
"steamnetdebug",
"steamnetdebug: Toggles Steamworks networking debug logging.", (
string[] args) =>
641 SteamManager.SetSteamworksNetworkingDebugLog(!SteamManager.NetworkingDebugLog);
644 commands.Add(
new Command(
"readycheck",
"Commence a ready check in multiplayer.", (
string[] args) =>
646 NewMessage(
"Ready checks can only be commenced in multiplayer.", Color.Red);
649 commands.Add(
new Command(
"setsalary",
"setsalary [0-100] [character/default]: Sets the salary of a certain character or the default salary to a percentage.", (
string[] args) =>
651 ThrowError(
"This command can only be used in multiplayer campaign.");
652 }, isCheat:
true, getValidArgs: () =>
658 new string[] {
"default" },
659 Character.CharacterList.Select(c => c.Name).Distinct().OrderBy(n => n)).ToArray(),
663 commands.Add(
new Command(
"bindkey",
"bindkey [key] [command]: Binds a key to a command.", (
string[] args) =>
667 ThrowError(
"No key or command specified.");
671 string keyString = args[0];
672 string command = args[1];
674 KeyOrMouse key = Enum.TryParse<Keys>(keyString, ignoreCase:
true, out var outKey)
676 : Enum.TryParse<
MouseButton>(keyString, ignoreCase:
true, out var outMouseButton)
680 if (key.Key == Keys.None && key.MouseButton ==
MouseButton.None)
682 ThrowError($
"Invalid key {keyString}.");
687 NewMessage($
"\"{command}\" bound to {key}.", GUIStyle.Green);
689 if (GameSettings.CurrentConfig.KeyMap.Bindings.FirstOrDefault(bind => bind.Value.Key != Keys.None && bind.Value.Key == key) is { } existingBind && existingBind.Value !=
null)
691 AddWarning($
"\"{key}\" has already been bound to {existingBind.Key}. The keybind will perform both actions when pressed.");
694 }, isCheat:
false, getValidArgs: () =>
new[] { Enum.GetNames(typeof(Keys)),
new[] {
"\"\"" } }));
696 commands.Add(
new Command(
"unbindkey",
"unbindkey [key]: Unbinds a command.", (
string[] args) =>
700 ThrowError(
"No key specified.");
704 string keyString = args[0];
706 KeyOrMouse key = Enum.TryParse<Keys>(keyString, ignoreCase:
true, out var outKey)
708 : Enum.TryParse<
MouseButton>(keyString, ignoreCase:
true, out var outMouseButton)
712 if (key.Key == Keys.None && key.MouseButton ==
MouseButton.None)
714 ThrowError($
"Invalid key {keyString}.");
718 NewMessage(
"Keybind unbound.", GUIStyle.Green);
722 commands.Add(
new Command(
"savebinds",
"savebinds: Writes current keybinds into the config file.", (
string[] args) =>
724 ShowQuestionPrompt($
"Some keybinds may render the game unusable, are you sure you want to make these keybinds persistent? ({DebugConsoleMapping.Instance.Bindings.Count} keybind(s) assigned) Y/N",
727 if (option2.ToIdentifier() !=
"y")
729 NewMessage(
"Aborted.", GUIStyle.Red);
733 GameSettings.SaveCurrentConfig();
737 commands.Add(
new Command(
"togglegrid",
"Toggle visual snap grid in sub editor.", (
string[] args) =>
739 SubEditorScreen.ShouldDrawGrid = !SubEditorScreen.ShouldDrawGrid;
740 NewMessage(SubEditorScreen.ShouldDrawGrid ?
"Enabled submarine grid." :
"Disabled submarine grid.", GUIStyle.Green);
743 commands.Add(
new Command(
"spreadsheetexport",
"Export items in format recognized by the spreadsheet importer.", (
string[] args) =>
745 SpreadsheetExport.Export();
748 commands.Add(
new Command(
"wikiimage_character",
"Save an image of the currently controlled character with a transparent background.", (
string[] args) =>
750 if (
Character.Controlled ==
null) {
return; }
757 DebugConsole.ThrowError(
"The command 'wikiimage_character' failed.", e);
761 commands.Add(
new Command(
"wikiimage_sub",
"Save an image of the main submarine with a transparent background.", (
string[] args) =>
763 if (
Submarine.MainSub ==
null) {
return; }
766 MapEntity.SelectedList.Clear();
767 MapEntity.ClearHighlightedEntities();
772 DebugConsole.ThrowError(
"The command 'wikiimage_sub' failed.", e);
776 AssignRelayToServer(
"kick",
false);
777 AssignRelayToServer(
"kickid",
false);
778 AssignRelayToServer(
"ban",
false);
779 AssignRelayToServer(
"banid",
false);
780 AssignRelayToServer(
"dumpids",
false);
781 AssignRelayToServer(
"dumptofile",
false);
782 AssignRelayToServer(
"findentityids",
false);
783 AssignRelayToServer(
"campaigninfo",
false);
784 AssignRelayToServer(
"help",
false);
785 AssignRelayToServer(
"verboselogging",
false);
786 AssignRelayToServer(
"freecam",
false);
787 AssignRelayToServer(
"steamnetdebug",
false);
788 AssignRelayToServer(
"quickstart",
false);
789 AssignRelayToServer(
"togglegrid",
false);
790 AssignRelayToServer(
"bindkey",
false);
791 AssignRelayToServer(
"unbindkey",
false);
792 AssignRelayToServer(
"savebinds",
false);
793 AssignRelayToServer(
"spreadsheetexport",
false);
795 AssignRelayToServer(
"listspamfilters",
false);
796 AssignRelayToServer(
"crash",
false);
797 AssignRelayToServer(
"showballastflorasprite",
false);
798 AssignRelayToServer(
"simulatedlatency",
false);
799 AssignRelayToServer(
"simulatedloss",
false);
800 AssignRelayToServer(
"simulatedduplicateschance",
false);
801 AssignRelayToServer(
"simulatedlongloadingtime",
false);
802 AssignRelayToServer(
"storeinfo",
false);
803 AssignRelayToServer(
"sendrawpacket",
false);
806 commands.Add(
new Command(
"clientlist",
"", (
string[] args) => { }));
807 AssignRelayToServer(
"clientlist",
true);
808 commands.Add(
new Command(
"say",
"", (
string[] args) => { }));
809 AssignRelayToServer(
"say",
true);
810 commands.Add(
new Command(
"msg",
"", (
string[] args) => { }));
811 AssignRelayToServer(
"msg",
true);
812 commands.Add(
new Command(
"setmaxplayers|maxplayers",
"", (
string[] args) => { }));
813 AssignRelayToServer(
"setmaxplayers",
true);
814 commands.Add(
new Command(
"setpassword|password",
"", (
string[] args) => { }));
815 AssignRelayToServer(
"setpassword",
true);
816 commands.Add(
new Command(
"traitorlist",
"", (
string[] args) => { }));
817 AssignRelayToServer(
"traitorlist",
true);
818 AssignRelayToServer(
"money",
true);
819 AssignRelayToServer(
"showmoney",
true);
820 AssignRelayToServer(
"setskill",
true);
821 AssignRelayToServer(
"setsalary",
true);
822 AssignRelayToServer(
"readycheck",
true);
823 commands.Add(
new Command(
"debugjobassignment",
"", (
string[] args) => { }));
824 AssignRelayToServer(
"debugjobassignment",
true);
826 AssignRelayToServer(
"givetalent",
true);
827 AssignRelayToServer(
"unlocktalents",
true);
828 AssignRelayToServer(
"giveexperience",
true);
830 AssignOnExecute(
"control", (
string[] args) =>
832 if (args.Length < 1) { return; }
833 if (GameMain.NetworkMember !=
null)
835 GameMain.Client?.SendConsoleCommand(
"control " + string.Join(
' ', args[0]));
838 var character = FindMatchingCharacter(args,
true);
839 if (character !=
null)
844 AssignRelayToServer(
"control",
true);
846 commands.Add(
new Command(
"shake",
"", (
string[] args) =>
848 GameMain.GameScreen.Cam.Shake = 10.0f;
851 AssignOnExecute(
"explosion", (
string[] args) =>
853 Vector2 explosionPos = Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition);
854 float range = 500, force = 10, damage = 50, structureDamage = 20, itemDamage = 100, empStrength = 0.0f, ballastFloraStrength = 50f;
855 if (args.Length > 0)
float.TryParse(args[0], out range);
856 if (args.Length > 1)
float.TryParse(args[1], out force);
857 if (args.Length > 2)
float.TryParse(args[2], out damage);
858 if (args.Length > 3)
float.TryParse(args[3], out structureDamage);
859 if (args.Length > 4)
float.TryParse(args[4], out itemDamage);
860 if (args.Length > 5)
float.TryParse(args[5], out empStrength);
861 if (args.Length > 6)
float.TryParse(args[6], out ballastFloraStrength);
862 new Explosion(range, force, damage, structureDamage, itemDamage, empStrength, ballastFloraStrength).Explode(explosionPos,
null);
865 AssignOnExecute(
"teleportcharacter|teleport", (
string[] args) =>
867 Vector2 cursorWorldPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
868 TeleportCharacter(cursorWorldPos,
Character.Controlled, args);
871 AssignOnExecute(
"spawn|spawncharacter", (
string[] args) =>
873 SpawnCharacter(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), out
string errorMsg);
874 if (!
string.IsNullOrWhiteSpace(errorMsg))
876 ThrowError(errorMsg);
880 AssignOnExecute(
"los", (
string[] args) =>
882 if (args.None() || !
bool.TryParse(args[0], out
bool state))
884 state = !GameMain.LightManager.LosEnabled;
886 GameMain.LightManager.LosEnabled = state;
887 NewMessage(
"Line of sight effect " + (GameMain.LightManager.LosEnabled ?
"enabled" :
"disabled"), Color.Yellow);
889 AssignRelayToServer(
"los",
false);
891 AssignOnExecute(
"lighting|lights", (
string[] args) =>
893 if (args.None() || !
bool.TryParse(args[0], out
bool state))
895 state = !GameMain.LightManager.LightingEnabled;
897 GameMain.LightManager.LightingEnabled = state;
898 NewMessage(
"Lighting " + (GameMain.LightManager.LightingEnabled ?
"enabled" :
"disabled"), Color.Yellow);
900 AssignRelayToServer(
"lighting|lights",
false);
902 AssignOnExecute(
"ambientlight", (
string[] args) =>
904 bool add =
string.Equals(args.LastOrDefault(),
"add");
905 string colorString =
string.Join(
",", add ? args.SkipLast(1) : args);
906 if (colorString.Equals(
"restore", StringComparison.OrdinalIgnoreCase))
908 foreach (Hull hull in Hull.HullList)
910 if (hull.OriginalAmbientLight != null)
912 hull.AmbientLight = hull.OriginalAmbientLight.Value;
913 hull.OriginalAmbientLight = null;
916 NewMessage(
"Restored all hull ambient lights", Color.Yellow);
920 Color color = XMLExtensions.ParseColor(colorString);
921 if (Level.Loaded !=
null)
923 Level.Loaded.GenerationParams.AmbientLightColor = color;
927 GameMain.LightManager.AmbientLight = add ? GameMain.LightManager.AmbientLight.Add(color) : color;
930 foreach (Hull hull in Hull.HullList)
932 hull.OriginalAmbientLight ??= hull.AmbientLight;
933 hull.AmbientLight = add ? hull.AmbientLight.Add(color) : color;
938 NewMessage($
"Set ambient light color to {color}.", Color.Yellow);
942 NewMessage($
"Increased ambient light by {color}.", Color.Yellow);
945 AssignRelayToServer(
"ambientlight",
false);
947 commands.Add(
new Command(
"multiplylights",
"Multiplies the colors of all the static lights in the sub with the given Vector4 value (for example, 1,1,1,0.5).", (
string[] args) =>
949 if (Screen.Selected != GameMain.SubEditorScreen || args.Length < 1)
951 ThrowError(
"The multiplylights command can only be used in the submarine editor.");
953 if (args.Length < 1)
return;
956 Vector4 value = XMLExtensions.ParseVector4(
string.Join(
"", args));
957 foreach (Item item in
Item.ItemList)
959 if (item.ParentInventory != null || item.body != null) continue;
960 var lightComponent = item.GetComponent<LightComponent>();
961 if (lightComponent != null) lightComponent.LightColor =
963 (lightComponent.LightColor.R / 255.0f) * value.X,
964 (lightComponent.LightColor.G / 255.0f) * value.Y,
965 (lightComponent.LightColor.B / 255.0f) * value.Z,
966 (lightComponent.LightColor.A / 255.0f) * value.W);
970 commands.Add(
new Command(
"color|colour",
"Change color (as bytes from 0 to 255) of the selected item/structure instances. Applied only in the subeditor.", (
string[] args) =>
972 if (Screen.Selected == GameMain.SubEditorScreen)
974 if (!MapEntity.SelectedAny)
976 ThrowError(
"You have to select item(s)/structure(s) first!");
982 ThrowError(
"Not enough arguments provided! At least three required.");
985 if (!
byte.TryParse(args[0], out
byte r))
987 ThrowError($
"Failed to parse value for RED from {args[0]}");
989 if (!
byte.TryParse(args[1], out
byte g))
991 ThrowError($
"Failed to parse value for GREEN from {args[1]}");
993 if (!
byte.TryParse(args[2], out
byte b))
995 ThrowError($
"Failed to parse value for BLUE from {args[2]}");
997 Color color =
new Color(r, g, b);
1000 if (!
byte.TryParse(args[3], out
byte a))
1002 ThrowError($
"Failed to parse value for ALPHA from {args[3]}");
1009 foreach (var mapEntity
in MapEntity.SelectedList)
1011 if (mapEntity is Structure s)
1013 s.SpriteColor = color;
1015 else if (mapEntity is Item i)
1017 i.SpriteColor = color;
1024 commands.Add(
new Command(
"listcloudfiles",
"Lists all of your files on the Steam Cloud.", args =>
1027 foreach (var file
in Steamworks.SteamRemoteStorage.Files)
1029 NewMessage($
"* {i}: {file.Filename}, {file.Size} bytes", Color.Orange);
1032 NewMessage($
"Bytes remaining: {Steamworks.SteamRemoteStorage.QuotaRemainingBytes}/{Steamworks.SteamRemoteStorage.QuotaBytes}", Color.Yellow);
1035 commands.Add(
new Command(
"removefromcloud",
"Removes a file from Steam Cloud.", args =>
1037 if (args.Length < 1) {
return; }
1038 var files = Steamworks.SteamRemoteStorage.Files;
1039 Steamworks.SteamRemoteStorage.RemoteFile file;
1040 if (
int.TryParse(args[0], out
int index) && index>=0 && index<files.Count)
1042 file = files[index];
1046 file = files.Find(f => f.Filename.Equals(args[0], StringComparison.InvariantCultureIgnoreCase));
1049 if (!
string.IsNullOrEmpty(file.Filename))
1053 NewMessage($
"Deleting {file.Filename}", Color.Orange);
1057 ThrowError($
"Failed to delete {file.Filename}");
1062 commands.Add(
new Command(
"resetall",
"Reset all items and structures to prefabs. Only applicable in the subeditor.", args =>
1064 if (Screen.Selected == GameMain.SubEditorScreen)
1066 Item.ItemList.ForEach(i => i.Reset());
1067 Structure.WallList.ForEach(s => s.Reset());
1068 foreach (MapEntity entity
in MapEntity.SelectedList)
1070 if (entity is Item item)
1072 item.CreateEditingHUD();
1075 else if (entity is Structure structure)
1077 structure.CreateEditingHUD();
1084 commands.Add(
new Command(
"resetentitiesbyidentifier",
"resetentitiesbyidentifier [tag/identifier]: Reset items and structures with the given tag/identifier to prefabs. Only applicable in the subeditor.", args =>
1086 if (args.Length == 0) {
return; }
1087 if (Screen.Selected == GameMain.SubEditorScreen)
1089 bool entityFound =
false;
1090 foreach (MapEntity entity
in MapEntity.MapEntityList)
1092 if (entity is Item item)
1094 if (item.Prefab.Identifier != args[0] && !item.Tags.Contains(args[0])) {
continue; }
1096 if (MapEntity.SelectedList.Contains(item)) { item.CreateEditingHUD(); }
1099 else if (entity is Structure structure)
1101 if (structure.Prefab.Identifier != args[0] && !structure.Tags.Contains(args[0])) {
continue; }
1103 if (MapEntity.SelectedList.Contains(structure)) { structure.CreateEditingHUD(); }
1110 NewMessage($
"Reset {entity.Name}.");
1114 if (MapEntity.SelectedList.Count == 0)
1116 NewMessage(
"No entities selected.");
1123 return new string[][]
1125 MapEntityPrefab.List.Select(me => me.Identifier.Value).ToArray()
1129 commands.Add(
new Command(
"resetselected",
"Reset selected items and structures to prefabs. Only applicable in the subeditor.", args =>
1131 if (Screen.Selected == GameMain.SubEditorScreen)
1133 if (MapEntity.SelectedList.Count == 0)
1135 NewMessage(
"No entities selected.");
1139 foreach (MapEntity entity
in MapEntity.SelectedList)
1141 if (entity is Item item)
1145 else if (entity is Structure structure)
1153 NewMessage($
"Reset {entity.Name}.");
1155 foreach (MapEntity entity
in MapEntity.SelectedList)
1157 if (entity is Item item)
1159 item.CreateEditingHUD();
1162 else if (entity is Structure structure)
1164 structure.CreateEditingHUD();
1171 commands.Add(
new Command(
"alpha",
"Change the alpha (as bytes from 0 to 255) of the selected item/structure instances. Applied only in the subeditor.", (
string[] args) =>
1173 if (Screen.Selected == GameMain.SubEditorScreen)
1175 if (!MapEntity.SelectedAny)
1177 ThrowError(
"You have to select item(s)/structure(s) first!");
1181 if (args.Length > 0)
1183 if (!
byte.TryParse(args[0], out
byte a))
1185 ThrowError($
"Failed to parse value for ALPHA from {args[0]}");
1189 foreach (var mapEntity
in MapEntity.SelectedList)
1191 if (mapEntity is Structure s)
1193 s.SpriteColor =
new Color(s.SpriteColor.R, s.SpriteColor.G, s.SpriteColor.G, a);
1195 else if (mapEntity is Item i)
1197 i.SpriteColor =
new Color(i.SpriteColor.R, i.SpriteColor.G, i.SpriteColor.G, a);
1204 ThrowError(
"Not enough arguments provided! One required!");
1210 commands.Add(
new Command(
"cleansub",
"", (
string[] args) =>
1212 for (
int i = MapEntity.MapEntityList.Count - 1; i >= 0; i--)
1214 MapEntity me = MapEntity.MapEntityList[i];
1216 if (me.SimPosition.Length() > 2000.0f)
1218 NewMessage(
"Removed " + me.Name +
" (simposition " + me.SimPosition +
")", Color.Orange);
1219 MapEntity.MapEntityList.RemoveAt(i);
1221 else if (!me.ShouldBeSaved)
1223 NewMessage(
"Removed " + me.Name +
" (!ShouldBeSaved)", Color.Orange);
1224 MapEntity.MapEntityList.RemoveAt(i);
1226 else if (me is Item)
1229 var wire = item.GetComponent<
Wire>();
1230 if (wire ==
null)
continue;
1232 if (wire.GetNodes().Count > 0 && !wire.Connections.Any(c => c !=
null))
1235 NewMessage(
"Dropped wire (ID: " + wire.Item.ID +
") - attached on wall but no connections found", Color.Orange);
1241 commands.Add(
new Command(
"messagebox|guimessagebox",
"messagebox [header] [msg] [default/ingame]: Creates a message box.", (
string[] args) =>
1243 var msgBox =
new GUIMessageBox(
1244 args.Length > 0 ? args[0] :
"",
1245 args.Length > 1 ? args[1] :
"",
1246 buttons:
new LocalizedString[] {
"OK" },
1247 type: args.Length < 3 || args[2] ==
"default" ? GUIMessageBox.Type.Default : GUIMessageBox.Type.InGame);
1249 msgBox.Buttons[0].OnClicked = msgBox.Close;
1252 AssignOnExecute(
"debugdraw", (
string[] args) =>
1254 if (args.None() || !
bool.TryParse(args[0], out
bool state))
1256 state = !GameMain.DebugDraw;
1258 GameMain.DebugDraw = state;
1259 NewMessage(
"Debug draw mode " + (GameMain.DebugDraw ?
"enabled" :
"disabled"), Color.Yellow);
1261 AssignRelayToServer(
"debugdraw",
false);
1263 AssignOnExecute(
"debugdrawlos", (
string[] args) =>
1265 if (args.None() || !
bool.TryParse(args[0], out
bool state))
1267 state = !GameMain.LightManager.DebugLos;
1269 GameMain.LightManager.DebugLos = state;
1270 NewMessage(
"Los debug draw mode " + (GameMain.LightManager.DebugLos ?
"enabled" :
"disabled"), Color.Yellow);
1272 AssignOnExecute(
"debugwiring", (
string[] args) =>
1274 if (args.None() || !
bool.TryParse(args[0], out
bool state))
1276 state = !ConnectionPanel.DebugWiringMode;
1281 AssignRelayToServer(
"debugdraw",
false);
1283 AssignOnExecute(
"devmode", (
string[] args) =>
1285 if (args.None() || !
bool.TryParse(args[0], out
bool state))
1287 state = !GameMain.DevMode;
1289 GameMain.DevMode = state;
1290 if (GameMain.DevMode)
1292 GameMain.LightManager.LightingEnabled = false;
1293 GameMain.LightManager.LosEnabled = false;
1297 GameMain.LightManager.LightingEnabled = true;
1298 GameMain.LightManager.LosEnabled = true;
1299 GameMain.LightManager.LosAlpha = 1f;
1301 NewMessage(
"Dev mode " + (GameMain.DevMode ?
"enabled" :
"disabled"), Color.Yellow);
1303 AssignRelayToServer(
"devmode",
false);
1305 AssignOnExecute(
"debugdrawlocalization", (
string[] args) =>
1307 if (args.None() || !
bool.TryParse(args[0], out
bool state))
1309 state = !TextManager.DebugDraw;
1311 TextManager.DebugDraw = state;
1312 NewMessage(
"Localization debug draw mode " + (TextManager.DebugDraw ?
"enabled" :
"disabled"), Color.Yellow);
1314 AssignRelayToServer(
"debugdraw",
false);
1316 AssignOnExecute(
"togglevoicechatfilters", (
string[] args) =>
1318 if (args.None() || !
bool.TryParse(args[0], out
bool state))
1320 state = !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
1322 var config = GameSettings.CurrentConfig;
1323 config.Audio.DisableVoiceChatFilters = state;
1324 GameSettings.SetCurrentConfig(config);
1325 NewMessage(
"Voice chat filters " + (GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters ?
"disabled" :
"enabled"), Color.Yellow);
1327 AssignRelayToServer(
"togglevoicechatfilters",
false);
1329 commands.Add(
new Command(
"fpscounter",
"fpscounter: Toggle the FPS counter.", (
string[] args) =>
1331 GameMain.ShowFPS = !GameMain.ShowFPS;
1332 NewMessage(
"FPS counter " + (GameMain.DebugDraw ?
"enabled" :
"disabled"), Color.Yellow);
1334 commands.Add(
new Command(
"showperf",
"showperf: Toggle performance statistics on/off.", (
string[] args) =>
1336 GameMain.ShowPerf = !GameMain.ShowPerf;
1337 NewMessage(
"Performance statistics " + (GameMain.ShowPerf ?
"enabled" :
"disabled"), Color.Yellow);
1340 AssignOnClientExecute(
"netstats", (
string[] args) =>
1342 if (GameMain.Client ==
null)
return;
1343 GameMain.Client.ShowNetStats = !GameMain.Client.ShowNetStats;
1346 commands.Add(
new Command(
"hudlayoutdebugdraw|debugdrawhudlayout",
"hudlayoutdebugdraw: Toggle the debug drawing mode of HUD layout areas on/off.", (
string[] args) =>
1348 HUDLayoutSettings.DebugDraw = !HUDLayoutSettings.DebugDraw;
1349 NewMessage(
"HUD layout debug draw mode " + (HUDLayoutSettings.DebugDraw ?
"enabled" :
"disabled"), Color.Yellow);
1352 commands.Add(
new Command(
"interactdebugdraw|debugdrawinteract",
"interactdebugdraw: Toggle the debug drawing mode of item interaction ranges on/off.", (
string[] args) =>
1355 NewMessage(
"Interact debug draw mode " + (
Character.DebugDrawInteract ?
"enabled" :
"disabled"), Color.Yellow);
1358 AssignOnExecute(
"togglehud|hud", (
string[] args) =>
1360 GUI.DisableHUD = !GUI.DisableHUD;
1361 GameMain.Instance.IsMouseVisible = !GameMain.Instance.IsMouseVisible;
1362 NewMessage(GUI.DisableHUD ?
"Disabled HUD" :
"Enabled HUD", Color.Yellow);
1364 AssignRelayToServer(
"togglehud|hud",
false);
1366 AssignOnExecute(
"toggleupperhud", (
string[] args) =>
1368 GUI.DisableUpperHUD = !GUI.DisableUpperHUD;
1369 NewMessage(GUI.DisableUpperHUD ?
"Disabled upper HUD" :
"Enabled upper HUD", Color.Yellow);
1371 AssignRelayToServer(
"toggleupperhud",
false);
1373 AssignOnExecute(
"toggleitemhighlights", (
string[] args) =>
1375 GUI.DisableItemHighlights = !GUI.DisableItemHighlights;
1376 NewMessage(GUI.DisableItemHighlights ?
"Disabled item highlights" :
"Enabled item highlights", Color.Yellow);
1378 AssignRelayToServer(
"toggleitemhighlights",
false);
1380 AssignOnExecute(
"togglecharacternames", (
string[] args) =>
1382 GUI.DisableCharacterNames = !GUI.DisableCharacterNames;
1383 NewMessage(GUI.DisableCharacterNames ?
"Disabled character names" :
"Enabled character names", Color.Yellow);
1385 AssignRelayToServer(
"togglecharacternames",
false);
1387 AssignOnExecute(
"followsub", (
string[] args) =>
1389 Camera.FollowSub = !Camera.FollowSub;
1390 NewMessage(Camera.FollowSub ?
"Set the camera to follow the closest submarine" :
"Disabled submarine following.", Color.Yellow);
1392 AssignRelayToServer(
"followsub",
false);
1394 AssignOnExecute(
"toggleaitargets|aitargets", (
string[] args) =>
1396 AITarget.ShowAITargets = !AITarget.ShowAITargets;
1397 NewMessage(AITarget.ShowAITargets ?
"Enabled AI target drawing" :
"Disabled AI target drawing", Color.Yellow);
1399 AssignRelayToServer(
"toggleaitargets|aitargets",
false);
1401 AssignOnExecute(
"debugai", (
string[] args) =>
1403 HumanAIController.DebugAI = !HumanAIController.DebugAI;
1404 if (HumanAIController.DebugAI)
1406 GameMain.DevMode = true;
1407 GameMain.DebugDraw = true;
1408 GameMain.LightManager.LightingEnabled = false;
1409 GameMain.LightManager.LosEnabled = false;
1413 GameMain.DevMode = false;
1414 GameMain.DebugDraw = false;
1415 GameMain.LightManager.LightingEnabled = true;
1416 GameMain.LightManager.LosEnabled = true;
1417 GameMain.LightManager.LosAlpha = 1f;
1419 NewMessage(HumanAIController.DebugAI ?
"AI debug info visible" :
"AI debug info hidden", Color.Yellow);
1421 AssignRelayToServer(
"debugai",
false);
1423 AssignOnExecute(
"showmonsters", (
string[] args) =>
1425 CreatureMetrics.UnlockAll =
true;
1426 CreatureMetrics.Save();
1427 NewMessage(
"All monsters are now visible in the character editor.", Color.Yellow);
1428 if (Screen.Selected == GameMain.CharacterEditorScreen)
1430 GameMain.CharacterEditorScreen.Deselect();
1431 GameMain.CharacterEditorScreen.Select();
1434 AssignRelayToServer(
"showmonsters",
false);
1436 AssignOnExecute(
"hidemonsters", (
string[] args) =>
1438 CreatureMetrics.UnlockAll =
false;
1439 CreatureMetrics.Save();
1440 NewMessage(
"All monsters that haven't yet been encountered in the game are now hidden in the character editor.", Color.Yellow);
1441 if (Screen.Selected == GameMain.CharacterEditorScreen)
1443 GameMain.CharacterEditorScreen.Deselect();
1444 GameMain.CharacterEditorScreen.Select();
1447 AssignRelayToServer(
"hidemonsters",
false);
1449 AssignRelayToServer(
"water|editwater",
false);
1450 AssignRelayToServer(
"fire|editfire",
false);
1452 AssignRelayToServer(
"debugvoip",
true);
1455 commands.Add(
new Command(
"mute",
"mute [name]: Prevent the client from speaking to anyone through the voice chat. Using this command requires a permission from the server host.",
1459 if (GameMain.Client ==
null)
return null;
1460 return new string[][]
1462 GameMain.Client.ConnectedClients.Select(c => c.Name).ToArray()
1465 commands.Add(
new Command(
"unmute",
"unmute [name]: Allow the client to speak to anyone through the voice chat. Using this command requires a permission from the server host.",
1469 if (GameMain.Client ==
null)
return null;
1470 return new string[][]
1472 GameMain.Client.ConnectedClients.Select(c => c.Name).ToArray()
1476 commands.Add(
new Command(
"checkcrafting",
"checkcrafting: Checks item deconstruction & crafting recipes for inconsistencies.", (
string[] args) =>
1478 List<FabricationRecipe> fabricableItems =
new List<FabricationRecipe>();
1479 foreach (ItemPrefab itemPrefab
in ItemPrefab.Prefabs)
1481 fabricableItems.AddRange(itemPrefab.FabricationRecipes.Values);
1483 foreach (ItemPrefab itemPrefab
in ItemPrefab.Prefabs)
1485 int? minCost = itemPrefab.GetMinPrice();
1486 int? fabricationCost =
null;
1487 int? deconstructProductCost =
null;
1489 var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == itemPrefab && f.RequiredItems.Any());
1490 if (fabricationRecipe !=
null)
1492 foreach (var ingredient
in fabricationRecipe.RequiredItems)
1494 int? ingredientPrice = ingredient.ItemPrefabs.Min(ip => ip.GetMinPrice());
1495 if (ingredientPrice.HasValue)
1497 if (!fabricationCost.HasValue) { fabricationCost = 0; }
1498 float useAmount = ingredient.UseCondition ? ingredient.MinCondition : 1.0f;
1499 fabricationCost += (int)(ingredientPrice.Value * ingredient.Amount * useAmount);
1504 foreach (var deconstructItem
in itemPrefab.DeconstructItems)
1506 if (!(MapEntityPrefab.Find(
null, deconstructItem.ItemIdentifier, showErrorMessages:
false) is ItemPrefab targetItem))
1508 ThrowErrorLocalized(
"Error in item \"" + itemPrefab.Name +
"\" - could not find deconstruct item \"" + deconstructItem.ItemIdentifier +
"\"!");
1512 float avgOutCondition = (deconstructItem.OutConditionMin + deconstructItem.OutConditionMax) / 2;
1514 int? deconstructProductPrice = targetItem.GetMinPrice();
1515 if (deconstructProductPrice.HasValue)
1517 if (!deconstructProductCost.HasValue) { deconstructProductCost = 0; }
1518 deconstructProductCost += (int)(deconstructProductPrice * avgOutCondition);
1521 if (fabricationRecipe !=
null)
1523 var ingredient = fabricationRecipe.RequiredItems.Find(r => r.ItemPrefabs.Contains(targetItem));
1525 if (ingredient ==
null)
1527 foreach (var requiredItem
in fabricationRecipe.RequiredItems)
1529 foreach (var itemPrefab2
in requiredItem.ItemPrefabs)
1531 foreach (var recipe
in itemPrefab2.FabricationRecipes.Values)
1533 ingredient ??= recipe.RequiredItems.Find(r => r.ItemPrefabs.Contains(targetItem));
1539 if (ingredient ==
null)
1541 NewMessage(
"Deconstructing \"" + itemPrefab.Name +
"\" produces \"" + deconstructItem.ItemIdentifier +
"\", which isn't required in the fabrication recipe of the item.", Color.Red);
1543 else if (ingredient.UseCondition && ingredient.MinCondition < avgOutCondition)
1545 NewMessage($
"Deconstructing \"{itemPrefab.Name}\" produces more \"{deconstructItem.ItemIdentifier}\", than what's required to fabricate the item (required: {targetItem.Name} {(int)(ingredient.MinCondition * 100)}%, output: {deconstructItem.ItemIdentifier} {(int)(avgOutCondition * 100)}%)", Color.Red);
1550 if (fabricationCost.HasValue && minCost.HasValue)
1552 if (fabricationCost.Value < minCost * 0.9f)
1554 float ratio = (float)fabricationCost.Value / minCost.Value;
1555 Color color = ToolBox.GradientLerp(ratio, Color.Red, Color.Yellow, Color.Green);
1556 NewMessage(
"The fabrication ingredients of \"" + itemPrefab.Name +
"\" only cost " + (
int)(ratio * 100) +
"% of the price of the item. Item price: " + minCost.Value +
", ingredient prices: " + fabricationCost.Value, color);
1558 else if (fabricationCost.Value > minCost * 1.1f)
1560 float ratio = (float)fabricationCost.Value / minCost.Value;
1561 Color color = ToolBox.GradientLerp(ratio - 1.0f, Color.Green, Color.Yellow, Color.Red);
1562 NewMessage(
"The fabrication ingredients of \"" + itemPrefab.Name +
"\" cost " + (
int)(ratio * 100 - 100) +
"% more than the price of the item. Item price: " + minCost.Value +
", ingredient prices: " + fabricationCost.Value, color);
1565 if (deconstructProductCost.HasValue && minCost.HasValue)
1567 if (deconstructProductCost.Value < minCost * 0.8f)
1569 float ratio = (float)deconstructProductCost.Value / minCost.Value;
1570 Color color = ToolBox.GradientLerp(ratio, Color.Red, Color.Yellow, Color.Green);
1571 NewMessage(
"The deconstruction output of \"" + itemPrefab.Name +
"\" is only worth " + (
int)(ratio * 100) +
"% of the price of the item. Item price: " + minCost.Value +
", output value: " + deconstructProductCost.Value, color);
1573 else if (deconstructProductCost.Value > minCost * 1.1f)
1575 float ratio = (float)deconstructProductCost.Value / minCost.Value;
1576 Color color = ToolBox.GradientLerp(ratio - 1.0f, Color.Green, Color.Yellow, Color.Red);
1577 NewMessage(
"The deconstruction output of \"" + itemPrefab.Name +
"\" is worth " + (
int)(ratio * 100 - 100) +
"% more than the price of the item. Item price: " + minCost.Value +
", output value: " + deconstructProductCost.Value, color);
1581 }, isCheat:
false));
1583 commands.Add(
new Command(
"analyzeitem",
"analyzeitem: Analyzes one item for exploits.", (
string[] args) =>
1585 if (args.Length < 1) {
return; }
1587 List<FabricationRecipe> fabricableItems =
new List<FabricationRecipe>();
1588 foreach (ItemPrefab iPrefab
in ItemPrefab.Prefabs)
1590 fabricableItems.AddRange(iPrefab.FabricationRecipes.Values);
1593 string itemNameOrId = args[0].ToLowerInvariant();
1595 ItemPrefab itemPrefab =
1596 (MapEntityPrefab.FindByName(itemNameOrId) ??
1597 MapEntityPrefab.FindByIdentifier(itemNameOrId.ToIdentifier())) as ItemPrefab;
1599 if (itemPrefab ==
null)
1601 NewMessage(
"Item not found for analyzing.");
1604 if (itemPrefab.DefaultPrice ==
null)
1606 NewMessage($
"Item \"{itemPrefab.Name}\" is not sellable/purchaseable.");
1609 NewMessage(
"Analyzing item " + itemPrefab.Name +
" with base cost " + itemPrefab.DefaultPrice.Price);
1611 var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == itemPrefab);
1613 if (fabricationRecipe !=
null)
1615 foreach (var priceInfo
in itemPrefab.GetSellPricesOver(0))
1617 NewMessage($
" If bought at {GetSeller(priceInfo.Value)} it costs {priceInfo.Value.Price}");
1619 int? totalBestPrice = 0;
1620 foreach (var ingredient
in fabricationRecipe.RequiredItems)
1622 foreach (ItemPrefab ingredientItemPrefab
in ingredient.ItemPrefabs)
1624 int defaultPrice = ingredientItemPrefab.DefaultPrice?.Price ?? 0;
1625 NewMessage($
" Its ingredient {ingredientItemPrefab.Name} has base cost {defaultPrice}");
1626 totalPrice += defaultPrice;
1627 totalBestPrice += ingredientItemPrefab.GetMinPrice();
1628 int basePrice = defaultPrice;
1629 foreach (var ingredientItemPriceInfo
in ingredientItemPrefab.GetBuyPricesUnder())
1631 if (basePrice > ingredientItemPriceInfo.Value.Price)
1633 NewMessage($
" {GetSeller(ingredientItemPriceInfo.Value).CapitaliseFirstInvariant()} sells ingredient {ingredientItemPrefab.Name} for cheaper, {ingredientItemPriceInfo.Value.Price}", Color.Yellow);
1637 NewMessage($
" {GetSeller(ingredientItemPriceInfo.Value).CapitaliseFirstInvariant()} sells ingredient {ingredientItemPrefab.Name} for more, {ingredientItemPriceInfo.Value.Price}", Color.Teal);
1642 int costDifference = itemPrefab.DefaultPrice.Price - totalPrice;
1643 NewMessage($
" Constructing the item from store-bought items provides {costDifference} profit with default values.");
1645 if (totalBestPrice.HasValue)
1647 int? bestDifference = priceInfo.Value.Price - totalBestPrice;
1648 NewMessage($
" Constructing the item from store-bought items provides {bestDifference} profit with best-case scenario values.");
1651 static string GetSeller(PriceInfo priceInfo) => $
"store with identifier \"{priceInfo.StoreIdentifier}\"";
1657 return new string[][] { ItemPrefab.Prefabs.SelectMany(p => p.Aliases).Concat(ItemPrefab.Prefabs.Select(p => p.Identifier.Value)).ToArray() };
1658 }, isCheat:
false));
1660 commands.Add(
new Command(
"checkcraftingexploits",
"checkcraftingexploits: Finds outright item exploits created by buying store-bought ingredients and constructing them into sellable items.", (
string[] args) =>
1662 List<FabricationRecipe> fabricableItems =
new List<FabricationRecipe>();
1663 foreach (ItemPrefab itemPrefab
in ItemPrefab.Prefabs)
1665 fabricableItems.AddRange(itemPrefab.FabricationRecipes.Values);
1667 List<Tuple<string, int>> costDifferences =
new List<Tuple<string, int>>();
1669 int maximumAllowedCost = 5;
1671 if (args.Length > 0)
1673 Int32.TryParse(args[0], out maximumAllowedCost);
1675 foreach (ItemPrefab itemPrefab
in ItemPrefab.Prefabs)
1677 int? defaultCost = itemPrefab.DefaultPrice?.Price;
1678 int? fabricationCostStore =
null;
1680 var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == itemPrefab);
1681 if (fabricationRecipe ==
null)
1686 bool canBeBought =
true;
1688 foreach (var ingredient
in fabricationRecipe.RequiredItems)
1690 int? ingredientPrice = ingredient.ItemPrefabs.Where(p => p.CanBeBought).Min(ip => ip.DefaultPrice?.Price);
1691 if (ingredientPrice.HasValue)
1693 if (!fabricationCostStore.HasValue) { fabricationCostStore = 0; }
1694 float useAmount = ingredient.UseCondition ? ingredient.MinCondition : 1.0f;
1695 fabricationCostStore += (int)(ingredientPrice.Value * ingredient.Amount * useAmount);
1699 canBeBought =
false;
1702 if (fabricationCostStore.HasValue && defaultCost.HasValue && canBeBought)
1704 int costDifference = defaultCost.Value - fabricationCostStore.Value;
1705 if (costDifference > maximumAllowedCost || costDifference < 0f)
1707 float ratio = (float)fabricationCostStore.Value / defaultCost.Value;
1708 string message = $
"Fabricating \"{itemPrefab.Name}\" costs {(int)(ratio * 100)}% of the price of the item, or {costDifference} more. Item price: {defaultCost.Value}, ingredient prices: {fabricationCostStore.Value}";
1709 costDifferences.Add(
new Tuple<string, int>(message, costDifference));
1714 costDifferences.Sort((x, y) => x.Item2.CompareTo(y.Item2));
1716 foreach (Tuple<string, int> costDifference
in costDifferences)
1718 Color color = Color.Yellow;
1719 NewMessage(costDifference.Item1, color);
1721 }, isCheat:
false));
1723 commands.Add(
new Command(
"adjustprice",
"adjustprice: Recursively prints out expected price adjustments for items derived from this item.", (
string[] args) =>
1725 List<FabricationRecipe> fabricableItems =
new List<FabricationRecipe>();
1726 foreach (ItemPrefab iP
in ItemPrefab.Prefabs)
1728 fabricableItems.AddRange(iP.FabricationRecipes.Values);
1730 if (args.Length < 2)
1732 NewMessage(
"Item or value not defined.");
1735 string itemNameOrId = args[0].ToLowerInvariant();
1737 ItemPrefab materialPrefab =
1738 (MapEntityPrefab.Find(itemNameOrId, identifier:
null, showErrorMessages:
false) ??
1739 MapEntityPrefab.Find(
null, identifier: itemNameOrId, showErrorMessages:
false)) as ItemPrefab;
1741 if (materialPrefab ==
null)
1743 NewMessage(
"Item not found for price adjustment.");
1747 AdjustItemTypes adjustItemType = AdjustItemTypes.NoAdjustment;
1748 if (args.Length > 2)
1750 switch (args[2].ToLowerInvariant())
1753 adjustItemType = AdjustItemTypes.Additive;
1756 adjustItemType = AdjustItemTypes.Multiplicative;
1761 if (Int32.TryParse(args[1].ToLowerInvariant(), out
int newPrice))
1763 Dictionary<ItemPrefab, int> newPrices =
new Dictionary<ItemPrefab, int>();
1764 PrintItemCosts(newPrices, materialPrefab, fabricableItems, newPrice,
true, adjustItemType: adjustItemType);
1765 PrintItemCosts(newPrices, materialPrefab, fabricableItems, newPrice,
false, adjustItemType: adjustItemType);
1768 }, isCheat:
false));
1770 commands.Add(
new Command(
"deconstructvalue",
"deconstructvalue: Views and compares deconstructed component prices for this item.", (
string[] args) =>
1772 List<FabricationRecipe> fabricableItems =
new List<FabricationRecipe>();
1773 foreach (ItemPrefab iP
in ItemPrefab.Prefabs)
1775 fabricableItems.AddRange(iP.FabricationRecipes.Values);
1777 if (args.Length < 1)
1779 NewMessage(
"Item not defined.");
1782 string itemNameOrId = args[0].ToLowerInvariant();
1784 ItemPrefab parentItem =
1785 (MapEntityPrefab.Find(itemNameOrId, identifier:
null, showErrorMessages:
false) ??
1786 MapEntityPrefab.Find(
null, identifier: itemNameOrId, showErrorMessages:
false)) as ItemPrefab;
1788 if (parentItem ==
null)
1790 NewMessage(
"Item not found for price adjustment.");
1794 var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == parentItem);
1796 NewMessage(parentItem.Name +
" has the price " + (parentItem.DefaultPrice?.Price ?? 0));
1797 if (fabricationRecipe !=
null)
1799 NewMessage(
" It constructs from:");
1801 foreach (RequiredItem requiredItem
in fabricationRecipe.RequiredItems)
1803 foreach (ItemPrefab itemPrefab
in requiredItem.ItemPrefabs)
1805 int defaultPrice = itemPrefab.DefaultPrice?.Price ?? 0;
1806 NewMessage(
" " + itemPrefab.Name +
" has the price " + defaultPrice);
1807 totalValue += defaultPrice;
1810 NewMessage(
"Its total value was: " + totalValue);
1813 NewMessage(
" The item deconstructs into:");
1814 foreach (DeconstructItem deconstructItem
in parentItem.DeconstructItems)
1816 ItemPrefab itemPrefab =
1817 (MapEntityPrefab.Find(deconstructItem.ItemIdentifier.Value, identifier:
null, showErrorMessages:
false) ??
1818 MapEntityPrefab.Find(
null, identifier: deconstructItem.ItemIdentifier, showErrorMessages:
false)) as ItemPrefab;
1819 if (itemPrefab ==
null)
1821 ThrowError($
" Couldn't find deconstruct product \"{deconstructItem.ItemIdentifier}\"!");
1825 int defaultPrice = itemPrefab.DefaultPrice?.Price ?? 0;
1826 NewMessage(
" " + itemPrefab.Name +
" has the price " + defaultPrice);
1827 totalValue += defaultPrice;
1829 NewMessage(
"Its deconstruct value was: " + totalValue);
1831 }, isCheat:
false));
1833 commands.Add(
new Command(
"setentityproperties",
"setentityproperties [property name] [value]: Sets the value of some property on all selected items/structures in the sub editor.", (
string[] args) =>
1835 if (args.Length != 2 || Screen.Selected != GameMain.SubEditorScreen) {
return; }
1836 foreach (MapEntity me
in MapEntity.SelectedList)
1838 bool propertyFound =
false;
1839 if (!(me is ISerializableEntity serializableEntity)) {
continue; }
1840 if (serializableEntity.SerializableProperties ==
null) {
continue; }
1842 if (serializableEntity.SerializableProperties.TryGetValue(args[0].ToIdentifier(), out SerializableProperty property))
1844 propertyFound =
true;
1845 object prevValue =
property.GetValue(me);
1846 if (property.TrySetValue(me, args[1]))
1848 NewMessage($
"Changed the value \"{args[0]}\" from {(prevValue?.ToString() ?? null)} to {args[1]} on entity \"{me.ToString()}\".", Color.LightGreen);
1852 NewMessage($
"Failed to set the value of \"{args[0]}\" to \"{args[1]}\" on the entity \"{me.ToString()}\".", Color.Orange);
1855 if (me is Item item)
1859 ic.
SerializableProperties.TryGetValue(args[0].ToIdentifier(), out SerializableProperty componentProperty);
1860 if (componentProperty ==
null) {
continue; }
1861 propertyFound =
true;
1862 object prevValue = componentProperty.GetValue(ic);
1863 if (componentProperty.TrySetValue(ic, args[1]))
1865 NewMessage($
"Changed the value \"{args[0]}\" from {prevValue} to {args[1]} on item \"{me.ToString()}\", component \"{ic.GetType().Name}\".", Color.LightGreen);
1869 NewMessage($
"Failed to set the value of \"{args[0]}\" to \"{args[1]}\" on the item \"{me.ToString()}\", component \"{ic.GetType().Name}\".", Color.Orange);
1875 NewMessage($
"Property \"{args[0]}\" not found in the entity \"{me.ToString()}\".", Color.Orange);
1881 List<Identifier> propertyList =
new List<Identifier>();
1882 foreach (MapEntity me
in MapEntity.SelectedList)
1884 if (!(me is ISerializableEntity serializableEntity)) {
continue; }
1885 if (serializableEntity.SerializableProperties ==
null) {
continue; }
1886 propertyList.AddRange(serializableEntity.SerializableProperties.Select(p => p.Key));
1887 if (me is Item item)
1896 return new string[][]
1898 propertyList.Distinct().Select(i => i.Value).OrderBy(n => n).ToArray(),
1899 Array.Empty<
string>()
1903 commands.Add(
new Command(
"checkmissingloca",
"", (
string[] args) =>
1905 void SwapLanguage(LanguageIdentifier language)
1907 var config = GameSettings.CurrentConfig;
1908 config.Language = language;
1909 GameSettings.SetCurrentConfig(config);
1912 HashSet<string> missingTexts =
new HashSet<string>();
1915 Dictionary<Identifier, HashSet<LanguageIdentifier>> missingTags =
new Dictionary<Identifier, HashSet<LanguageIdentifier>>();
1916 Dictionary<LanguageIdentifier, HashSet<Identifier>> tags =
new Dictionary<LanguageIdentifier, HashSet<Identifier>>();
1917 foreach (LanguageIdentifier language
in TextManager.AvailableLanguages)
1919 SwapLanguage(language);
1920 tags.Add(language,
new HashSet<Identifier>(TextManager.GetAllTagTextPairs().Select(t => t.Key)));
1923 foreach (LanguageIdentifier language
in TextManager.AvailableLanguages)
1926 foreach (var missionPrefab
in MissionPrefab.Prefabs)
1928 Identifier missionId = missionPrefab.ConfigElement.GetAttribute(
"textidentifier") ==
null ?
1929 missionPrefab.Identifier :
1930 missionPrefab.ConfigElement.GetAttributeIdentifier(
"textidentifier", Identifier.Empty);
1932 if (!tags[language].Contains(missionPrefab.ConfigElement.GetAttributeIdentifier(
"name", Identifier.Empty)))
1934 addIfMissing($
"missionname.{missionId}".ToIdentifier(), language);
1939 addIfMissing($
"MissionDescriptionNeutral.{missionId}".ToIdentifier(), language);
1940 addIfMissing($
"MissionDescription1.{missionId}".ToIdentifier(), language);
1941 addIfMissing($
"MissionDescription2.{missionId}".ToIdentifier(), language);
1942 addIfMissing($
"MissionTeam1.{missionId}".ToIdentifier(), language);
1943 addIfMissing($
"MissionTeam2.{missionId}".ToIdentifier(), language);
1947 if (!tags[language].Contains(missionPrefab.ConfigElement.GetAttributeIdentifier(
"description", Identifier.Empty)))
1949 addIfMissing($
"missiondescription.{missionId}".ToIdentifier(), language);
1951 if (!tags[language].Contains(missionPrefab.ConfigElement.GetAttributeIdentifier(
"successmessage", Identifier.Empty)))
1953 addIfMissing($
"missionsuccess.{missionId}".ToIdentifier(), language);
1956 if (missionPrefab.ConfigElement.GetAttribute(
"failuremessage") !=
null &&
1957 !tags[language].Contains(missionPrefab.ConfigElement.GetAttributeIdentifier(
"failuremessage", Identifier.Empty)))
1959 addIfMissing($
"missionfailure.{missionId}".ToIdentifier(), language);
1962 for (
int i = 0; i < missionPrefab.Messages.Length; i++)
1964 if (missionPrefab.Messages[i].IsNullOrWhiteSpace() || (missionPrefab.Messages[i] as FallbackLString)?.GetLastFallback() is FallbackLString { PrimaryIsLoaded: false })
1966 addIfMissing($
"MissionMessage{i}.{missionId}".ToIdentifier(), language);
1971 foreach (var eventPrefab
in EventPrefab.Prefabs)
1973 if (eventPrefab is not TraitorEventPrefab traitorEventPrefab) {
continue; }
1974 addIfMissing($
"eventname.{traitorEventPrefab.Identifier}".ToIdentifier(), language);
1977 foreach (Type itemComponentType
in typeof(
ItemComponent).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(
ItemComponent))))
1979 checkSerializableEntityType(itemComponentType);
1981 checkSerializableEntityType(typeof(Item));
1982 checkSerializableEntityType(typeof(Hull));
1983 checkSerializableEntityType(typeof(Structure));
1985 void checkSerializableEntityType(Type t)
1987 foreach (var property
in t.GetProperties())
1989 if (!property.IsDefined(typeof(
Editable),
false)) {
continue; }
1991 string propertyTag = $
"{property.DeclaringType.Name}.{property.Name}";
1993 if (addIfMissingAll(language,
1994 propertyTag.ToIdentifier(),
1995 property.Name.ToIdentifier(),
1996 $
"sp.{property.Name}.name".ToIdentifier(),
1997 $
"sp.{propertyTag}.name".ToIdentifier()) && language ==
"English".ToLanguageIdentifier())
1999 missingTexts.Add($
"<sp.{propertyTag.ToLower()}.name>{property.Name.FormatCamelCaseWithSpaces()}</sp.{propertyTag.ToLower()}.name>");
2002 var description = (
property.GetCustomAttributes(
true).First(a => a is Serialize) as Serialize).Description;
2004 if (addIfMissingAll(language,
2005 $
"sp.{propertyTag}.description".ToIdentifier(),
2006 $
"sp.{property.Name}.description".ToIdentifier(),
2007 $
"{property.Name.ToIdentifier()}.description".ToIdentifier()) && language ==
"English".ToLanguageIdentifier())
2009 missingTexts.Add($
"<sp.{propertyTag.ToLower()}.description>{description}</sp.{propertyTag.ToLower()}.description>");
2014 foreach (SubmarineInfo sub
in SubmarineInfo.SavedSubmarines)
2016 if (sub.Type !=
SubmarineType.Player || !sub.IsVanillaSubmarine()) {
continue; }
2018 addIfMissing($
"submarine.name.{sub.Name}".ToIdentifier(), language);
2019 addIfMissing((
"submarine.description." + sub.Name).ToIdentifier(), language);
2022 foreach (AfflictionPrefab affliction
in AfflictionPrefab.List)
2024 if (affliction.ShowIconThreshold > affliction.MaxStrength &&
2025 affliction.ShowIconToOthersThreshold > affliction.MaxStrength &&
2026 affliction.ShowInHealthScannerThreshold > affliction.MaxStrength)
2032 Identifier afflictionId = affliction.TranslationIdentifier;
2033 addIfMissing($
"afflictionname.{afflictionId}".ToIdentifier(), language);
2035 if (affliction.Descriptions.Any())
2037 foreach (var description
in affliction.Descriptions)
2039 addIfMissing(description.TextTag, language);
2044 addIfMissing($
"afflictiondescription.{afflictionId}".ToIdentifier(), language);
2048 foreach (var talentTree
in TalentTree.JobTalentTrees)
2050 foreach (var talentSubTree
in talentTree.TalentSubTrees)
2052 addIfMissing($
"talenttree.{talentSubTree.Identifier}".ToIdentifier(), language);
2056 foreach (var talent
in TalentPrefab.TalentPrefabs)
2058 addIfMissing($
"talentname.{talent.Identifier}".ToIdentifier(), language);
2062 foreach (MapEntityPrefab me
in MapEntityPrefab.List)
2064 Identifier nameIdentifier = (
"entityname." + me.Identifier).ToIdentifier();
2065 if (tags[language].Contains(nameIdentifier)) {
continue; }
2066 if (me.HideInMenus) {
continue; }
2068 ContentXElement configElement =
null;
2070 if (me is ItemPrefab itemPrefab)
2072 configElement = itemPrefab.ConfigElement;
2074 else if (me is StructurePrefab structurePrefab)
2076 configElement = structurePrefab.ConfigElement;
2078 if (configElement !=
null)
2080 var overrideIdentifier = configElement.GetAttributeIdentifier(
"nameidentifier",
null);
2081 if (overrideIdentifier !=
null && tags[language].Contains(
"entityname." + overrideIdentifier)) {
continue; }
2084 addIfMissing(nameIdentifier, language);
2088 foreach (Identifier englishTag
in tags[TextManager.DefaultLanguage])
2090 foreach (LanguageIdentifier language
in TextManager.AvailableLanguages)
2092 if (language == TextManager.DefaultLanguage) {
continue; }
2093 addIfMissing(englishTag, language);
2097 List<string> lines =
new List<string>
2099 "Missing from English:"
2102 Dictionary<string, List<string>> missingByLanguages =
new Dictionary<string, List<string>>();
2103 List<string> missingFromEnglish =
new List<string>();
2104 foreach (KeyValuePair<Identifier, HashSet<LanguageIdentifier>> kvp
in missingTags)
2106 if (kvp.Value.Contains(TextManager.DefaultLanguage))
2108 missingFromEnglish.Add(kvp.Key.Value);
2112 string languagesStr =
string.Join(
", ", kvp.Value.OrderBy(v => v.Value.Value));
2113 if (!missingByLanguages.ContainsKey(languagesStr))
2115 missingByLanguages.Add(languagesStr,
new List<string>());
2117 missingByLanguages[languagesStr].Add(kvp.Key.Value);
2120 foreach (
string text
in missingFromEnglish.OrderBy(v => v))
2125 foreach (KeyValuePair<
string, List<string>> missingByLanguage
in missingByLanguages)
2127 lines.Add(
string.Empty);
2128 lines.Add($
"Missing from {missingByLanguage.Key}");
2129 foreach (
string text
in missingByLanguage.Value.OrderBy(v => v))
2135 string filePath =
"missingloca.txt";
2137 File.WriteAllLines(filePath, lines);
2139 ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
2140 SwapLanguage(TextManager.DefaultLanguage);
2142 if (missingTexts.Any())
2144 ShowQuestionPrompt(
"Dump the property names and descriptions missing from English to a new xml file? Y/N",
2147 if (option.ToLowerInvariant() ==
"y")
2149 string path =
"newtexts.txt";
2150 Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
2151 File.WriteAllLines(path, missingTexts);
2152 Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
2153 ToolBox.OpenFileWithShell(Path.GetFullPath(path));
2154 SwapLanguage(TextManager.DefaultLanguage);
2159 void addIfMissing(Identifier tag, LanguageIdentifier language)
2161 if (!tags[language].Contains(tag))
2163 if (!missingTags.ContainsKey(tag)) { missingTags[tag] =
new HashSet<LanguageIdentifier>(); }
2164 missingTags[tag].Add(language);
2167 bool addIfMissingAll(LanguageIdentifier language, params Identifier[] potentialTags)
2169 if (!potentialTags.Any(t => tags[language].Contains(t)))
2171 var tag = potentialTags.First();
2172 if (!missingTags.ContainsKey(tag)) { missingTags[tag] =
new HashSet<LanguageIdentifier>(); }
2173 missingTags[tag].Add(language);
2181 commands.Add(
new Command(
"checkduplicateloca",
"", (
string[] args) =>
2183 if (args.Length < 1)
2185 ThrowError(
"Please specify a file path.");
2188 XDocument doc1 = XMLExtensions.TryLoadXml(args[0]);
2189 if (doc1?.Root ==
null)
2191 ThrowError($
"Could not load the file \"{args[0]}\"");
2194 List<(
string tag,
string text)> texts =
new List<(
string tag,
string text)>();
2196 bool duplicatesFound =
false;
2197 foreach (XElement element
in doc1.Root.Elements())
2199 string tag = element.Name.ToString();
2200 string text = element.ElementInnerText();
2201 if (texts.Any(t => t.tag == tag))
2203 ThrowError($
"Duplicate tag \"{tag}\".");
2204 duplicatesFound =
true;
2207 if (duplicatesFound)
2209 ThrowError($
"Aborting, please fix duplicate tags in the file and try again.");
2213 foreach (XElement element
in doc1.Root.Elements())
2215 string tag = element.Name.ToString();
2216 string text = element.ElementInnerText();
2217 if (texts.Any(t => t.text == text))
2219 if (tag.StartsWith(
"sp."))
2221 string[] split = tag.Split(
'.');
2222 if (split.Length > 3)
2224 texts.RemoveAll(t => t.text == text);
2225 string newTag = $
"sp.{split[2]}.{split[3]}";
2226 texts.Add((newTag, text));
2227 NewMessage($
"Duplicate text \"{tag}\", merging to \"{newTag}\".");
2231 NewMessage($
"Duplicate text \"{tag}\", using existing one \"{texts.Find(t => t.text == text).tag}\".");
2236 texts.Add((tag, text));
2237 ThrowError($
"Duplicate text \"{tag}\". Could not determine if the text can be merged with an existing one, please check it manually.");
2242 texts.Add((tag, text));
2246 string filePath =
"uniquetexts.xml";
2248 File.WriteAllLines(filePath, texts.Select(t => $
"<{t.tag}>{t.text}</{t.tag}>"));
2250 ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
2253 commands.Add(
new Command(
"comparelocafiles",
"comparelocafiles [file1] [file2]", (
string[] args) =>
2255 if (args.Length < 2)
2257 ThrowError(
"Please specify two files two compare.");
2261 XDocument doc1 = XMLExtensions.TryLoadXml(args[0]);
2262 if (doc1?.Root ==
null)
2264 ThrowError($
"Could not load the file \"{args[0]}\"");
2267 XDocument doc2 = XMLExtensions.TryLoadXml(args[1]);
2268 if (doc2?.Root ==
null)
2270 ThrowError($
"Could not load the file \"{args[1]}\"");
2274 var content1 = getContent(doc1.Root);
2275 var language1 = doc1.Root.GetAttributeIdentifier(
"language",
string.Empty);
2277 var content2 = getContent(doc2.Root);
2278 var language2 = doc2.Root.GetAttributeIdentifier(
"language",
string.Empty);
2280 foreach (KeyValuePair<string, string> kvp
in content1)
2282 if (!content2.ContainsKey(kvp.Key))
2284 ThrowError($
"File 2 doesn't contain the text tag \"{kvp.Key}\"");
2286 else if (language1 == language2 && content2[kvp.Key] != kvp.Value)
2288 ThrowError($
"Texts for the tag \"{kvp.Key}\" don't match:\n1. {kvp.Value}\n2. {content2[kvp.Key]}");
2291 foreach (KeyValuePair<string, string> kvp
in content2)
2293 if (!content1.ContainsKey(kvp.Key))
2295 ThrowError($
"File 1 doesn't contain the text tag \"{kvp.Key}\"");
2299 static Dictionary<string, string> getContent(XElement element)
2301 Dictionary<string, string> content =
new Dictionary<string, string>();
2302 foreach (XElement subElement
in element.Elements())
2304 string key = subElement.Name.ToString().ToLowerInvariant();
2305 if (content.ContainsKey(key)) {
continue; }
2306 content.Add(key, subElement.ElementInnerText());
2312 commands.Add(
new Command(
"eventstats",
"", (
string[] args) =>
2314 List<string> debugLines;
2315 if (args.Length > 0)
2317 if (!Enum.TryParse(args[0], ignoreCase:
true, out Level.PositionType spawnType))
2319 var enums = Enum.GetNames(typeof(Level.PositionType));
2320 ThrowError($
"\"{args[0]}\" is not a valid Level.PositionType. Available options are: {string.Join(",
", enums)}");
2323 bool fullLog =
false;
2324 if (args.Length > 1)
2326 bool.TryParse(args[1], out fullLog);
2328 debugLines = EventSet.GetDebugStatistics(filter: monsterEvent => monsterEvent.SpawnPosType.HasFlag(spawnType), fullLog: fullLog);
2332 debugLines = EventSet.GetDebugStatistics();
2334 string filePath =
"eventstats.txt";
2336 File.WriteAllLines(filePath, debugLines);
2338 ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
2341 commands.Add(
new Command(
"setfreecamspeed",
"setfreecamspeed [speed]: Set the camera movement speed when not controlling a character. Defaults to 1.", (
string[] args) =>
2343 if (args.Length > 0)
2345 float.TryParse(args[0], NumberStyles.Number, CultureInfo.InvariantCulture, out
float speed);
2346 Screen.Selected.Cam.FreeCamMoveSpeed = speed;
2350 commands.Add(
new Command(
"converttowreck",
"", (
string[] args) =>
2352 if (Screen.Selected is not SubEditorScreen)
2354 ThrowError(
"The command can only be used in the submarine editor.");
2359 ThrowError(
"Load a submarine first to convert it to a wreck.");
2362 if (
Submarine.MainSub.Info.SubmarineElement ==
null)
2364 ThrowError(
"The submarine must be saved before you can convert it to a wreck.");
2367 var wreckedSubmarineInfo =
new SubmarineInfo(filePath:
string.Empty, element: WreckConverter.ConvertToWreck(
Submarine.MainSub.Info.SubmarineElement));
2368 wreckedSubmarineInfo.Name +=
"_Wrecked";
2370 GameMain.SubEditorScreen.LoadSub(wreckedSubmarineInfo);
2374 commands.Add(
new Command(
"deathprompt",
"Shows the death prompt for testing purposes.", (
string[] args) =>
2376 DeathPrompt.Create(delay: 1.0f);
2379 commands.Add(
new Command(
"listspamfilters",
"Lists filters that are in the global spam filter.", (
string[] args) =>
2381 if (!SpamServerFilters.GlobalSpamFilter.TryUnwrap(out var filter))
2383 ThrowError(
"Global spam list is not initialized.");
2387 if (!filter.Filters.Any())
2389 NewMessage(
"Global spam list is empty.", GUIStyle.Green);
2393 StringBuilder sb =
new();
2395 foreach (var f
in filter.Filters)
2397 sb.AppendLine(f.ToString());
2400 NewMessage(sb.ToString(), GUIStyle.Green);
2403 commands.Add(
new Command(
"setplanthealth",
"setplanthealth [value]: Sets the health of the selected plant in sub editor.", (
string[] args) =>
2405 if (1 > args.Length || Screen.Selected != GameMain.SubEditorScreen) {
return; }
2407 string arg = args[0];
2409 if (!
float.TryParse(arg, out
float value))
2411 ThrowError($
"{arg} is not a valid value.");
2415 foreach (MapEntity me
in MapEntity.SelectedList)
2419 if (it.GetComponent<Planter>() is { } planter)
2421 foreach (Growable seed
in planter.GrowableSeeds.Where(s => s !=
null))
2423 NewMessage($
"Set the health of {seed.Name} to {value} (from {seed.Health})");
2424 seed.Health = value;
2427 else if (it.GetComponent<Growable>() is { } seed)
2429 NewMessage($
"Set the health of {seed.Name} to {value} (from {seed.Health})");
2430 seed.Health = value;
2436 commands.Add(
new Command(
"showballastflorasprite",
"", (
string[] args) =>
2438 BallastFloraBehavior.AlwaysShowBallastFloraSprite = !BallastFloraBehavior.AlwaysShowBallastFloraSprite;
2439 NewMessage(
"ok", GUIStyle.Green);
2442 commands.Add(
new Command(
"printreceivertransfers",
"", (
string[] args) =>
2444 GameMain.Client.PrintReceiverTransters();
2447 commands.Add(
new Command(
"spamchatmessages",
"", (
string[] args) =>
2449 int msgCount = 1000;
2450 if (args.Length > 0)
int.TryParse(args[0], out msgCount);
2452 if (args.Length > 1)
int.TryParse(args[1], out msgLength);
2454 for (
int i = 0; i < msgCount; i++)
2456 if (GameMain.Client !=
null)
2458 GameMain.Client.SendChatMessage(ToolBox.RandomSeed(msgLength));
2463 commands.Add(
new Command(
"getprefabinfo",
"", (
string[] args) =>
2465 var prefab = MapEntityPrefab.Find(
null, args[0]);
2468 NewMessage(prefab.Name +
" " + prefab.Identifier +
" " + prefab.GetType().ToString());
2472 commands.Add(
new Command(
"copycharacterinfotoclipboard",
"", (
string[] args) =>
2476 XElement element =
Character.Controlled?.Info.Save(
null);
2477 Clipboard.SetText(element.ToString());
2478 DebugConsole.NewMessage($
"Copied the characterinfo of {Character.Controlled.Name} to clipboard.");
2482 commands.Add(
new Command(
"spawnallitems",
"", (
string[] args) =>
2484 var cursorPos = Screen.Selected.Cam?.ScreenToWorld(PlayerInput.MousePosition) ?? Vector2.Zero;
2485 foreach (ItemPrefab itemPrefab
in ItemPrefab.Prefabs)
2487 Entity.Spawner?.AddItemToSpawnQueue(itemPrefab, cursorPos);
2491 commands.Add(
new Command(
"camerasettings",
"camerasettings [defaultzoom] [zoomsmoothness] [movesmoothness] [minzoom] [maxzoom]: debug command for testing camera settings. The values default to 1.1, 8.0, 8.0, 0.1 and 2.0.", (
string[] args) =>
2493 float defaultZoom = Screen.Selected.Cam.DefaultZoom;
2494 if (args.Length > 0)
float.TryParse(args[0], NumberStyles.Number, CultureInfo.InvariantCulture, out defaultZoom);
2496 float zoomSmoothness = Screen.Selected.Cam.ZoomSmoothness;
2497 if (args.Length > 1)
float.TryParse(args[1], NumberStyles.Number, CultureInfo.InvariantCulture, out zoomSmoothness);
2498 float moveSmoothness = Screen.Selected.Cam.MoveSmoothness;
2499 if (args.Length > 2)
float.TryParse(args[2], NumberStyles.Number, CultureInfo.InvariantCulture, out moveSmoothness);
2501 float minZoom = Screen.Selected.Cam.MinZoom;
2502 if (args.Length > 3)
float.TryParse(args[3], NumberStyles.Number, CultureInfo.InvariantCulture, out minZoom);
2503 float maxZoom = Screen.Selected.Cam.MaxZoom;
2504 if (args.Length > 4)
float.TryParse(args[4], NumberStyles.Number, CultureInfo.InvariantCulture, out maxZoom);
2506 Screen.Selected.Cam.DefaultZoom = defaultZoom;
2507 Screen.Selected.Cam.ZoomSmoothness = zoomSmoothness;
2508 Screen.Selected.Cam.MoveSmoothness = moveSmoothness;
2509 Screen.Selected.Cam.MinZoom = minZoom;
2510 Screen.Selected.Cam.MaxZoom = maxZoom;
2513 commands.Add(
new Command(
"waterparams",
"waterparams [distortionscalex] [distortionscaley] [distortionstrengthx] [distortionstrengthy] [bluramount]: default 0.5 0.5 0.5 0.5 1", (
string[] args) =>
2515 float distortScaleX = 0.5f, distortScaleY = 0.5f;
2516 float distortStrengthX = 0.5f, distortStrengthY = 0.5f;
2517 float blurAmount = 0.0f;
2518 if (args.Length > 0)
float.TryParse(args[0], NumberStyles.Number, CultureInfo.InvariantCulture, out distortScaleX);
2519 if (args.Length > 1)
float.TryParse(args[1], NumberStyles.Number, CultureInfo.InvariantCulture, out distortScaleY);
2520 if (args.Length > 2)
float.TryParse(args[2], NumberStyles.Number, CultureInfo.InvariantCulture, out distortStrengthX);
2521 if (args.Length > 3)
float.TryParse(args[3], NumberStyles.Number, CultureInfo.InvariantCulture, out distortStrengthY);
2522 if (args.Length > 4)
float.TryParse(args[4], NumberStyles.Number, CultureInfo.InvariantCulture, out blurAmount);
2523 WaterRenderer.DistortionScale =
new Vector2(distortScaleX, distortScaleY);
2524 WaterRenderer.DistortionStrength =
new Vector2(distortStrengthX, distortStrengthY);
2525 WaterRenderer.BlurAmount = blurAmount;
2528 commands.Add(
new Command(
"generatelevels",
"generatelevels [amount]: generate a bunch of levels with the currently selected parameters in the level editor.", (
string[] args) =>
2530 if (GameMain.GameSession ==
null)
2533 if (args.Length > 0) {
int.TryParse(args[0], out amount); }
2534 GameMain.LevelEditorScreen.TestLevelGenerationForErrors(amount);
2538 NewMessage(
"Can't use the command while round is running.");
2542 commands.Add(
new Command(
"listcontainertags",
"Lists all container tags on the submarine.", (
string[] args) =>
2544 if (Screen.Selected != GameMain.SubEditorScreen)
2546 ThrowError(
"This command can only be used in the sub editor.");
2550 HashSet<Identifier> allContainerTagsInTheGame =
new();
2552 foreach (var itemPrefab
in ItemPrefab.Prefabs)
2554 foreach (var pc
in itemPrefab.PreferredContainers)
2556 foreach (Identifier identifier
in Enumerable.Union(pc.Primary, pc.Secondary))
2558 allContainerTagsInTheGame.Add(identifier);
2563 Dictionary<Identifier, float> prefab =
new();
2565 foreach (Item it
in Item.ItemList)
2567 foreach (var tag
in allContainerTagsInTheGame)
2569 if (it.GetTags().All(t => tag != t)) {
continue; }
2571 prefab.TryAdd(tag, 0.0f);
2576 StringBuilder sb =
new();
2577 foreach (var (tag, amount) in prefab.OrderByDescending(kvp => kvp.Value))
2579 sb.AppendLine($
"{tag}: {amount}");
2582 NewMessage(sb.ToString());
2583 }, isCheat:
false));
2585 commands.Add(
new Command(
"refreshrect",
"Updates the dimensions of the selected items to match the ones defined in the prefab. Applied only in the subeditor.", (
string[] args) =>
2588 if (Screen.Selected == GameMain.SubEditorScreen)
2590 if (!MapEntity.SelectedAny)
2592 ThrowError(
"You have to select item(s) first!");
2596 foreach (var mapEntity
in MapEntity.SelectedList)
2598 if (mapEntity is Item item)
2600 item.Rect = item.DefaultRect =
new Rectangle(item.Rect.X, item.Rect.Y,
2601 (
int)(item.Prefab.Sprite.size.X * item.Prefab.Scale),
2602 (
int)(item.Prefab.Sprite.size.Y * item.Prefab.Scale));
2604 else if (mapEntity is Structure structure)
2606 if (!structure.ResizeHorizontal)
2608 structure.Rect = structure.DefaultRect =
new Rectangle(structure.Rect.X, structure.Rect.Y,
2609 (
int)structure.Prefab.ScaledSize.X,
2610 structure.Rect.Height);
2612 if (!structure.ResizeVertical)
2614 structure.Rect = structure.DefaultRect =
new Rectangle(structure.Rect.X, structure.Rect.Y,
2615 structure.Rect.Width,
2616 (
int)structure.Prefab.ScaledSize.Y);
2623 }, isCheat:
false));
2625 commands.Add(
new Command(
"flip",
"Flip the currently controlled character.", (
string[] args) =>
2627 Character.Controlled?.AnimController.Flip();
2628 }, isCheat:
false));
2629 commands.Add(
new Command(
"mirror",
"Mirror the currently controlled character.", (
string[] args) =>
2631 (
Character.Controlled?.AnimController as FishAnimController)?.Mirror(lerp:
false);
2632 }, isCheat:
false));
2633 commands.Add(
new Command(
"forcetimeout",
"Immediately cause the client to time out if one is running.", (
string[] args) =>
2635 GameMain.Client?.ForceTimeOut();
2636 }, isCheat:
false));
2637 commands.Add(
new Command(
"bumpitem",
"", (
string[] args) =>
2640 if (args.Length > 0)
2642 float.TryParse(args[0], NumberStyles.Number, CultureInfo.InvariantCulture, out vel);
2644 Character.Controlled?.FocusedItem?.body?.ApplyLinearImpulse(Rand.Vector(vel));
2645 }, isCheat:
false));
2649 commands.Add(
new Command(
"dumptexts",
"dumptexts [filepath]: Extracts all the texts from the given text xml and writes them into a file (using the same filename, but with the .txt extension). If the filepath is omitted, the EnglishVanilla.xml file is used.", (
string[] args) =>
2651 string filePath = args.Length > 0 ? args[0] :
"Content/Texts/EnglishVanilla.xml";
2652 var doc = XMLExtensions.TryLoadXml(filePath);
2653 if (doc ==
null) {
return; }
2654 List<string> lines =
new List<string>();
2655 foreach (XElement element
in doc.Root.Elements())
2657 lines.Add(element.ElementInnerText());
2659 File.WriteAllLines(Path.GetFileNameWithoutExtension(filePath) +
".txt", lines);
2663 var files = TextManager.GetTextFiles().Select(f => f.CleanUpPath());
2664 return new string[][]
2666 TextManager.GetTextFiles().Where(f => Path.GetExtension(f)==
".xml").ToArray()
2670 commands.Add(
new Command(
"loadtexts",
"loadtexts [sourcefile] [destinationfile]: Loads all lines of text from a given .txt file and inserts them sequientially into the elements of an xml file. If the file paths are omitted, EnglishVanilla.txt and EnglishVanilla.xml are used.", (
string[] args) =>
2672 string sourcePath = args.Length > 0 ? args[0] :
"Content/Texts/EnglishVanilla.txt";
2673 string destinationPath = args.Length > 1 ? args[1] :
"Content/Texts/EnglishVanilla.xml";
2678 lines = File.ReadAllLines(sourcePath);
2682 ThrowError(
"Reading the file \"" + sourcePath +
"\" failed.", e);
2685 var doc = XMLExtensions.TryLoadXml(destinationPath);
2686 if (doc ==
null) {
return; }
2688 foreach (XElement element
in doc.Root.Elements())
2690 if (i >= lines.Length)
2692 ThrowError(
"Error while loading texts to the xml file. The xml has more elements than the number of lines in the text file.");
2695 element.Value = lines[i];
2698 doc.SaveSafe(destinationPath);
2702 var files = TextManager.GetTextFiles().Select(f => f.CleanUpPath());
2703 return new string[][]
2705 files.Where(f => Path.GetExtension(f)==
".txt").ToArray(),
2706 files.Where(f => Path.GetExtension(f)==
".xml").ToArray()
2710 commands.Add(
new Command(
"updatetextfile",
"updatetextfile [sourcefile] [destinationfile]: Inserts all the xml elements that are only present in the source file into the destination file. Can be used to update outdated translation files more easily.", (
string[] args) =>
2712 if (args.Length < 2)
return;
2713 string sourcePath = args[0];
2714 string destinationPath = args[1];
2716 var sourceDoc = XMLExtensions.TryLoadXml(sourcePath);
2717 var destinationDoc = XMLExtensions.TryLoadXml(destinationPath);
2719 if (sourceDoc ==
null || destinationDoc ==
null) {
return; }
2721 XElement destinationElement = destinationDoc.Root.Elements().First();
2722 foreach (XElement element
in sourceDoc.Root.Elements())
2724 if (destinationDoc.Root.Element(element.Name) ==
null)
2726 element.Value =
"!!!!!!!!!!!!!" + element.Value;
2727 destinationElement.AddAfterSelf(element);
2729 XNode nextNode = destinationElement.NextNode;
2730 while ((!(nextNode is XElement) || nextNode == element) && nextNode !=
null) nextNode = nextNode.NextNode;
2731 destinationElement = nextNode as XElement;
2733 destinationDoc.SaveSafe(destinationPath);
2737 var files = TextManager.GetTextFiles().Where(f => Path.GetExtension(f) ==
".xml").
Select(f => f.CleanUpPath()).ToArray();
2738 return new string[][]
2745 commands.Add(
new Command(
"dumpentitytexts",
"dumpentitytexts [filepath]: gets the names and descriptions of all entity prefabs and writes them into a file along with xml tags that can be used in translation files. If the filepath is omitted, the file is written to Content/Texts/EntityTexts.txt", (
string[] args) =>
2747 string filePath = args.Length > 0 ? args[0] :
"Content/Texts/EntityTexts.txt";
2748 List<string> lines =
new List<string>();
2749 foreach (MapEntityPrefab me
in MapEntityPrefab.List)
2751 lines.Add($
"<EntityName.{me.Identifier}>{me.Name}</EntityName.{me.Identifier}>");
2752 lines.Add($
"<EntityDescription.{me.Identifier}>{me.Description}</EntityDescription.{me.Identifier}>");
2755 File.WriteAllLines(filePath, lines);
2759 commands.Add(
new Command(
"dumpeventtexts",
"dumpeventtexts [filepath]: gets the texts from event files and and writes them into a file along with xml tags that can be used in translation files. If the filepath is omitted, the file is written to Content/Texts/EventTexts.txt", (
string[] args) =>
2761 string filePath = args.Length > 0 ? args[0] :
"Content/Texts/EventTexts.txt";
2762 List<string> lines =
new List<string>();
2763 HashSet<XDocument> docs =
new HashSet<XDocument>();
2764 HashSet<string> textIds =
new HashSet<string>();
2766 Dictionary<string, string> existingTexts =
new Dictionary<string, string>();
2768 foreach (EventPrefab eventPrefab
in EventSet.GetAllEventPrefabs())
2770 if (eventPrefab is not TraitorEventPrefab) {
continue; }
2771 if (eventPrefab.Identifier.IsEmpty)
2775 docs.Add(eventPrefab.ConfigElement.Document);
2776 getTextsFromElement(eventPrefab.ConfigElement, lines, eventPrefab.Identifier.Value);
2779 File.WriteAllLines(filePath, lines);
2782 ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
2786 ThrowError($
"Failed to open the file \"{filePath}\".", e);
2789 System.Xml.XmlWriterSettings settings =
new System.Xml.XmlWriterSettings
2792 NewLineOnAttributes =
false
2794 foreach (XDocument doc
in docs)
2796 using (var writer =
XmlWriter.
Create(
new System.Uri(doc.BaseUri).LocalPath, settings))
2798 doc.WriteTo(writer);
2804 void getTextsFromElement(XElement element, List<string> list,
string parentName)
2806 string text = element.GetAttributeString(
"text",
null);
2807 string textAttribute =
"text";
2808 XElement textElement = element;
2811 var subTextElement = element?.Element(
"Text");
2812 if (subTextElement !=
null)
2814 textAttribute =
"tag";
2815 text = subTextElement?.GetAttributeString(textAttribute,
null);
2816 textElement = subTextElement;
2820 AddWarning(
"Failed to find text from the element " + element.ToString());
2824 string textId = $
"EventText.{parentName}";
2825 if (!
string.IsNullOrEmpty(text) && !text.Contains(
"EventText.", StringComparison.OrdinalIgnoreCase))
2827 if (existingTexts.TryGetValue(text, out
string existingTextId))
2829 textElement.SetAttributeValue(textAttribute, existingTextId);
2833 textIds.Add(parentName);
2834 list.Add($
"<{textId}>{text}</{textId}>");
2835 existingTexts.Add(text, textId);
2836 textElement.SetAttributeValue(textAttribute, textId);
2840 int conversationIndex = 1;
2841 int objectiveIndex = 1;
2842 foreach (var subElement
in element.Elements())
2844 string elementName = parentName;
2845 bool ignore =
false;
2846 switch (subElement.Name.ToString().ToLowerInvariant())
2848 case "conversationaction":
2849 while (textIds.Contains(elementName +
".c" + conversationIndex))
2851 conversationIndex++;
2853 elementName +=
".c" + conversationIndex;
2855 case "eventlogaction":
2856 while (textIds.Contains(elementName +
".objective" + objectiveIndex))
2860 elementName +=
".objective" + objectiveIndex;
2863 while (textIds.Contains(elementName.Substring(0, elementName.Length - 3) +
".o" + conversationIndex))
2865 conversationIndex++;
2867 elementName = elementName.Substring(0, elementName.Length - 3) +
".o" + conversationIndex;
2873 if (ignore) {
continue; }
2874 getTextsFromElement(subElement, list, elementName);
2879 commands.Add(
new Command(
"itemcomponentdocumentation",
"", (
string[] args) =>
2881 Dictionary<string, string> typeNames =
new Dictionary<string, string>
2883 {
"Single",
"Float"},
2884 {
"Int32",
"Integer"},
2885 {
"Boolean",
"True/False"},
2886 {
"String",
"Text"},
2889 var itemComponentTypes = typeof(
ItemComponent).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(
ItemComponent))).ToList();
2890 itemComponentTypes.Sort((i1, i2) => {
return i1.Name.CompareTo(i2.Name); });
2894 string filePath = args.Length > 0 ? args[0] :
"ItemComponentDocumentation.txt";
2895 List<string> lines =
new List<string>();
2896 foreach (Type t
in itemComponentTypes)
2899 lines.Add($
"[h1]{t.Name}[/h1]");
2902 var properties = t.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.DeclaredOnly).ToList();
2903 Type baseType = t.BaseType;
2904 while (baseType !=
null && baseType != typeof(
ItemComponent))
2906 properties.AddRange(baseType.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.DeclaredOnly));
2907 baseType = baseType.BaseType;
2910 if (!properties.Any(p => p.GetCustomAttributes(
true).Any(a => a is Serialize)))
2912 lines.Add(
"No editable properties.");
2917 lines.Add(
"[table]");
2920 lines.Add(
" [th]Name[/th]");
2921 lines.Add(
" [th]Type[/th]");
2922 lines.Add(
" [th]Default value[/th]");
2924 lines.Add(
" [th]Description[/th]");
2926 lines.Add(
" [/tr]");
2930 Dictionary<Identifier, SerializableProperty> dictionary =
new Dictionary<Identifier, SerializableProperty>();
2931 foreach (var property
in properties)
2933 object[] attributes =
property.GetCustomAttributes(
true);
2934 Serialize serialize = attributes.FirstOrDefault(a => a is Serialize) as Serialize;
2935 if (serialize ==
null) {
continue; }
2937 string propertyTypeName =
property.PropertyType.Name;
2938 if (typeNames.ContainsKey(propertyTypeName))
2940 propertyTypeName = typeNames[propertyTypeName];
2942 else if (property.PropertyType.IsEnum)
2944 List<string> valueNames =
new List<string>();
2945 foreach (
object enumValue
in Enum.GetValues(property.PropertyType))
2947 valueNames.Add(enumValue.ToString());
2949 propertyTypeName =
string.Join(
"/", valueNames);
2951 string defaultValueString = serialize.DefaultValue?.ToString() ??
"";
2952 if (property.PropertyType == typeof(
float))
2954 defaultValueString = ((float)serialize.DefaultValue).ToString(CultureInfo.InvariantCulture);
2959 lines.Add($
" [td]{property.Name}[/td]");
2960 lines.Add($
" [td]{propertyTypeName}[/td]");
2961 lines.Add($
" [td]{defaultValueString}[/td]");
2964 string rangeText =
"-";
2965 if (editable !=
null)
2967 if (editable.
MinValueFloat >
float.MinValue || editable.MaxValueFloat <
float.MaxValue)
2969 rangeText = editable.
MinValueFloat +
"-" + editable.MaxValueFloat;
2971 else if (editable.
MinValueInt >
int.MinValue || editable.MaxValueInt <
int.MaxValue)
2973 rangeText = editable.
MinValueInt +
"-" + editable.MaxValueInt;
2978 if (!
string.IsNullOrEmpty(serialize.Description))
2980 lines.Add($
" [td]{serialize.Description}[/td]");
2983 lines.Add(
" [/tr]");
2985 lines.Add(
"[/table]");
2989 File.WriteAllLines(filePath, lines);
2991 ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
2995 commands.Add(
new Command(
"checkduplicates",
"Checks the given language for duplicate translation keys and writes to file.", (
string[] args) =>
2997 if (args.Length != 1) {
return; }
2998 TextManager.CheckForDuplicates(args[0].ToIdentifier().ToLanguageIdentifier());
3001 commands.Add(
new Command(
"writetocsv|xmltocsv",
"Writes the default language (English) to a .csv file.", (
string[] args) =>
3003 TextManager.WriteToCSV();
3004 NPCConversation.WriteToCSV();
3007 commands.Add(
new Command(
"csvtoxml",
"csvtoxml -> Converts .csv localization files Content/Texts/Texts.csv and Content/Texts/NPCConversations.csv to .xml for use in-game.", (
string[] args) =>
3009 ShowQuestionPrompt(
"Do you want to save the text files to the project folder (../../../BarotraumaShared/Content/Texts/)? If not, they are saved in the current working directory. Y/N",
3012 ShowQuestionPrompt(
"Do you want to convert the NPC conversations as well? Y/N",
3015 LocalizationCSVtoXML.ConvertMasterLocalizationKit(
3016 option1.ToLowerInvariant() ==
"y" ?
"../../../BarotraumaShared/Content/Texts/" :
"Content/Texts",
3017 option1.ToLowerInvariant() ==
"y" ?
"../../../BarotraumaShared/Content/NPCConversations/" :
"Content/NPCConversations",
3018 convertConversations: option2.ToLowerInvariant() ==
"y");
3023 commands.Add(
new Command(
"printproperties",
"Goes through the currently collected property list for missing localizations and writes them to a file.", (
string[] args) =>
3025 string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) +
"\\propertylocalization.txt";
3027 File.WriteAllLines(path, SerializableEntityEditor.MissingLocalizations);
3031 commands.Add(
new Command(
"getproperties",
"Goes through the MapEntity prefabs and checks their serializable properties for localization issues.", (
string[] args) =>
3033 if (Screen.Selected != GameMain.SubEditorScreen)
return;
3034 foreach (MapEntityPrefab ep
in MapEntityPrefab.List)
3036 ep.DebugCreateInstance();
3039 for (
int i = 0; i < MapEntity.MapEntityList.Count; i++)
3041 var entity = MapEntity.MapEntityList[i] as ISerializableEntity;
3044 List<(
object obj, SerializableProperty property)> allProperties =
new List<(
object obj, SerializableProperty property)>();
3046 if (entity is Item item)
3048 allProperties.AddRange(item.GetProperties<
Editable>());
3053 var properties =
new List<SerializableProperty>();
3054 properties.AddRange(SerializableProperty.GetProperties<
Editable>(entity));
3055 properties.AddRange(SerializableProperty.GetProperties<
InGameEditable>(entity));
3057 for (
int k = 0; k < properties.Count; k++)
3059 allProperties.Add((entity, properties[k]));
3063 for (
int j = 0; j < allProperties.Count; j++)
3065 var
property = allProperties[j].property;
3066 string propertyName = (allProperties[j].obj.GetType().Name +
"." +
property.PropertyInfo.Name).ToLowerInvariant();
3067 LocalizedString displayName = TextManager.Get($
"sp.{propertyName}.name");
3068 if (displayName.IsNullOrEmpty())
3070 displayName =
property.Name.FormatCamelCaseWithSpaces();
3073 if (editable !=
null)
3075 if (!SerializableEntityEditor.MissingLocalizations.Contains($
"sp.{propertyName}.name|{displayName}"))
3077 NewMessage(
"Missing Localization for property: " + propertyName);
3078 SerializableEntityEditor.MissingLocalizations.Add($
"sp.{propertyName}.name|{displayName}");
3079 SerializableEntityEditor.MissingLocalizations.Add($
"sp.{propertyName}.description|{property.GetAttribute<Serialize>().Description}");
3089 commands.Add(
new Command(
"reloadcorepackage",
"", (
string[] args) =>
3091 if (args.Length < 1)
3093 if (Screen.Selected == GameMain.GameScreen)
3095 ThrowError(
"Reloading the core package while in GameScreen WILL break everything; to do it anyway, type 'reloadcorepackage force'");
3099 if (Screen.Selected == GameMain.SubEditorScreen)
3101 ThrowError(
"Reloading the core package while in sub editor WILL break everything; to do it anyway, type 'reloadcorepackage force'");
3106 if (GameMain.NetworkMember !=
null)
3108 ThrowError(
"Cannot change content packages while playing online");
3112 ContentPackageManager.EnabledPackages.ReloadCore();
3115 commands.Add(
new Command(
"reloadpackage",
"reloapackage [name]: reloads a content package.", (
string[] args) =>
3117 if (args.Length < 1)
3119 ThrowError(
"Please specify the name of the package to reload.");
3123 if (args.Length < 2)
3125 if (Screen.Selected == GameMain.GameScreen)
3127 ThrowError(
"Reloading the package while in GameScreen may break things; to do it anyway, type 'reloadpackage [name] force'");
3130 if (Screen.Selected == GameMain.SubEditorScreen)
3132 ThrowError(
"Reloading the core package while in sub editor may break things; to do it anyway, type 'reloadpackage [name] force'");
3137 if (GameMain.NetworkMember !=
null)
3139 ThrowError(
"Cannot change content packages while playing online");
3143 var
package = ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Name == args[0]);
3144 if (package ==
null)
3146 ThrowError($
"Could not find the package {args[0]}!");
3149 ContentPackageManager.EnabledPackages.ReloadPackage(package);
3150 }, getValidArgs: () =>
new[]
3152 ContentPackageManager.RegularPackages.Select(p => p.Name).ToArray()
3156 commands.Add(
new Command(
"startdedicatedserver",
"", (
string[] args) =>
3158 Process.Start(
"DedicatedServer.exe");
3161 commands.Add(
new Command(
"editserversettings",
"", (
string[] args) =>
3163 if (Process.GetProcessesByName(
"DedicatedServer").Length > 0)
3165 NewMessage(
"Can't be edited if DedicatedServer.exe is already running", Color.Red);
3169 Process.Start(
"notepad.exe",
"serversettings.xml");
3174 #warning TODO: reimplement?
3188 AssignOnClientExecute(
3192 if (args.Length < 1) { return; }
3194 NewMessage(
"Valid permissions are:", Color.White);
3197 NewMessage(
" - " + permission.ToString(), Color.White);
3199 ShowQuestionPrompt(
"Permission to grant to client " + args[0] +
"?", (perm) =>
3201 GameMain.Client?.SendConsoleCommand(
"giveperm \"" + args[0] +
"\" " + perm);
3206 AssignOnClientExecute(
3210 if (args.Length < 1) { return; }
3212 if (args.Length < 2)
3214 NewMessage(
"Valid permissions are:", Color.White);
3215 foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions)))
3217 NewMessage(
" - " + permission.ToString(), Color.White);
3221 ShowQuestionPrompt(
"Permission to revoke from client " + args[0] +
"?", (perm) =>
3223 GameMain.Client?.SendConsoleCommand(
"revokeperm \"" + args[0] +
"\" " + perm);
3228 AssignOnClientExecute(
3232 if (args.Length < 1)
return;
3234 NewMessage(
"Valid ranks are:", Color.White);
3237 NewMessage(
" - " + permissionPreset.DisplayName, Color.White);
3239 ShowQuestionPrompt(
"Rank to grant to client " + args[0] +
"?", (rank) =>
3241 GameMain.Client?.SendConsoleCommand(
"giverank \"" + args[0] +
"\" " + rank);
3246 AssignOnClientExecute(
3250 if (args.Length < 1)
return;
3252 ShowQuestionPrompt(
"Console command permissions to grant to client " + args[0] +
"? You may enter multiple commands separated with a space or use \"all\" to give the permission to use all console commands.", (commandNames) =>
3254 GameMain.Client?.SendConsoleCommand(
"givecommandperm \"" + args[0] +
"\" " + commandNames);
3259 AssignOnClientExecute(
3260 "revokecommandperm",
3263 if (args.Length < 1)
return;
3265 ShowQuestionPrompt(
"Console command permissions to revoke from client " + args[0] +
"? You may enter multiple commands separated with a space or use \"all\" to revoke the permission to use any console commands.", (commandNames) =>
3267 GameMain.Client?.SendConsoleCommand(
"revokecommandperm \"" + args[0] +
"\" " + commandNames);
3272 AssignOnClientExecute(
3276 if (args.Length < 1)
return;
3278 GameMain.Client.SendConsoleCommand(
"showperm " + args[0]);
3282 AssignOnClientExecute(
3286 if (GameMain.Client ==
null || args.Length == 0)
return;
3287 ShowQuestionPrompt(
"Reason for banning the endpoint \"" + args[0] +
"\"? (Enter c to cancel)", (reason) =>
3289 if (reason ==
"c" || reason ==
"C") {
return; }
3290 ShowQuestionPrompt(
"Enter the duration of the ban (leave empty to ban permanently, or use the format \"[days] d [hours] h\") (Enter c to cancel)", (duration) =>
3292 if (duration ==
"c" || duration ==
"C") {
return; }
3293 TimeSpan? banDuration =
null;
3294 if (!
string.IsNullOrWhiteSpace(duration))
3296 if (!TryParseTimeSpan(duration, out TimeSpan parsedBanDuration))
3298 ThrowError(
"\"" + duration +
"\" is not a valid ban duration. Use the format \"[days] d [hours] h\", \"[days] d\" or \"[hours] h\".");
3301 banDuration = parsedBanDuration;
3304 GameMain.Client?.SendConsoleCommand(
3307 (banDuration.HasValue ? banDuration.Value.TotalSeconds.ToString() :
"0") +
" " +
3314 commands.Add(
new Command(
"unban",
"unban [name]: Unban a specific client.", (
string[] args) =>
3316 if (GameMain.Client ==
null || args.Length == 0)
return;
3317 string clientName =
string.Join(
" ", args);
3318 GameMain.Client.UnbanPlayer(clientName);
3321 commands.Add(
new Command(
"unbanaddress",
"unbanaddress [endpoint]: Unban a specific endpoint.", (
string[] args) =>
3323 if (GameMain.Client ==
null || args.Length == 0)
return;
3326 GameMain.Client.UnbanPlayer(endpoint);
3330 AssignOnClientExecute(
3331 "campaigndestination|setcampaigndestination",
3334 var campaign = GameMain.GameSession?.GameMode as CampaignMode;
3335 if (campaign ==
null)
3337 ThrowError(
"No campaign active!");
3341 if (args.Length == 0)
3344 foreach (LocationConnection connection
in campaign.Map.CurrentLocation.Connections)
3346 NewMessage(
" " + i +
". " + connection.OtherLocation(campaign.Map.CurrentLocation).DisplayName, Color.White);
3349 ShowQuestionPrompt(
"Select a destination (0 - " + (campaign.Map.CurrentLocation.Connections.Count - 1) +
"):", (
string selectedDestination) =>
3351 int destinationIndex = -1;
3352 if (!int.TryParse(selectedDestination, out destinationIndex)) return;
3353 if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count)
3355 NewMessage(
"Index out of bounds!", Color.Red);
3358 GameMain.Client?.SendConsoleCommand(
"campaigndestination " + destinationIndex);
3363 int destinationIndex = -1;
3364 if (!
int.TryParse(args[0], out destinationIndex))
return;
3365 if (destinationIndex < 0 || destinationIndex >= campaign.Map.CurrentLocation.Connections.Count)
3367 NewMessage(
"Index out of bounds!", Color.Red);
3370 GameMain.Client.SendConsoleCommand(
"campaigndestination " + destinationIndex);
3376 commands.Add(
new Command(
"setcurrentlocationtype",
"setcurrentlocationtype [location type]: Change the type of the current location.", (
string[] args) =>
3379 if (GameMain.GameSession?.Campaign ==
null)
3381 ThrowError(
"Campaign not active!");
3384 if (args.Length == 0)
3386 ThrowError(
"Please give the location type after the command.");
3389 var locationType = LocationType.Prefabs.Find(lt => lt.Identifier == args[0]);
3390 if (locationType ==
null)
3392 ThrowError($
"Could not find the location type \"{args[0]}\".");
3395 GameMain.GameSession.Campaign.Map.CurrentLocation.ChangeType(GameMain.GameSession.Campaign, locationType);
3399 return new string[][]
3401 LocationType.Prefabs.Select(lt => lt.Identifier.Value).ToArray()
3405 commands.Add(
new Command(
"sendrawpacket",
"sendrawpacket [data]: Send a string of hex values as raw binary data to the server", (
string[] args) =>
3407 if (GameMain.NetworkMember is
null)
3409 ThrowError(
"Not connected to a server");
3413 if (args.Length == 0)
3415 ThrowError(
"No data provided");
3419 string dataString =
string.Join(
" ", args);
3423 byte[] bytes = ToolBox.HexStringToBytes(dataString);
3425 foreach (
byte b
in bytes) { msg.
WriteByte(b); }
3426 GameMain.Client?.ClientPeer?.DebugSendRawMessage(msg);
3427 NewMessage($
"Sent {bytes.Length} byte(s)", Color.Green);
3431 ThrowError(
"Failed to parse the data", e);
3436 commands.Add(
new Command(
"limbscale",
"Define the limbscale for the controlled character. Provide id or name if you want to target another character. Note: the changes are not saved!", (
string[] args) =>
3439 if (character ==
null)
3441 ThrowError(
"Not controlling any character!");
3444 if (args.Length == 0)
3446 ThrowError(
"Please give the value after the command.");
3449 if (!
float.TryParse(args[0], NumberStyles.Number, CultureInfo.InvariantCulture, out
float value))
3451 ThrowError(
"Failed to parse float value from the arguments");
3454 RagdollParams ragdollParams = character.AnimController.RagdollParams;
3455 ragdollParams.LimbScale = MathHelper.Clamp(value, RagdollParams.MIN_SCALE, RagdollParams.MAX_SCALE);
3456 var pos = character.WorldPosition;
3457 character.AnimController.Recreate();
3458 character.TeleportTo(pos);
3461 commands.Add(
new Command(
"jointscale",
"Define the jointscale for the controlled character. Provide id or name if you want to target another character. Note: the changes are not saved!", (
string[] args) =>
3464 if (character ==
null)
3466 ThrowError(
"Not controlling any character!");
3469 if (args.Length == 0)
3471 ThrowError(
"Please give the value after the command.");
3474 if (!
float.TryParse(args[0], NumberStyles.Number, CultureInfo.InvariantCulture, out
float value))
3476 ThrowError(
"Failed to parse float value from the arguments");
3479 RagdollParams ragdollParams = character.AnimController.RagdollParams;
3480 ragdollParams.JointScale = MathHelper.Clamp(value, RagdollParams.MIN_SCALE, RagdollParams.MAX_SCALE);
3481 var pos = character.WorldPosition;
3482 character.AnimController.Recreate();
3483 character.TeleportTo(pos);
3486 commands.Add(
new Command(
"ragdollscale",
"Rescale the ragdoll of the controlled character. Provide id or name if you want to target another character. Note: the changes are not saved!", (
string[] args) =>
3489 if (character ==
null)
3491 ThrowError(
"Not controlling any character!");
3494 if (args.Length == 0)
3496 ThrowError(
"Please give the value after the command.");
3499 if (!
float.TryParse(args[0], NumberStyles.Number, CultureInfo.InvariantCulture, out
float value))
3501 ThrowError(
"Failed to parse float value from the arguments");
3504 RagdollParams ragdollParams = character.AnimController.RagdollParams;
3505 ragdollParams.LimbScale = MathHelper.Clamp(value, RagdollParams.MIN_SCALE, RagdollParams.MAX_SCALE);
3506 ragdollParams.JointScale = MathHelper.Clamp(value, RagdollParams.MIN_SCALE, RagdollParams.MAX_SCALE);
3507 var pos = character.WorldPosition;
3508 character.AnimController.Recreate();
3509 character.TeleportTo(pos);
3512 commands.Add(
new Command(
"recreateragdoll",
"Recreate the ragdoll of the controlled character. Provide id or name if you want to target another character.", (
string[] args) =>
3514 var character = (args.Length == 0) ?
Character.Controlled : FindMatchingCharacter(args,
true);
3515 if (character ==
null)
3517 ThrowError(
"Not controlling any character!");
3520 var pos = character.WorldPosition;
3521 character.AnimController.Recreate();
3522 character.TeleportTo(pos);
3525 commands.Add(
new Command(
"resetragdoll",
"Reset the ragdoll of the controlled character. Provide id or name if you want to target another character.", (
string[] args) =>
3527 var character = (args.Length == 0) ?
Character.Controlled : FindMatchingCharacter(args,
true);
3528 if (character ==
null)
3530 ThrowError(
"Not controlling any character!");
3533 character.AnimController.ResetRagdoll(forceReload:
true);
3536 commands.Add(
new Command(
"loadanimation",
"Loads an animation variation by name for the controlled character. The animation file has to be in the correct animations folder. Note: the changes are not saved!", (
string[] args) =>
3539 if (character ==
null)
3541 ThrowError(
"Not controlling any character!");
3544 if (args.Length < 2)
3546 ThrowError(
"Insufficient parameters: Have to pass the type of animation (Walk, Run, SwimSlow, SwimFast, or Crouch) and the filename!");
3549 string type = args[0];
3550 if (!Enum.TryParse(type, ignoreCase:
true, out AnimationType animationType))
3552 ThrowError($
"Failed to parse animation type from {type}. Supported types are Walk, Run, SwimSlow, SwimFast, and Crouch!");
3555 string fileName = args[1];
3556 character.AnimController.TryLoadAnimation(animationType, Path.GetFileNameWithoutExtension(fileName), out _, throwErrors:
true);
3559 commands.Add(
new Command(
"reloadwearables",
"Reloads the sprites of all limbs and wearable sprites (clothing) of the controlled character. Provide id or name if you want to target another character.", args =>
3561 var character = (args.Length == 0) ?
Character.Controlled : FindMatchingCharacter(args,
true);
3562 if (character ==
null)
3564 ThrowError(
"Not controlling any character or no matching character found with the provided arguments.");
3567 ReloadWearables(character);
3570 commands.Add(
new Command(
"loadwearable",
"Force select certain variant for the selected character.", args =>
3573 if (character ==
null)
3575 ThrowError(
"Not controlling any character.");
3578 if (args.Length == 0)
3580 ThrowError(
"No arguments provided! Give an index number for the variant starting from 1.");
3583 if (
int.TryParse(args[0], out
int variant))
3585 ReloadWearables(character, variant);
3590 commands.Add(
new Command(
"reloadsprite|reloadsprites",
"Reloads the sprites of the selected item(s)/structure(s) (hovering over or selecting in the subeditor) or the controlled character. Can also reload sprites by entity id or by the name attribute (sprite element). Example 1: reloadsprite id itemid. Example 2: reloadsprite name \"Sprite name\"", args =>
3592 if (Screen.Selected is SpriteEditorScreen)
3596 else if (args.Length > 1)
3598 TryDoActionOnSprite(args[0], args[1], s =>
3604 else if (Screen.Selected is SubEditorScreen)
3606 if (!MapEntity.SelectedAny)
3608 ThrowError(
"You have to select item(s)/structure(s) first!");
3610 MapEntity.SelectedList.ForEach(e =>
3612 if (e.Sprite !=
null)
3614 e.Sprite.ReloadXML();
3615 e.Sprite.ReloadTexture();
3622 if (character ==
null)
3624 ThrowError(
"Please provide the mode (name or id) and the value so that I can find the sprite for you!");
3627 var item = character.FocusedItem;
3630 item.Sprite.ReloadXML();
3631 item.Sprite.ReloadTexture();
3635 ReloadWearables(character);
3640 commands.Add(
new Command(
"flipx",
"flipx: mirror the main submarine horizontally", (
string[] args) =>
3642 if (GameMain.NetworkMember !=
null)
3644 ThrowError(
"Cannot use the flipx command while playing online.");
3650 commands.Add(
new Command(
"head",
"Load the head sprite and the wearables (hair etc). Required argument: head id. Optional arguments: hair index, beard index, moustache index, face attachment index.", args =>
3653 if (character ==
null)
3655 ThrowError(
"Not controlling any character!");
3658 if (args.Length == 0)
3660 ThrowError(
"No head id provided!");
3663 if (
int.TryParse(args[0], out
int id))
3665 int hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex;
3666 hairIndex = beardIndex = moustacheIndex = faceAttachmentIndex = -1;
3667 if (args.Length > 1)
3669 int.TryParse(args[1], out hairIndex);
3671 if (args.Length > 2)
3673 int.TryParse(args[2], out beardIndex);
3675 if (args.Length > 3)
3677 int.TryParse(args[3], out moustacheIndex);
3679 if (args.Length > 4)
3681 int.TryParse(args[4], out faceAttachmentIndex);
3683 character.ReloadHead(
id, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
3684 foreach (var limb
in character.AnimController.Limbs)
3688 limb.RecreateSprites();
3694 commands.Add(
new Command(
"spawnsub",
"spawnsub [subname] [is thalamus]: Spawn a submarine at the position of the cursor", (
string[] args) =>
3696 if (GameMain.NetworkMember !=
null)
3698 ThrowError(
"Cannot spawn additional submarines during a multiplayer session.");
3701 if (args.Length == 0)
3703 ThrowError(
"Please enter the name of the submarine.");
3708 var subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s =>
3710 s.DisplayName.Equals(args[0], StringComparison.OrdinalIgnoreCase) ||
3711 s.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase));
3712 if (subInfo ==
null)
3714 ThrowError($
"Could not find a submarine with the name \"{args[0]}\".");
3719 spawnedSub.SetPosition(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition));
3722 spawnedSub.MakeWreck();
3723 if (args.Length > 1 &&
bool.TryParse(args[1], out
bool isThalamus))
3727 spawnedSub.CreateWreckAI();
3731 spawnedSub.DisableWreckAI();
3736 spawnedSub.DisableWreckAI();
3743 string errorMsg =
"Failed to spawn a submarine. Arguments: \"" +
string.Join(
" ", args) +
"\".";
3744 ThrowError(errorMsg, e);
3745 GameAnalyticsManager.AddErrorEventOnce(
"DebugConsole.SpawnSubmarine:Error", GameAnalyticsManager.ErrorSeverity.Error, errorMsg +
'\n' + e.Message +
'\n' + e.StackTrace.CleanupStackTrace());
3750 return new string[][]
3752 SubmarineInfo.SavedSubmarines.Select(s => s.DisplayName.Value).ToArray()
3757 commands.Add(
new Command(
"pause",
"Toggles the pause state when playing offline", (
string[] args) =>
3759 if (GameMain.NetworkMember ==
null)
3762 DebugConsole.NewMessage(
"Game paused: " + Paused);
3766 DebugConsole.NewMessage(
"Cannot pause when a multiplayer session is active.");
3770 AssignOnClientExecute(
"showseed|showlevelseed", (
string[] args) =>
3772 if (Level.Loaded ==
null)
3774 ThrowError(
"No level loaded.");
3778 NewMessage(
"Level seed: " + Level.Loaded.Seed);
3779 NewMessage(
"Level generation params: " + Level.Loaded.GenerationParams.Identifier);
3780 NewMessage(
"Adjacent locations: " + (Level.Loaded.StartLocation?.Type.Identifier ??
"none".ToIdentifier()) +
", " + (Level.Loaded.StartLocation?.Type.Identifier ??
"none".ToIdentifier()));
3781 NewMessage(
"Mirrored: " + Level.Loaded.Mirrored);
3782 NewMessage(
"Level size: " + Level.Loaded.Size.X +
"x" + Level.Loaded.Size.Y);
3783 NewMessage(
"Minimum main path width: " + (Level.Loaded.LevelData?.MinMainPathWidth?.ToString() ??
"unknown"));
3787 commands.Add(
new Command(
"cl_lua", $
"cl_lua: Runs a string on the client.", (
string[] args) =>
3789 if (GameMain.Client !=
null && !GameMain.Client.HasPermission(
ClientPermissions.ConsoleCommands))
3791 ThrowError(
"Command not permitted.");
3795 if (GameMain.LuaCs.Lua ==
null)
3797 ThrowError(
"LuaCs not initialized, use the console command cl_reloadluacs to force initialization.");
3803 GameMain.LuaCs.Lua.DoString(string.Join(
" ", args));
3807 LuaCsLogger.HandleException(ex, LuaCsMessageOrigin.LuaMod);
3811 commands.Add(
new Command(
"cl_reloadlua|cl_reloadcs|cl_reloadluacs",
"Re-initializes the LuaCs environment.", (
string[] args) =>
3813 GameMain.LuaCs.Initialize();
3816 commands.Add(
new Command(
"cl_toggleluadebug",
"Toggles the MoonSharp Debug Server.", (
string[] args) =>
3820 if (args.Length > 0)
3822 int.TryParse(args[0], out port);
3825 GameMain.LuaCs.ToggleDebugger(port);
3829 private static void ReloadWearables(Character character,
int variant = 0)
3831 foreach (var limb
in character.AnimController.Limbs)
3833 limb.Sprite?.ReloadTexture();
3834 limb.DamagedSprite?.ReloadTexture();
3835 limb.DeformSprite?.Sprite.ReloadTexture();
3836 foreach (var wearable
in limb.WearingItems)
3838 if (variant > 0 && wearable.Variant > 0)
3840 wearable.Variant = variant;
3842 wearable.ParsePath(
true);
3843 wearable.Sprite.ReloadXML();
3844 wearable.Sprite.ReloadTexture();
3846 foreach (var wearable
in limb.OtherWearables)
3848 wearable.ParsePath(
true);
3849 wearable.Sprite.ReloadXML();
3850 wearable.Sprite.ReloadTexture();
3852 if (limb.HuskSprite !=
null)
3854 limb.HuskSprite.Sprite.ReloadXML();
3855 limb.HuskSprite.Sprite.ReloadTexture();
3857 if (limb.HerpesSprite !=
null)
3859 limb.HerpesSprite.Sprite.ReloadXML();
3860 limb.HerpesSprite.Sprite.ReloadTexture();
3865 private static bool TryDoActionOnSprite(
string firstArg,
string secondArg, Action<Sprite> action)
3870 var sprites = Sprite.LoadedSprites.Where(s => s.Name !=
null && s.Name.Equals(secondArg, StringComparison.OrdinalIgnoreCase));
3873 foreach (var s
in sprites)
3881 ThrowError(
"Cannot find any matching sprites by the name: " + secondArg);
3886 sprites = Sprite.LoadedSprites.Where(s => s.EntityIdentifier !=
null && s.EntityIdentifier == secondArg);
3889 foreach (var s
in sprites)
3897 ThrowError(
"Cannot find any matching sprites by the id: " + secondArg);
3901 ThrowError(
"The first argument must be either 'name' or 'id'");
3907 private enum AdjustItemTypes
3914 private static void PrintItemCosts(Dictionary<ItemPrefab, int> newPrices, ItemPrefab materialPrefab, List<FabricationRecipe> fabricableItems,
int newPrice,
bool adjustDown,
string depth =
"", AdjustItemTypes adjustItemType = AdjustItemTypes.NoAdjustment)
3918 NewMessage(depth + materialPrefab.Name +
" cannot be adjusted to this price, because it would become less than 1.");
3923 newPrices.TryAdd(materialPrefab, newPrice);
3925 int componentCost = 0;
3926 int newComponentCost = 0;
3928 var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == materialPrefab);
3930 if (fabricationRecipe !=
null)
3932 foreach (RequiredItem requiredItem
in fabricationRecipe.RequiredItems)
3934 foreach (ItemPrefab itemPrefab
in requiredItem.ItemPrefabs)
3936 GetAdjustedPrice(itemPrefab, ref componentCost, ref newComponentCost, newPrices);
3940 string componentCostMultiplier =
"";
3941 if (componentCost > 0)
3943 componentCostMultiplier = $
" (Relative difference to component cost {GetComponentCostDifference(materialPrefab.DefaultPrice.Price, componentCost)} => {GetComponentCostDifference(newPrice, newComponentCost)}, or flat profit {(int)(materialPrefab.DefaultPrice.Price - (int)componentCost)} => {newPrice - newComponentCost})";
3945 string priceAdjustment =
"";
3946 if (newPrice != materialPrefab.DefaultPrice.Price)
3948 priceAdjustment =
", Suggested price adjustment is " + materialPrefab.DefaultPrice.Price +
" => " + newPrice;
3950 NewMessage(depth + materialPrefab.Name +
"(" + materialPrefab.DefaultPrice.Price +
") " + priceAdjustment + componentCostMultiplier);
3954 if (componentCost > 0)
3956 double newPriceMult = (double)newPrice / (
double)(materialPrefab.DefaultPrice.Price);
3957 int newPriceDiff = componentCost + newPrice - materialPrefab.DefaultPrice.Price;
3959 switch (adjustItemType)
3961 case AdjustItemTypes.Additive:
3962 NewMessage(depth + materialPrefab.Name +
"'s components should be adjusted " + componentCost +
" => " + newPriceDiff);
3964 case AdjustItemTypes.Multiplicative:
3965 NewMessage(depth + materialPrefab.Name +
"'s components should be adjusted " + componentCost +
" => " + Math.Round(newPriceMult * componentCost));
3969 if (fabricationRecipe !=
null)
3971 foreach (RequiredItem requiredItem
in fabricationRecipe.RequiredItems)
3973 foreach (ItemPrefab itemPrefab
in requiredItem.ItemPrefabs)
3975 if (itemPrefab.DefaultPrice !=
null)
3977 switch (adjustItemType)
3979 case AdjustItemTypes.NoAdjustment:
3980 PrintItemCosts(newPrices, itemPrefab, fabricableItems, itemPrefab.DefaultPrice.Price, adjustDown, depth, adjustItemType);
3982 case AdjustItemTypes.Additive:
3983 PrintItemCosts(newPrices, itemPrefab, fabricableItems, itemPrefab.DefaultPrice.Price + (
int)((newPrice - materialPrefab.DefaultPrice.Price) / (
double)fabricationRecipe.RequiredItems.Length), adjustDown, depth, adjustItemType);
3985 case AdjustItemTypes.Multiplicative:
3986 PrintItemCosts(newPrices, itemPrefab, fabricableItems, (
int)(itemPrefab.DefaultPrice.Price * newPriceMult), adjustDown, depth, adjustItemType);
3997 var fabricationRecipes = fabricableItems.Where(f => f.RequiredItems.Any(x => x.ItemPrefabs.Contains(materialPrefab)));
4003 int targetComponentCost = 0;
4004 int newTargetComponentCost = 0;
4006 foreach (RequiredItem requiredItem
in fabricationRecipeParent.
RequiredItems)
4008 foreach (ItemPrefab itemPrefab
in requiredItem.ItemPrefabs)
4010 GetAdjustedPrice(itemPrefab, ref targetComponentCost, ref newTargetComponentCost, newPrices);
4013 switch (adjustItemType)
4015 case AdjustItemTypes.NoAdjustment:
4018 case AdjustItemTypes.Additive:
4019 PrintItemCosts(newPrices, fabricationRecipeParent.
TargetItem, fabricableItems, fabricationRecipeParent.
TargetItem.
DefaultPrice.
Price + newPrice - materialPrefab.DefaultPrice.Price, adjustDown, depth, adjustItemType);
4021 case AdjustItemTypes.Multiplicative:
4022 double maintainedMultiplier = GetComponentCostDifference(fabricationRecipeParent.
TargetItem.
DefaultPrice.
Price, targetComponentCost);
4023 PrintItemCosts(newPrices, fabricationRecipeParent.
TargetItem, fabricableItems, (
int)(newTargetComponentCost * maintainedMultiplier), adjustDown, depth, adjustItemType);
4031 private static double GetComponentCostDifference(
int itemCost,
int componentCost)
4033 return Math.Round((
double)(itemCost / (
double)componentCost), 2);
4036 private static void GetAdjustedPrice(ItemPrefab itemPrefab, ref
int componentCost, ref
int newComponentCost, Dictionary<ItemPrefab, int> newPrices)
4038 if (newPrices.TryGetValue(itemPrefab, out
int newPrice))
4040 newComponentCost += newPrice;
4042 else if (itemPrefab.DefaultPrice !=
null)
4044 newComponentCost += itemPrefab.DefaultPrice.Price;
4046 if (itemPrefab.DefaultPrice !=
null)
4048 componentCost += itemPrefab.DefaultPrice.Price;
void Set(KeyOrMouse key, string command)
void Remove(KeyOrMouse key)
IReadOnlyDictionary< KeyOrMouse, string > Bindings
static DebugConsoleMapping Instance
void ClientExecute(string[] args)
readonly bool IsCheat
Using a command that's considered a cheat disables achievements
Action< string[]> OnExecute
readonly ImmutableArray< Identifier > Names
Action< string[]> OnClientExecute
Executed when a client uses the command. If not set, the command is relayed to the server as-is.
readonly ImmutableArray< RequiredItem > RequiredItems
static GameSession?? GameSession
static NetworkMember NetworkMember
static XmlWriter Create(string path, System.Xml.XmlWriterSettings settings)
void Drop(Character dropper, bool createNetworkEvent=true, bool setTransform=true)
static bool DebugWiringMode
The base class for components holding the different functionalities of the item
Dictionary< Identifier, SerializableProperty > SerializableProperties
static Option< Endpoint > Parse(string str)
bool HasPermission(ClientPermissions permission)
bool HasConsoleCommandPermission(Identifier commandName)
static readonly List< PermissionPreset > List
static void SetErrorReasonCallback(ErrorReasonCallback callback)