Mod Loader/Reference/Debug console API
Open the in-game console with Ctrl+`. Type help to list commands. The console keeps the latest 120 output lines in memory; Unity messages and command exceptions are also written to the game or BepInEx logs. Ordinary command replies are only kept in the console unless your plugin logs them separately.
Register a command
Commands run on Unity's main thread. A handler receives the text after the command name as one string and returns text to print. Command names may contain letters, digits, hyphens, and underscores.
using System;
using BepInEx;
using DressmakerMods;
[BepInPlugin("example.inspect", "Inspect Example", "1.0.0")]
[BepInDependency("yuu.dressmaker.mods")]
public sealed class InspectExample : BaseUnityPlugin
{
private IDisposable command;
private void Start()
{
command = DebugConsoleApi.Register("hello", "Greet someone.", name =>
"Hello, " + (name.Length == 0 ? "Dressmaker" : name) + "!");
DebugConsoleApi.Write("Try: hello Yuu");
}
private void OnDestroy() => command?.Dispose();
}
Registering a name that is already in use throws an exception. Dispose the registration when your plugin unloads.
Read command arguments
The handler gets everything after the command name as one string. For example, commission-prompt My Commission passes My Commission. The console doesn't split quoted arguments or remove quotes. For a command that takes one ID, use the whole argument and don't put quotation marks around it.
If your command takes a number, check it with TryParse and return a usage message when it isn't valid:
command = DebugConsoleApi.Register("repeat-word", "Usage: repeat-word 1 to 5", argument =>
{
if (!int.TryParse(argument, out int count) || count < 1 || count > 5)
return "Enter a number from 1 to 5. Example: repeat-word 3";
return string.Join(" ", System.Linq.Enumerable.Repeat("hello", count));
});
Command names are case-insensitive. How arguments are matched is up to your handler. Return a message for an expected problem, such as an unknown ID. Unexpected exceptions are caught by the console and written to the loader log.
Request a particular commission
To test a commission, you can add a command that requests its briefing at the front desk. Dressmaker's own quest-selection menu calls FrontDesk.RequestQuestPrompt; the example below uses the same method.
Use a backed-up test save. Requesting a briefing bypasses the normal choice of which commission to offer, including its prerequisites. Continuing through the dialogue and measurements can update and save progress. This example doesn't erase completed quests or replace an active commission.
Build the command plugin
Create a BepInEx plugin as described in Write a C# mod. Add references to DressmakerMods.dll, BepInEx.dll, 0Harmony.dll, Assembly-CSharp.dll, Framework.dll, and the game's Unity assemblies. The loader provides command registration; QuestDefinition, PlayerProgress, GameManager and FrontDesk belong to the game.
Put this code in your plugin project. It adds two commands:
using System;
using System.Collections.Generic;
using System.Linq;
using BepInEx;
using DressmakerMods;
using Framework;
using HarmonyLib;
[BepInPlugin("example.commission-console", "Commission Console", "1.0.0")]
[BepInDependency("yuu.dressmaker.mods")]
public sealed class CommissionConsole : BaseUnityPlugin
{
private readonly List<IDisposable> commands = new List<IDisposable>();
private void Start()
{
commands.Add(DebugConsoleApi.Register("commission-list",
"List up to 30 commission IDs. Usage: commission-list [part of ID]", ListCommissions));
commands.Add(DebugConsoleApi.Register("commission-prompt",
"Request a commission briefing. Usage: commission-prompt exact ID", PromptCommission));
}
private static string ListCommissions(string argument)
{
string filter = argument.Trim();
var matches = QuestDefinition.AllQuestDefinitions
.Where(q => q != null && q.name.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0)
.OrderBy(q => q.name).ToArray();
if (matches.Length == 0) return "No matching commissions. Check that your story pack loaded.";
string result = string.Join("\n", matches.Take(30).Select(q => q.name));
return matches.Length > 30 ? result + "\nMore matches exist. Add part of the ID to narrow the list." : result;
}
private static string PromptCommission(string argument)
{
string id = argument.Trim();
if (id.Length == 0) return "Usage: commission-prompt exact ID\nFind an ID with commission-list.";
if (!GameManager.HasInstance || !FrontDesk.HasInstance)
return "Load a test save and go to the front desk first.";
var game = GameManager.Instance;
var desk = FrontDesk.Instance;
if (game.CurrentScene != GameManager.Scene.FrontDesk || !desk.isActiveAndEnabled)
return "Go to the front desk first.";
if (desk.IsPresenting || game.proposedQuest != null || game.commissionSubmitted)
return "Finish the current conversation or hand-in first.";
var quest = QuestDefinition.AllQuestDefinitions.FirstOrDefault(q => q != null &&
string.Equals(q.name, id, StringComparison.OrdinalIgnoreCase));
if (quest == null) return "Unknown commission ID. Use commission-list to find one.";
if (quest.questGiverCharacter == null || quest.IsGiftQuest())
return "Choose a regular commission with a client.";
var progress = PlayerProgress.Current;
if (progress.completedQuests.Count == 0)
return "Finish the first commission before using this example.";
if (progress.activeCommissions.Count != 0)
return "Finish your active commissions first. This example doesn't replace them.";
if (progress.HasCompletedQuest(quest))
return "This save has already completed that commission. Use a different test save.";
// Check the same dialogue lookup the game uses before starting its presentation loop.
var check = AccessTools.Method(typeof(FrontDesk), "HasYarnDialogue",
new[] { typeof(QuestDefinition), typeof(QuestDefinition.QuestOutcome?) });
if (check == null) return "This game build has a different dialogue check. Update the command before using it.";
if (!(bool)check.Invoke(desk, new object[] { quest, null }))
return "The commission's prompt dialogue isn't loaded. Check the story pack and game log.";
desk.RequestQuestPrompt(quest);
return "Requested " + quest.name + ". Close the console to continue the briefing.";
}
private void OnDestroy()
{
foreach (var command in commands) command.Dispose();
commands.Clear();
}
}
Try it in game
- Compile the plugin, put its DLL in
BepInEx/plugins, and restart Dressmaker. - Load a test save that has finished the first commission. Finish any active commissions, then go to the front desk and wait for the conversation to end.
- Open the console with Ctrl+`. Type
commission-list, or add part of an ID to filter it, such ascommission-list gerard. - Copy the exact ID from the result and enter
commission-prompt exact ID, replacingexact IDwith that result. Don't add quotes. - Close the console and continue the client's briefing. Follow the usual measuring flow.
These commands are added by this example plugin; they aren't built-in commands. The list uses internal IDs, not translated commission titles. Modded commissions appear once their pack has registered them.
How the example works
commission-list searches the loaded QuestDefinition.AllQuestDefinitions list and limits output to 30 matches so it fits in the console history. Narrow the search if there are more.
commission-prompt checks the scene and conversation state, then finds the commission. It refuses gifts, missing clients, active commissions and already-completed commissions. This keeps the example focused on testing a new regular commission. It doesn't provide a replay system for completed story branches.
Before requesting the briefing, it calls the game's private HasYarnDialogue method through Harmony to check whether the prompt is loaded. That check and the front-desk methods can change with game updates. If the check is missing, the command stops and tells you to update it. A successful return means the briefing was requested, not that the commission was accepted or completed.
Don't create a QuestState and append it to activeCommissions just to start the job. The normal flow also handles measurements, the selected commission and saving. Skipping those steps can leave an incomplete state.
Print progress and clean up
Return text for a single reply. Use DebugConsoleApi.Write for later updates, such as a message from a coroutine after an operation finishes. Keep Unity work and console calls on the main thread; don't call them from a background task.
DebugConsoleApi.Run("help commission-prompt") runs through the same command handler and prints its result. If you need a reply in BepInEx/LogOutput.log too, write it with your plugin's Logger.LogInfo.
Keep each registration handle and dispose it in OnDestroy, as shown above. Disposing a handle removes that command. Use a distinct name or a mod prefix to avoid colliding with another plugin.
Methods and properties
IDisposable Register(string name, string description, Func<string, string> handler)- Adds a command. The handler argument is the full remainder of the command line.
string Run(string commandLine)- Runs a command as if entered in the console and returns its result. Unknown commands return an error string.
void Write(string message)- Adds text to the console output. Newlines create separate lines.
bool IsOpen- True while the console window is open.
Built-in commands include help, clear, version, scene, and art-export. Run help to see commands added by installed plugins. Exceptions thrown by a command handler appear in the console and loader log.
Export art targets
Visit the room you want to edit and run art-export. It writes BepInEx/config/DressmakerMods/art-reference.json and creates a dated folder under art-atlases with original sprite sheets, their regions and Unity object paths. Earlier atlas exports are kept. The command only sees loaded assets, so visit other rooms and export again if something is missing.
See Replace a sprite atlas for the files and a working pack example.