Useful bulk Game Master scripts for your MU Online server
Automate repetitive Game Master tasks on your MU Online server with bulk scripts: item distribution, bans, mass resets, inactive account cleanup, and scheduled announcements.
Administering an MU Online server with hundreds or thousands of active accounts makes it impractical to perform every moderation, event, or maintenance action manually, one account at a time. Bulk GM scripts solve this bottleneck, letting you distribute event prizes to a list of winners, ban account
Administering an MU Online server with hundreds or thousands of active accounts makes it impractical to perform every moderation, event, or maintenance action manually, one account at a time. Bulk GM scripts solve this bottleneck, letting you distribute event prizes to a list of winners, ban accounts by behavior pattern, mass-reset characters, or clean up inactive accounts with a single reviewed and auditable command. This tutorial gathers the most useful scripts for day-to-day administration, with the care that bulk actions demand — because an UPDATE without a correct WHERE can destroy the server economy in seconds.
Why centralize GM actions into scripts
Actions performed manually, command by command, have three problems: they're slow at scale, leave no reliable audit trail, and are inconsistent between different GMs. A versioned script fixes all three: it runs in seconds over thousands of rows, logs every action, and any authorized GM executes it the same way. The golden rule is to always run the SELECT version of the filter before the UPDATE/DELETE version, checking the number of affected rows.
Prerequisites
- Read and write access to the server's database (MSSQL or MySQL, depending on the emulator).
- A recent database backup before any bulk execution.
- An audit table (
GMActionLogor equivalent) to record who executed what and when. - A test/staging environment with a database copy to validate new scripts before running them in production.
Audit log structure
Before any bulk script, make sure a table exists to record administrative actions:
CREATE TABLE GMActionLog (
LogId INT IDENTITY(1,1) PRIMARY KEY,
GMAccount VARCHAR(50) NOT NULL,
ActionType VARCHAR(50) NOT NULL,
TargetAccount VARCHAR(50) NULL,
Details VARCHAR(500) NULL,
ExecutedAt DATETIME DEFAULT GETDATE()
);
Every bulk action described below should end with an INSERT into this table, even if the rest of the script is simple.
Script 1 — Bulk item distribution (event prize)
To deliver an item to a list of event winners without repeating the command one by one:
-- Confirmation before executing
SELECT AccountID, CharacterName FROM Character
WHERE CharacterName IN ('Player1','Player2','Player3');
-- Distribution (MuEMU/IGCN example, adjust table per emulator)
INSERT INTO WarehouseItems (AccountID, ItemIndex, ItemLevel, ItemOptions, Durability)
SELECT AccountID, 168, 15, 0, 255
FROM Character
WHERE CharacterName IN ('Player1','Player2','Player3');
INSERT INTO GMActionLog (GMAccount, ActionType, Details)
VALUES ('admin_root', 'ITEM_DISTRIBUTION', 'Summer event prize - 3 accounts');
Avoid delivering items directly into a character's inventory while online — prefer the warehouse (stash), which doesn't require the player to be logged out.
Script 2 — Preventing duplicate delivery
For recurring events, track who has already received the prize with a dedicated log table:
CREATE TABLE ItemDistributionLog (
AccountID VARCHAR(50),
EventCode VARCHAR(50),
DistributedAt DATETIME DEFAULT GETDATE(),
PRIMARY KEY (AccountID, EventCode)
);
-- Deliver only to those who haven't received it yet
INSERT INTO WarehouseItems (AccountID, ItemIndex, ItemLevel)
SELECT c.AccountID, 168, 15
FROM Character c
WHERE c.CharacterName IN ('Player1','Player2')
AND NOT EXISTS (
SELECT 1 FROM ItemDistributionLog d
WHERE d.AccountID = c.AccountID AND d.EventCode = 'SUMMER2026'
);
This pattern avoids the classic mistake of running the script twice by accident and duplicating valuable prizes.
Script 3 — Bulk ban by behavior pattern
To ban accounts sharing suspicious traits (e.g. same IP with multiple accounts botting):
-- Confirmation: how many accounts will be affected
SELECT AccountID, LastIP, LastLoginDate FROM Account
WHERE LastIP IN ('203.0.113.10','203.0.113.11') AND BanStatus = 0;
-- Ban
UPDATE Account
SET BanStatus = 1, BanReason = 'Bot usage - suspicious shared IP'
WHERE LastIP IN ('203.0.113.10','203.0.113.11') AND BanStatus = 0;
INSERT INTO GMActionLog (GMAccount, ActionType, Details)
VALUES ('admin_root', 'BAN_BATCH', 'Ban by suspicious IP - 2 IPs, see prior SELECT');
Always filter by BanStatus = 0 so you don't reprocess already-banned accounts and pollute the log.
Script 4 — Mass character reset (free reset event)
For a free reset event limited to characters above a certain level:
-- Confirmation
SELECT Name, cLevel, ResetCount FROM Character WHERE cLevel >= 400;
-- Mass reset
UPDATE Character
SET cLevel = 1, Experience = 0, LevelUpPoint = 0,
ResetCount = ResetCount + 1,
Strength = 30, Dexterity = 30, Vitality = 30, Energy = 30
WHERE cLevel >= 400;
INSERT INTO GMActionLog (GMAccount, ActionType, Details)
VALUES ('admin_root', 'MASS_RESET', 'Free reset event - cLevel >= 400');
Base attribute values (Strength, Dexterity, etc.) vary by class — adjust the query with a CASE per class if your server doesn't use uniform base attributes.
Script 5 — Cleaning up inactive accounts
To identify and optionally archive accounts that haven't logged in for a long time, freeing up character names and database space:
-- Identify accounts inactive for more than 365 days
SELECT AccountID, LastLoginDate FROM Account
WHERE LastLoginDate < DATEADD(DAY, -365, GETDATE());
-- Flag (don't delete directly) for archiving
UPDATE Account
SET AccountStatus = 'ARCHIVED_CANDIDATE'
WHERE LastLoginDate < DATEADD(DAY, -365, GETDATE()) AND AccountStatus = 'ACTIVE';
Never DELETE old accounts directly in a single step — flag them for archiving, wait for an announced grace period (30-60 days), and only then delete or move them to a history table.
Script 6 — Scheduled server announcements
To notify players about maintenance or events without relying on a GM typing at the exact right moment, use an announcements table read by a scheduled service:
CREATE TABLE ScheduledAnnouncements (
Id INT IDENTITY(1,1) PRIMARY KEY,
Message VARCHAR(500),
ScheduledTime DATETIME,
Sent BIT DEFAULT 0
);
INSERT INTO ScheduledAnnouncements (Message, ScheduledTime)
VALUES ('Maintenance in 30 minutes - save your progress', DATEADD(MINUTE, 30, GETDATE()));
An external service (scheduled job) checks this table every minute and sends the message via the GameServer's command pipe when ScheduledTime arrives, marking Sent = 1 so it doesn't repeat.
Best practices when running bulk scripts
| Practice | Why |
|---|---|
| Always run SELECT before UPDATE/DELETE | Confirms the number of affected rows before committing data changes |
| Run in staging/database copy first | Avoids discovering a logic error directly in production |
| Log every action in the audit table | Enables investigating complaints and reverting decisions with context |
Use transactions (BEGIN TRAN/COMMIT) | Allows an immediate ROLLBACK if the result doesn't match what was expected |
| Never grant execution access to all GMs | Reduces risk of error or abuse; centralize sensitive scripts among few admins |
Using transactions to reduce risk
Wrapping the script in a transaction lets you check the result before confirming:
BEGIN TRAN;
UPDATE Account SET BanStatus = 1 WHERE LastIP = '203.0.113.10';
-- Check the result
SELECT * FROM Account WHERE LastIP = '203.0.113.10';
-- If correct:
COMMIT;
-- If something is wrong:
-- ROLLBACK;
This pattern is especially valuable for irreversible actions like banning and mass resets, where a filter mistake is costly.
Organizing scripts in a repository
Just like server code, GM scripts should be versioned. A simple structure:
gm-scripts/
├── events/
│ ├── free-reset-2026-07.sql
│ └── summer-prize.sql
├── moderation/
│ ├── ban-by-ip.sql
│ └── inactive-cleanup.sql
└── README.md
Each file should contain, in a comment, the date it was used, the responsible GM, and the expected result (how many rows affected). This turns loose scripts into an auditable history of server administration.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Item delivered twice | Script run twice without a log check | Add a distribution tracking table (Script 2) |
| Wrong accounts banned | WHERE filter broader than intended | Always run the confirmation SELECT before the UPDATE |
| Reset affected characters it shouldn't have | Poorly filtered level or class range | Adjust the WHERE and validate with SELECT in staging first |
| Data loss after mass DELETE | No backup taken immediately beforehand | Always back up and prefer flagging for archiving over deleting directly |
| Scheduled announcement didn't fire | Scheduled service not running or table not read | Check the responsible job/cron and the GameServer command pipe |
Checklist before running a bulk GM script
- Database backup taken immediately before execution.
- SELECT version of the filter run and confirmed.
- Script tested in staging or a database copy.
- Transaction (BEGIN TRAN) used for irreversible actions.
- Action logged in the audit table (GMActionLog).
- Script versioned in the GM scripts repository.
- Execution access restricted to authorized administrators.
With these scripts organized and auditable, the natural next step is integrating them into the overall server maintenance pipeline — see the MU Online server creation tutorial to understand how this administrative workflow fits into the full operation.
Frequently asked questions
Is it safe to run SQL scripts directly in production for GM actions?
Only with a backup taken immediately beforehand and a prior test in staging or on a copy of the database. Bulk actions (UPDATE/DELETE without a precise WHERE) are the most common cause of serious accidents on MU servers — always run a SELECT with the same filters first to check how many rows will be affected.
Do these scripts replace in-game GM commands?
Not entirely. In-game commands (like /additem or /ban) are better for one-off actions and for GMs without technical database access. Bulk scripts make sense when an action affects dozens or hundreds of accounts at once, which would be impractical command by command.
How do I prevent an item distribution script from duplicating deliveries?
Log every delivery in a tracking table (e.g. ItemDistributionLog) with the account ID and event ID, and have the script check that table before inserting the item again. This also serves as an audit trail if a player claims they didn't receive the prize.
Can I schedule these scripts to run on their own?
Yes, using SQL Server Agent (MSSQL) or a cron job that calls a script running the query. This is common for tasks like inactive account cleanup or daily event resets, but sensitive actions (banning, item removal) should keep manual approval before execution.
What's the risk of running a mass reset on all characters?
If the query doesn't correctly filter by minimum level or class, you might reset characters that shouldn't be, causing lost builds and mass complaints. Always run the confirmation SELECT beforehand and notify the community in advance about the maintenance window.