Add webtools static frontend and serve it via docker-compose
Adds an nginx:alpine service serving webtools/ (item/map lookup frontend) on port 80 alongside the existing login/char/world stack. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
42cf2944ba
commit
36857a2eab
5 files changed
+651
No files matched your search
@@ -81,6 +81,14 @@ services:
|
||||
charserver:
|
||||
condition: service_started
|
||||
|
||||
webtools:
|
||||
image: nginx:alpine
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- "./webtools:/usr/share/nginx/html:ro"
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
logs:
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
const COMMANDS = [
|
||||
{cat:"Movement & Teleport", items:[
|
||||
{cmd:"/here", args:[], level:"any", desc:"Teleports you to your own current position — mostly used to force-refresh your client after a stuck screen.", ex:"/here"},
|
||||
{cmd:"/tele", args:["map","x","y"], level:"any", desc:"Teleport yourself to exact map/x/y coordinates.", ex:"/tele 20 5300 5100"},
|
||||
{cmd:"/gotomap", args:["map"], level:"any", desc:"Teleport to a map's default entry point.", ex:"/gotomap 22"},
|
||||
{cmd:"/go", args:["locationId"], level:"any", desc:"Jump to one of the server's built-in named locations (capital, event map, etc. — numeric index).", ex:"/go 1"},
|
||||
{cmd:"/goto", args:["charname"], level:"any", desc:"Teleport yourself to another online player.", ex:"/goto thomas"},
|
||||
{cmd:"/teletome", args:["charname|all"], level:"any", desc:"Pull one player — or every connected player — to your position.", ex:"/teletome all"},
|
||||
{cmd:"/move", args:["charname","map","x","y"], level:"any", desc:"Teleport someone else to a specific map/x/y (you stay put).", ex:"/move thomas 20 5300 5100"},
|
||||
{cmd:"/moveto", args:["…"], level:"any", desc:"Variant move helper; check live behaviour before relying on it — args are context-dependent.", ex:"/moveto 20 5300 5100"},
|
||||
]},
|
||||
{cat:"Character & Stats", items:[
|
||||
{cmd:"/level", args:["level","[charname]"], level:"any", desc:"Set a character's level directly. Defaults to yourself if no name given.", ex:"/level 50 thomas"},
|
||||
{cmd:"/levelup", args:[], level:"any", desc:"Grant exactly enough EXP to level up once. Alias: /l", ex:"/levelup"},
|
||||
{cmd:"/exp", args:["amount","[charname]"], level:"any", desc:"Add raw EXP to a character.", ex:"/exp 100000 thomas"},
|
||||
{cmd:"/stat", args:["statname","value"], level:"any", desc:"Directly set a named base stat (str/con/dex/int/cha/sen etc).", ex:"/stat str 40"},
|
||||
{cmd:"/statp", args:["amount","[charname]"], level:"any", desc:"Grant (or remove, negative) unspent stat points.", ex:"/statp 50"},
|
||||
{cmd:"/skillp", args:["amount","[charname]"], level:"any", desc:"Grant (or remove) unspent skill points.", ex:"/skillp 20"},
|
||||
{cmd:"/cha", args:["…"], level:"any", desc:"Adjusts character-level fields — inspect gmcmds.cpp:1027 before relying on exact args.", ex:"/cha"},
|
||||
{cmd:"/class", args:["classid"], level:"any", desc:"Change job/class.", ex:"/class 122"},
|
||||
{cmd:"/job", args:["soldier|muse|hawker|dealer"], level:"any", desc:"First job change at level 10+ (one-time, mirrors the in-game job-change quest).", ex:"/job soldier"},
|
||||
{cmd:"/mystat", args:["field"], level:"any", desc:"Print one of your own live stat fields to yourself (ap, acc, hp, mp, maxhp, maxmp, dodge, def, mdef, crit, mspd, aspd, xprate, stamina, weight, zulydrop, itemdrop, serverdrop, clan, map, job).", ex:"/mystat maxhp"},
|
||||
{cmd:"/heal", args:["[hp]","[mp]"], level:"any", desc:"Heal yourself. No args = full HP/MP.", ex:"/heal"},
|
||||
{cmd:"/learnskill", args:["skillId"], level:"gm 300+", desc:"Instantly learn a skill by its STB id.", ex:"/learnskill 181"},
|
||||
{cmd:"/killinrange", args:["range"], level:"any", desc:"Instantly kill every monster within range of you.", ex:"/killinrange 20"},
|
||||
{cmd:"/convert", args:["newMontype"], level:"dev 900+", desc:"Turn your current battle target into a different monster type in place.", ex:"/convert 210"},
|
||||
{cmd:"/hide", args:[], level:"any", desc:"Toggle GM invisible/hidden mode (see who2 for who can still see you).", ex:"/hide"},
|
||||
]},
|
||||
{cat:"Items & Economy", items:[
|
||||
{cmd:"/item", args:["itemid","itemtype","amount","[refine]","[lifespan]","[socketed]","[stats]"], level:"any", desc:"Give an item to yourself. Only the first 3 args are required. (See Item Configurator tab.)", ex:"/item 12 3 1 9 100 1 50"},
|
||||
{cmd:"/give2", args:["charname","itemid","itemtype","amount","[refine]","[lifespan]","[socketed]","[stats]"], level:"any", desc:"Give an item to another online player by name.", ex:"/give2 thomas 12 3 1"},
|
||||
{cmd:"/itemstat", args:["…"], level:"any", desc:"Adjust stats on an item — check gmcmds.cpp:1316 for the exact field order before use.", ex:"/itemstat"},
|
||||
{cmd:"/refine", args:["slot","level"], level:"any", desc:"Refine an equipped slot. Slot keyword (e.g. mask) or defaults to weapon.", ex:"/refine mask 6"},
|
||||
{cmd:"/drop", args:["itemtype","itemid","[count]"], level:"any", desc:"Drop an item on the ground at your feet (not into your inventory).", ex:"/drop 3 12 1"},
|
||||
{cmd:"/givezuly", args:["charname","amount"], level:"any", desc:"Give Zuly (in-game currency) to a player.", ex:"/givezuly thomas 100000"},
|
||||
{cmd:"/unionpoints", args:["charname","amount"], level:"any", desc:"Grant union points to a player.", ex:"/unionpoints thomas 500"},
|
||||
{cmd:"/set", args:["itemid","[refine]"], level:"any", desc:"Quick-equip a costume/hat item onto yourself by id.", ex:"/set 205 5"},
|
||||
{cmd:"/sendhp", args:["…"], level:"any", desc:"Force-resync your HP display to the client.", ex:"/sendhp"},
|
||||
]},
|
||||
{cat:"Monsters & Spawns", items:[
|
||||
{cmd:"/mon", args:["montype","[count]","[AI]"], level:"any", desc:"Spawn one or more monsters of a type near you. AI 0 = the monster's normal default AI.", ex:"/mon 1 5"},
|
||||
{cmd:"/tdmon", args:["montype","[count]"], level:"any", desc:"Spawn Tower-Defense-mode monsters (fixed AI 331) — used for TD map testing.", ex:"/tdmon 210 3"},
|
||||
{cmd:"/spawn", args:["montype","max","radius","respawnSecs","spawnType","[triggerMontype]","[triggerAmount]","[limit]","[spawnKey]","[aggro]","[refVar]","[refVal]"], level:"dev 900+", desc:"Permanently register a new spawn point at your current position (writes to list_special_spawns).", ex:"/spawn 1 5 10 30 1"},
|
||||
{cmd:"/npc", args:["npcid","[dialogId]"], level:"any", desc:"Place an NPC at your position.", ex:"/npc 1001"},
|
||||
]},
|
||||
{cat:"Quests", items:[
|
||||
{cmd:"/addquest", args:["questid"], level:"any", desc:"Add a quest to your active quest log.", ex:"/addquest 101"},
|
||||
{cmd:"/iquest", args:["…"], level:"any", desc:"Inspect/modify one of your in-progress quest slots.", ex:"/iquest 0"},
|
||||
{cmd:"/dquest", args:[], level:"any", desc:"Toggle quest-debug mode (SendPM narration of every quest condition/action check).", ex:"/dquest"},
|
||||
{cmd:"/listquest", args:[], level:"any", desc:"List all quests currently in your log.", ex:"/listquest"},
|
||||
{cmd:"/listqflag", args:[], level:"any", desc:"List your quest flag bits.", ex:"/listqflag"},
|
||||
{cmd:"/listqswitch", args:[], level:"any", desc:"List quest switches for your active quest slots.", ex:"/listqswitch"},
|
||||
{cmd:"/listqvar", args:[], level:"any", desc:"List quest variables for your active quest slots.", ex:"/listqvar"},
|
||||
{cmd:"/setqvar", args:["slot","varIndex","value"], level:"any", desc:"Directly set a quest variable.", ex:"/setqvar 0 2 1"},
|
||||
{cmd:"/qtrigger", args:["…"], level:"any", desc:"Manually fire a quest trigger event.", ex:"/qtrigger 0 3"},
|
||||
]},
|
||||
{cat:"World & Config", items:[
|
||||
{cmd:"/rate", args:["type","value"], level:"dev 900+", desc:"Live-adjust a rate config value (xp, drop, zuly, etc.) without restarting.", ex:"/rate xp 3"},
|
||||
{cmd:"/setxprate", args:["value"], level:"any", desc:"Set the global EXP rate multiplier.", ex:"/setxprate 3"},
|
||||
{cmd:"/setdroprate", args:["value"], level:"any", desc:"Set the global item drop rate multiplier.", ex:"/setdroprate 2"},
|
||||
{cmd:"/setzulyrate", args:["value"], level:"any", desc:"Set the global Zuly drop rate multiplier.", ex:"/setzulyrate 2"},
|
||||
{cmd:"/reload", args:["config|mobs|drops|cmd|spawns|tdef"], level:"any", desc:"Hot-reload a data set from the database without a server restart.", ex:"/reload spawns"},
|
||||
{cmd:"/configreset", args:["maxlevel|xprate|cmd"], level:"dev 900+", desc:"Reload/adjust core server config values and persist them to list_config.", ex:"/configreset xprate 3"},
|
||||
{cmd:"/settime", args:["…"], level:"any", desc:"Set the in-game day/night clock.", ex:"/settime 12"},
|
||||
{cmd:"/cfmode", args:["…"], level:"any", desc:"Toggle Clan Field open/closed mode.", ex:"/cfmode"},
|
||||
{cmd:"/pvp", args:[], level:"dev 900+", desc:"Toggle PvP on/off for the map you're currently on.", ex:"/pvp"},
|
||||
{cmd:"/setobjvar", args:["varId(<2000)","index(<20)","value"], level:"dev 300+", desc:"Set a global object variable slot (used by AIP scripts/world state).", ex:"/setobjvar 5 0 1"},
|
||||
{cmd:"/readobjvar", args:["varId","index"], level:"dev 300+", desc:"Read back a global object variable slot to yourself.", ex:"/readobjvar 5 0"},
|
||||
{cmd:"/shutdown", args:["minutes"], level:"any", desc:"Schedule a graceful server shutdown with a countdown.", ex:"/shutdown 10"},
|
||||
{cmd:"/setspeedmod", args:["value"], level:"any", desc:"Adjust your own movement speed modifier.", ex:"/setspeedmod 150"},
|
||||
{cmd:"/rules", args:[], level:"any", desc:"Print the configured server rules text to yourself.", ex:"/rules"},
|
||||
]},
|
||||
{cat:"Waypoints (TD maps)", items:[
|
||||
{cmd:"/setwp", args:["…"], level:"any", desc:"Add a waypoint at your position for tower-defense mob pathing.", ex:"/setwp"},
|
||||
{cmd:"/delwp", args:["…"], level:"any", desc:"Delete a waypoint.", ex:"/delwp 3"},
|
||||
{cmd:"/savewp", args:[], level:"any", desc:"Persist the current waypoint list to disk/DB.", ex:"/savewp"},
|
||||
{cmd:"/checkwp", args:[], level:"any", desc:"Print the current waypoint list to yourself.", ex:"/checkwp"},
|
||||
]},
|
||||
{cat:"Appearance & Effects", items:[
|
||||
{cmd:"/face", args:["faceId(0-15)","[style(0-3)]"], level:"any", desc:"Change your character's face.", ex:"/face 3 1"},
|
||||
{cmd:"/hair", args:["hairId(0-6)"], level:"any", desc:"Change your character's hairstyle.", ex:"/hair 2"},
|
||||
{cmd:"/bodysize", args:["size"], level:"any", desc:"Scale your character's body.", ex:"/bodysize 120"},
|
||||
{cmd:"/headsize", args:["size"], level:"any", desc:"Scale your character's head.", ex:"/headsize 120"},
|
||||
{cmd:"/ani", args:["…"], level:"any", desc:"Play an animation on yourself.", ex:"/ani 5"},
|
||||
{cmd:"/usebuff", args:["effectId"], level:"any", desc:"Play a skill-effect visual on yourself (cosmetic only).", ex:"/usebuff 12"},
|
||||
{cmd:"/summon", args:["…"], level:"any", desc:"Summon a pet/companion entity.", ex:"/summon 1"},
|
||||
{cmd:"/skill", args:["…"], level:"any", desc:"Cast/force a skill effect.", ex:"/skill 100"},
|
||||
]},
|
||||
{cat:"Fairy", items:[
|
||||
{cmd:"/givefairy", args:[], level:"any", desc:"Grant yourself a fairy companion.", ex:"/givefairy"},
|
||||
{cmd:"/fairymode", args:["mode"], level:"any", desc:"Set fairy behaviour mode.", ex:"/fairymode 1"},
|
||||
{cmd:"/fairywait", args:["seconds"], level:"any", desc:"Set fairy wait-time config.", ex:"/fairywait 30"},
|
||||
{cmd:"/fairystay", args:["seconds"], level:"any", desc:"Set fairy stay-time config.", ex:"/fairystay 45"},
|
||||
{cmd:"/fairytestmode", args:["0|1"], level:"any", desc:"Toggle fairy test mode.", ex:"/fairytestmode 1"},
|
||||
]},
|
||||
{cat:"Communication & Moderation", items:[
|
||||
{cmd:"/b", args:["message"], level:"any", desc:"Broadcast a global chat message (rate-limited unless accesslevel > 100).", ex:"/b Server restarting in 10 minutes"},
|
||||
{cmd:"/ann", args:["message"], level:"any", desc:"Send a server-wide announcement banner.", ex:"/ann Double EXP weekend is live!"},
|
||||
{cmd:"/mute", args:["charname","minutes"], level:"any", desc:"Mute a player's chat for N minutes.", ex:"/mute griefer123 30"},
|
||||
{cmd:"/kick", args:["charname"], level:"any", desc:"Disconnect a player. Fails if their accesslevel ≥ yours.", ex:"/kick griefer123"},
|
||||
{cmd:"/ban", args:["charname"], level:"any", desc:"Ban a player's account.", ex:"/ban griefer123"},
|
||||
{cmd:"/dialog", args:["…"], level:"dev 300+", desc:"Force-open an NPC dialog window on a target.", ex:"/dialog"},
|
||||
]},
|
||||
{cat:"Info & Diagnostics", items:[
|
||||
{cmd:"/info", args:[], level:"any", desc:"Print your own full character info to yourself.", ex:"/info"},
|
||||
{cmd:"/CharInfo", args:["charname"], level:"any", desc:"Print another player's character info to yourself.", ex:"/CharInfo thomas"},
|
||||
{cmd:"/targetinfo", args:[], level:"any", desc:"Print info about your current battle target.", ex:"/targetinfo"},
|
||||
{cmd:"/serverinfo", args:[], level:"any", desc:"Print server stats (uptime, counts) to yourself.", ex:"/serverinfo"},
|
||||
{cmd:"/who", args:[], level:"any", desc:"Print a count of connected players vs GMs.", ex:"/who"},
|
||||
{cmd:"/who2", args:[], level:"any", desc:"List every connected player by name with position/level/job.", ex:"/who2"},
|
||||
{cmd:"/mybuffs", args:[], level:"any", desc:"List currently active buffs on yourself.", ex:"/mybuffs"},
|
||||
{cmd:"/debugmode", args:[], level:"any", desc:"Toggle verbose PM narration of internal code paths — useful when diagnosing a specific command.", ex:"/debugmode"},
|
||||
{cmd:"/aiwatch", args:["aiNumber"], level:"any", desc:"Turn on verbose server-log tracing for one specific monster AI script number.", ex:"/aiwatch 331"},
|
||||
{cmd:"/p", args:[], level:"any", desc:"Send yourself the contents of the server's packet.txt test file (developer packet-injection tool).", ex:"/p"},
|
||||
{cmd:"/save", args:[], level:"any", desc:"Force-save your character data immediately.", ex:"/save"},
|
||||
{cmd:"/savequest", args:[], level:"any", desc:"Force-save your quest progress immediately.", ex:"/savequest"},
|
||||
{cmd:"/partylvl", args:[], level:"any", desc:"Print average party level info.", ex:"/partylvl"},
|
||||
{cmd:"/pdmg", args:["…"], level:"any", desc:"Debug print of player damage calculation inputs.", ex:"/pdmg"},
|
||||
{cmd:"/mdmg", args:["…"], level:"any", desc:"Debug print of monster damage calculation inputs.", ex:"/mdmg"},
|
||||
{cmd:"/shoptype", args:["…"], level:"any", desc:"Inspect/set an NPC shop's type.", ex:"/shoptype"},
|
||||
{cmd:"/transx", args:["…"], level:"any", desc:"Debug transform/position helper.", ex:"/transx"},
|
||||
]},
|
||||
];
|
||||
@@ -0,0 +1,487 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>KTRose GM Toolbox</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#12141a; --bg-raised:#181b23; --bg-card:#1d2029; --line:#2a2e3a;
|
||||
--ink:#e9e6e2; --ink-dim:#9a99a8; --ink-faint:#65647a;
|
||||
--thorn:#c2557a; --thorn-dim:#8a4560; --thorn-ink:#e28aa8;
|
||||
--leaf:#7fb88a; --amber:#d1a054; --danger:#d16060;
|
||||
--mono: ui-monospace,"Cascadia Code","SF Mono","JetBrains Mono",Menlo,Consolas,monospace;
|
||||
--serif: Georgia,"Iowan Old Style","Palatino Linotype","Book Antiqua",serif;
|
||||
--sans: -apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
|
||||
--radius:10px;
|
||||
}
|
||||
@media (prefers-color-scheme: light){
|
||||
:root:not([data-theme="dark"]){
|
||||
--bg:#f7f4ef; --bg-raised:#ffffff; --bg-card:#ffffff; --line:#e2ddd3;
|
||||
--ink:#231d22; --ink-dim:#645d68; --ink-faint:#8e8790;
|
||||
--thorn:#a83060; --thorn-dim:#c98aa2; --thorn-ink:#8a1f45;
|
||||
--leaf:#3e7a4d; --amber:#8a611f; --danger:#a83a3a;
|
||||
}
|
||||
}
|
||||
:root[data-theme="light"]{
|
||||
--bg:#f7f4ef; --bg-raised:#ffffff; --bg-card:#ffffff; --line:#e2ddd3;
|
||||
--ink:#231d22; --ink-dim:#645d68; --ink-faint:#8e8790;
|
||||
--thorn:#a83060; --thorn-dim:#c98aa2; --thorn-ink:#8a1f45;
|
||||
--leaf:#3e7a4d; --amber:#8a611f; --danger:#a83a3a;
|
||||
}
|
||||
*{box-sizing:border-box;}
|
||||
html,body{margin:0;padding:0;}
|
||||
body{
|
||||
background:radial-gradient(1200px 600px at 15% -10%, color-mix(in srgb, var(--thorn) 10%, transparent), transparent) var(--bg);
|
||||
color:var(--ink); font-family:var(--sans); -webkit-font-smoothing:antialiased;
|
||||
}
|
||||
a{color:inherit;}
|
||||
::selection{background:color-mix(in srgb, var(--thorn) 35%, transparent);}
|
||||
|
||||
.shell{display:grid; grid-template-columns:260px 1fr; min-height:100vh;}
|
||||
@media (max-width:860px){.shell{grid-template-columns:1fr;}}
|
||||
|
||||
.side{
|
||||
border-right:1px solid var(--line); background:var(--bg-raised);
|
||||
padding:26px 20px; position:sticky; top:0; height:100vh; overflow-y:auto;
|
||||
display:flex; flex-direction:column; gap:20px;
|
||||
}
|
||||
@media (max-width:860px){.side{position:static; height:auto; border-right:none; border-bottom:1px solid var(--line);}}
|
||||
|
||||
.brand .mark{
|
||||
font-family:var(--serif); font-size:1.4rem; font-style:italic;
|
||||
background:linear-gradient(120deg, var(--thorn), var(--ink) 140%);
|
||||
-webkit-background-clip:text; background-clip:text; color:transparent;
|
||||
}
|
||||
.brand .sub{font-size:0.7rem; letter-spacing:0.1em; text-transform:uppercase; color:var(--ink-faint); margin-top:2px;}
|
||||
|
||||
.tabs{display:flex; flex-direction:column; gap:3px;}
|
||||
.tab{
|
||||
display:flex; align-items:center; gap:9px; padding:9px 11px; border-radius:8px;
|
||||
font-size:0.9rem; color:var(--ink-dim); cursor:pointer; border:1px solid transparent;
|
||||
}
|
||||
.tab:hover{background:var(--bg-card); color:var(--ink);}
|
||||
.tab.active{background:var(--bg-card); border-color:var(--line); color:var(--ink); font-weight:600;}
|
||||
.tab .ico{font-size:1rem; width:1.2em; text-align:center;}
|
||||
|
||||
.side-panel{display:none; flex-direction:column; gap:10px;}
|
||||
.side-panel.active{display:flex;}
|
||||
.side-panel input, .side-panel select{
|
||||
width:100%; background:var(--bg-card); border:1px solid var(--line); color:var(--ink);
|
||||
padding:9px 11px; border-radius:8px; font-size:0.87rem; font-family:var(--sans);
|
||||
}
|
||||
.side-panel input:focus, .side-panel select:focus{outline:2px solid var(--thorn); outline-offset:1px;}
|
||||
.side-panel .label{font-size:0.68rem; letter-spacing:0.1em; text-transform:uppercase; color:var(--ink-faint); margin:6px 0 -2px;}
|
||||
|
||||
.navgroup{display:flex; flex-direction:column; gap:2px; max-height:340px; overflow-y:auto;}
|
||||
.navlink{
|
||||
display:flex; align-items:center; justify-content:space-between; padding:6px 10px;
|
||||
border-radius:7px; font-size:0.83rem; color:var(--ink-dim); cursor:pointer; border:1px solid transparent;
|
||||
}
|
||||
.navlink:hover{background:var(--bg-card); color:var(--ink);}
|
||||
.navlink.on{background:var(--bg-card); border-color:var(--line); color:var(--ink); font-weight:600;}
|
||||
.navlink .count{font-variant-numeric:tabular-nums; font-size:0.7rem; color:var(--ink-faint);}
|
||||
|
||||
.legend{margin-top:auto; padding-top:14px; border-top:1px solid var(--line); font-size:0.7rem; color:var(--ink-faint); display:flex; flex-direction:column; gap:6px;}
|
||||
.legend .row{display:flex; align-items:center; gap:8px;}
|
||||
.pill{font-size:0.66rem; letter-spacing:0.06em; text-transform:uppercase; padding:2px 8px; border-radius:999px; font-weight:700; white-space:nowrap;}
|
||||
.pill-gm{background:color-mix(in srgb, var(--leaf) 22%, var(--bg-card)); color:var(--leaf);}
|
||||
.pill-dev{background:color-mix(in srgb, var(--amber) 25%, var(--bg-card)); color:var(--amber);}
|
||||
.pill-any{background:color-mix(in srgb, var(--ink-faint) 20%, var(--bg-card)); color:var(--ink-dim);}
|
||||
|
||||
.main{padding:34px 42px 80px; max-width:1040px;}
|
||||
@media (max-width:640px){.main{padding:22px 16px 60px;}}
|
||||
.view{display:none;}
|
||||
.view.active{display:block;}
|
||||
|
||||
.pagehead{margin-bottom:26px;}
|
||||
.pagehead h1{font-family:var(--serif); font-style:italic; font-weight:400; font-size:1.9rem; margin:0 0 8px; text-wrap:balance;}
|
||||
.pagehead p{margin:0; color:var(--ink-dim); font-size:0.93rem; max-width:66ch; line-height:1.55;}
|
||||
.pagehead code{font-family:var(--mono); background:var(--bg-card); border:1px solid var(--line); padding:1px 6px; border-radius:5px; font-size:0.85em;}
|
||||
|
||||
.resultmeta{font-size:0.78rem; color:var(--ink-faint); margin:20px 0 10px;}
|
||||
|
||||
.category h2{
|
||||
font-size:0.74rem; letter-spacing:0.11em; text-transform:uppercase; color:var(--thorn-dim);
|
||||
margin:28px 0 12px; padding-bottom:8px; border-bottom:1px solid var(--line);
|
||||
}
|
||||
.cmdlist{display:flex; flex-direction:column; gap:9px;}
|
||||
.cmd{background:var(--bg-card); border:1px solid var(--line); border-radius:var(--radius); padding:13px 15px; display:flex; flex-direction:column; gap:7px;}
|
||||
.cmd-row{display:flex; align-items:baseline; gap:10px; flex-wrap:wrap;}
|
||||
.cmd-syntax{font-family:var(--mono); font-size:0.9rem; font-weight:600;}
|
||||
.cmd-syntax .arg{color:var(--ink-dim); font-weight:400;}
|
||||
.copybtn{margin-left:auto; font-size:0.7rem; color:var(--ink-faint); border:1px solid var(--line); background:transparent; border-radius:6px; padding:3px 8px; cursor:pointer; font-family:var(--sans);}
|
||||
.copybtn:hover{color:var(--ink); border-color:var(--ink-faint);}
|
||||
.copybtn:focus-visible{outline:2px solid var(--thorn); outline-offset:1px;}
|
||||
.cmd-desc{font-size:0.86rem; color:var(--ink-dim); line-height:1.5; max-width:70ch;}
|
||||
.cmd-example{font-family:var(--mono); font-size:0.79rem; color:var(--thorn-ink); background:color-mix(in srgb, var(--thorn) 10%, transparent); border-left:2px solid var(--thorn); padding:6px 10px; border-radius:0 6px 6px 0; width:fit-content;}
|
||||
|
||||
/* Items */
|
||||
.item-toolbar{display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-bottom:16px;}
|
||||
.item-toolbar select, .item-toolbar input{
|
||||
background:var(--bg-card); border:1px solid var(--line); color:var(--ink); padding:8px 10px; border-radius:8px; font-size:0.85rem;
|
||||
}
|
||||
.item-grid{display:grid; grid-template-columns:repeat(auto-fill,minmax(230px,1fr)); gap:8px;}
|
||||
.item-card{
|
||||
background:var(--bg-card); border:1px solid var(--line); border-radius:9px; padding:10px 12px;
|
||||
cursor:pointer; display:flex; flex-direction:column; gap:3px;
|
||||
}
|
||||
.item-card:hover{border-color:var(--thorn-dim);}
|
||||
.item-card .nm{font-size:0.87rem; font-weight:600;}
|
||||
.item-card .id{font-family:var(--mono); font-size:0.72rem; color:var(--ink-faint);}
|
||||
.item-card.sel{border-color:var(--thorn); background:color-mix(in srgb, var(--thorn) 8%, var(--bg-card));}
|
||||
|
||||
.configurator{
|
||||
position:sticky; bottom:0; margin-top:20px; background:var(--bg-raised); border:1px solid var(--line);
|
||||
border-radius:var(--radius); padding:16px 18px; box-shadow:0 -8px 24px -12px rgba(0,0,0,0.4);
|
||||
}
|
||||
.configurator h3{margin:0 0 10px; font-size:0.95rem; font-family:var(--serif); font-style:italic; font-weight:400;}
|
||||
.cfg-grid{display:grid; grid-template-columns:repeat(6,1fr); gap:10px;}
|
||||
@media (max-width:760px){.cfg-grid{grid-template-columns:repeat(2,1fr);}}
|
||||
.cfg-field label{display:block; font-size:0.66rem; letter-spacing:0.08em; text-transform:uppercase; color:var(--ink-faint); margin-bottom:4px;}
|
||||
.cfg-field input, .cfg-field select{width:100%; background:var(--bg-card); border:1px solid var(--line); color:var(--ink); padding:7px 9px; border-radius:7px; font-size:0.85rem;}
|
||||
.cfg-out{display:flex; align-items:center; gap:10px; margin-top:14px; flex-wrap:wrap;}
|
||||
.cfg-out code{font-family:var(--mono); background:var(--bg-card); border:1px solid var(--line); padding:7px 12px; border-radius:7px; font-size:0.86rem; color:var(--thorn-ink); flex:1; min-width:200px;}
|
||||
.cfg-out button{background:var(--thorn); color:#fff; border:none; padding:8px 16px; border-radius:7px; font-size:0.85rem; cursor:pointer; font-weight:600;}
|
||||
.cfg-out button:hover{filter:brightness(1.08);}
|
||||
.cfg-empty{color:var(--ink-faint); font-size:0.85rem; padding:8px 0;}
|
||||
|
||||
/* Maps */
|
||||
.map-grid{display:grid; grid-template-columns:repeat(auto-fill,minmax(260px,1fr)); gap:12px;}
|
||||
.map-card{background:var(--bg-card); border:1px solid var(--line); border-radius:var(--radius); padding:16px;}
|
||||
.map-card h3{margin:0 0 4px; font-size:1.1rem; font-family:var(--mono);}
|
||||
.map-card .note{font-size:0.78rem; color:var(--thorn-dim); margin:4px 0 8px;}
|
||||
.map-stats{display:grid; grid-template-columns:1fr 1fr; gap:6px 14px; font-size:0.82rem; color:var(--ink-dim);}
|
||||
.map-stats b{color:var(--ink); font-variant-numeric:tabular-nums;}
|
||||
.map-empty-note{
|
||||
background:color-mix(in srgb, var(--amber) 12%, var(--bg-card)); border:1px solid color-mix(in srgb, var(--amber) 35%, var(--line));
|
||||
border-radius:var(--radius); padding:14px 16px; font-size:0.85rem; color:var(--ink-dim); margin-bottom:20px; line-height:1.5;
|
||||
}
|
||||
|
||||
.empty{padding:50px 20px; text-align:center; color:var(--ink-faint); font-size:0.9rem;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="shell">
|
||||
<aside class="side">
|
||||
<div class="brand">
|
||||
<div class="mark">KTRose Toolbox</div>
|
||||
<div class="sub">self-hosted · no auth</div>
|
||||
</div>
|
||||
|
||||
<nav class="tabs">
|
||||
<div class="tab active" data-view="commands"><span class="ico">⌘</span> GM Commands</div>
|
||||
<div class="tab" data-view="items"><span class="ico">◆</span> Item Configurator</div>
|
||||
<div class="tab" data-view="maps"><span class="ico">⌗</span> Maps</div>
|
||||
</nav>
|
||||
|
||||
<div class="side-panel active" id="panel-commands">
|
||||
<div class="label">Search</div>
|
||||
<input id="q-commands" type="text" placeholder="Search commands…" autocomplete="off" />
|
||||
<div class="navgroup" id="nav-commands"></div>
|
||||
</div>
|
||||
|
||||
<div class="side-panel" id="panel-items">
|
||||
<div class="label">Category</div>
|
||||
<select id="item-cat"></select>
|
||||
<div class="label">Search by name or id</div>
|
||||
<input id="q-items" type="text" placeholder="e.g. wooden sword, or 42" autocomplete="off" />
|
||||
</div>
|
||||
|
||||
<div class="side-panel" id="panel-maps">
|
||||
<div class="label">Sort</div>
|
||||
<select id="map-sort">
|
||||
<option value="id">Map ID</option>
|
||||
<option value="monsters">Monster count</option>
|
||||
<option value="npcs">NPC count</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="legend" id="legend-commands">
|
||||
<div class="row"><span class="pill pill-any">any</span> usable by any GM character</div>
|
||||
<div class="row"><span class="pill pill-gm">gm 300+</span> accesslevel ≥ 300</div>
|
||||
<div class="row"><span class="pill pill-dev">dev 900+</span> accesslevel ≥ 900 + IsDev</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="main">
|
||||
|
||||
<!-- ===================== COMMANDS ===================== -->
|
||||
<section class="view active" id="view-commands">
|
||||
<div class="pagehead">
|
||||
<h1>Every <code>/command</code> the world server understands</h1>
|
||||
<p>Typed straight into normal chat, leading slash included — e.g. <code>/level 50 thomas</code>.
|
||||
Pulled directly from <code>gmcmds.cpp</code>. Thomas is flagged accesslevel 900 + IsGM + IsDev,
|
||||
so everything here is available to that character.</p>
|
||||
</div>
|
||||
<div class="resultmeta" id="resultmeta-commands"></div>
|
||||
<div id="results-commands"></div>
|
||||
</section>
|
||||
|
||||
<!-- ===================== ITEMS ===================== -->
|
||||
<section class="view" id="view-items">
|
||||
<div class="pagehead">
|
||||
<h1>Pick an item, get the exact <code>/item</code> command</h1>
|
||||
<p>9,951 items straight out of <code>itemlist.xls</code> (the same catalog the client uses), grouped into
|
||||
the 14 categories the <code>/item</code> and <code>/give2</code> commands expect. Click one to configure
|
||||
refine/count/etc. and copy the ready-to-paste command.</p>
|
||||
</div>
|
||||
<div class="item-toolbar">
|
||||
<input id="give-to" type="text" placeholder="Leave empty for yourself, or type a charname for /give2" style="flex:1; min-width:240px;" />
|
||||
</div>
|
||||
<div class="resultmeta" id="resultmeta-items"></div>
|
||||
<div class="item-grid" id="results-items"></div>
|
||||
<div class="configurator" id="configurator" style="display:none;">
|
||||
<h3 id="cfg-title"></h3>
|
||||
<div class="cfg-grid">
|
||||
<div class="cfg-field"><label>Count</label><input id="cfg-amount" type="number" value="1" min="1" /></div>
|
||||
<div class="cfg-field"><label>Refine (0-9)</label><input id="cfg-refine" type="number" value="0" min="0" max="9" /></div>
|
||||
<div class="cfg-field"><label>Lifespan</label><input id="cfg-life" type="number" value="100" min="0" /></div>
|
||||
<div class="cfg-field"><label>Socketed</label><select id="cfg-socket"><option value="0">no</option><option value="1">yes</option></select></div>
|
||||
<div class="cfg-field"><label>Stats</label><input id="cfg-stats" type="number" value="0" min="0" /></div>
|
||||
</div>
|
||||
<div class="cfg-out">
|
||||
<code id="cfg-command"></code>
|
||||
<button id="cfg-copy" type="button">Copy command</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===================== MAPS ===================== -->
|
||||
<section class="view" id="view-maps">
|
||||
<div class="pagehead">
|
||||
<h1>Every map with active spawn or NPC data</h1>
|
||||
<p>Aggregated live from <code>list_special_spawns</code> and <code>list_npcs</code>.</p>
|
||||
</div>
|
||||
<div class="map-empty-note">
|
||||
No minimap images or display names are shown here — this repo only ships the server side of KTRose, not the
|
||||
client's 3D/texture assets, and the localized zone-name strings aren't parsed by the server's simplified STB
|
||||
loader either. What's below (spawn counts, monster totals, coordinate bounds) is real, queried straight from
|
||||
the running database.
|
||||
</div>
|
||||
<div class="map-grid" id="results-maps"></div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="commands-data.js"></script>
|
||||
<script>
|
||||
/* ---------------- shared helpers ---------------- */
|
||||
function copyText(t){ navigator.clipboard.writeText(t).catch(()=>{}); }
|
||||
|
||||
/* ---------------- tab switching ---------------- */
|
||||
document.querySelectorAll(".tab").forEach(tab=>{
|
||||
tab.addEventListener("click", ()=>{
|
||||
document.querySelectorAll(".tab").forEach(t=>t.classList.remove("active"));
|
||||
document.querySelectorAll(".side-panel").forEach(p=>p.classList.remove("active"));
|
||||
document.querySelectorAll(".view").forEach(v=>v.classList.remove("active"));
|
||||
tab.classList.add("active");
|
||||
const view = tab.dataset.view;
|
||||
document.getElementById("panel-"+view).classList.add("active");
|
||||
document.getElementById("view-"+view).classList.add("active");
|
||||
document.getElementById("legend-commands").style.display = view==="commands" ? "flex" : "none";
|
||||
});
|
||||
});
|
||||
|
||||
/* ---------------- COMMANDS TAB ---------------- */
|
||||
function levelClass(l){ if(l.startsWith("dev")) return "pill-dev"; if(l.startsWith("gm")) return "pill-gm"; return "pill-any"; }
|
||||
function syntaxHTML(item){
|
||||
let out = `<span>${item.cmd}</span>`;
|
||||
item.args.forEach(a=>{ out += ` <span class="arg">${a}</span>`; });
|
||||
return out;
|
||||
}
|
||||
function renderCommands(filter){
|
||||
const root = document.getElementById("results-commands");
|
||||
const meta = document.getElementById("resultmeta-commands");
|
||||
root.innerHTML = "";
|
||||
let total=0, shown=0;
|
||||
const f=(filter||"").trim().toLowerCase();
|
||||
COMMANDS.forEach(group=>{
|
||||
const filtered = group.items.filter(it=>{
|
||||
total++;
|
||||
if(!f) return true;
|
||||
return (it.cmd+" "+it.args.join(" ")+" "+it.desc+" "+it.ex).toLowerCase().includes(f);
|
||||
});
|
||||
if(filtered.length===0) return;
|
||||
shown += filtered.length;
|
||||
const catEl = document.createElement("div");
|
||||
catEl.className="category";
|
||||
catEl.id="cat-"+group.cat.replace(/\W+/g,"-");
|
||||
catEl.innerHTML = `<h2>${group.cat}</h2>`;
|
||||
const list = document.createElement("div");
|
||||
list.className="cmdlist";
|
||||
filtered.forEach(it=>{
|
||||
const el = document.createElement("div");
|
||||
el.className="cmd";
|
||||
el.innerHTML = `
|
||||
<div class="cmd-row">
|
||||
<span class="cmd-syntax">${syntaxHTML(it)}</span>
|
||||
<span class="pill ${levelClass(it.level)}">${it.level}</span>
|
||||
<button class="copybtn" type="button">copy</button>
|
||||
</div>
|
||||
<div class="cmd-desc">${it.desc}</div>
|
||||
<div class="cmd-example">${it.ex}</div>`;
|
||||
el.querySelector(".copybtn").addEventListener("click",(e)=>{
|
||||
copyText(it.ex); e.target.textContent="copied"; setTimeout(()=>{e.target.textContent="copy";},1200);
|
||||
});
|
||||
list.appendChild(el);
|
||||
});
|
||||
catEl.appendChild(list);
|
||||
root.appendChild(catEl);
|
||||
});
|
||||
if(shown===0) root.innerHTML = `<div class="empty">No command matches "${filter}".</div>`;
|
||||
meta.textContent = f ? `${shown} of ${total} commands match` : `${total} commands across ${COMMANDS.length} categories`;
|
||||
}
|
||||
function buildCommandsNav(){
|
||||
const nav = document.getElementById("nav-commands");
|
||||
COMMANDS.forEach(group=>{
|
||||
const link = document.createElement("div");
|
||||
link.className="navlink";
|
||||
link.innerHTML = `<span>${group.cat}</span><span class="count">${group.items.length}</span>`;
|
||||
link.addEventListener("click", ()=>{
|
||||
document.getElementById("q-commands").value="";
|
||||
renderCommands("");
|
||||
const t = document.getElementById("cat-"+group.cat.replace(/\W+/g,"-"));
|
||||
if(t) t.scrollIntoView({behavior:"smooth", block:"start"});
|
||||
});
|
||||
nav.appendChild(link);
|
||||
});
|
||||
}
|
||||
document.getElementById("q-commands").addEventListener("input", e=>renderCommands(e.target.value));
|
||||
|
||||
/* ---------------- ITEMS TAB ---------------- */
|
||||
let ITEMS = {};
|
||||
let selectedItem = null;
|
||||
|
||||
function fmtCmd(itemtype, itemnum){
|
||||
const amount = document.getElementById("cfg-amount").value || 1;
|
||||
const refine = document.getElementById("cfg-refine").value || 0;
|
||||
const life = document.getElementById("cfg-life").value || 100;
|
||||
const socket = document.getElementById("cfg-socket").value || 0;
|
||||
const stats = document.getElementById("cfg-stats").value || 0;
|
||||
const target = document.getElementById("give-to").value.trim();
|
||||
if(target){
|
||||
return `/give2 ${target} ${itemnum} ${itemtype} ${amount} ${refine} ${life} ${socket} ${stats}`;
|
||||
}
|
||||
return `/item ${itemnum} ${itemtype} ${amount} ${refine} ${life} ${socket} ${stats}`;
|
||||
}
|
||||
|
||||
function openConfigurator(itemtype, itemnum, name){
|
||||
selectedItem = {itemtype, itemnum, name};
|
||||
document.getElementById("configurator").style.display = "block";
|
||||
document.getElementById("cfg-title").textContent = `${name} · itemtype ${itemtype} / itemnum ${itemnum}`;
|
||||
updateConfiguratorOutput();
|
||||
document.querySelectorAll(".item-card").forEach(c=>c.classList.remove("sel"));
|
||||
const el = document.querySelector(`.item-card[data-itemtype="${itemtype}"][data-itemnum="${itemnum}"]`);
|
||||
if(el) el.classList.add("sel");
|
||||
}
|
||||
function updateConfiguratorOutput(){
|
||||
if(!selectedItem) return;
|
||||
document.getElementById("cfg-command").textContent = fmtCmd(selectedItem.itemtype, selectedItem.itemnum);
|
||||
}
|
||||
["cfg-amount","cfg-refine","cfg-life","cfg-socket","cfg-stats"].forEach(id=>{
|
||||
document.getElementById(id).addEventListener("input", updateConfiguratorOutput);
|
||||
});
|
||||
document.getElementById("give-to").addEventListener("input", updateConfiguratorOutput);
|
||||
document.getElementById("cfg-copy").addEventListener("click", (e)=>{
|
||||
copyText(document.getElementById("cfg-command").textContent);
|
||||
e.target.textContent = "copied!"; setTimeout(()=>{e.target.textContent="Copy command";},1200);
|
||||
});
|
||||
|
||||
function renderItems(){
|
||||
const root = document.getElementById("results-items");
|
||||
const meta = document.getElementById("resultmeta-items");
|
||||
const cat = document.getElementById("item-cat").value;
|
||||
const q = document.getElementById("q-items").value.trim().toLowerCase();
|
||||
root.innerHTML = "";
|
||||
|
||||
if(!q && cat === "__all__"){
|
||||
meta.textContent = "Pick a category or search by name/id to browse — 9,951 items total.";
|
||||
return;
|
||||
}
|
||||
|
||||
let pool = [];
|
||||
const cats = cat === "__all__" ? Object.keys(ITEMS) : [cat];
|
||||
cats.forEach(k=>{
|
||||
ITEMS[k].items.forEach(([id,name])=>pool.push({itemtype:+k, id, name, catname:ITEMS[k].name}));
|
||||
});
|
||||
|
||||
let filtered = pool;
|
||||
if(q){
|
||||
filtered = pool.filter(it => it.name.toLowerCase().includes(q) || String(it.id)===q);
|
||||
}
|
||||
const CAP = 180;
|
||||
const total = filtered.length;
|
||||
const shown = filtered.slice(0, CAP);
|
||||
|
||||
shown.forEach(it=>{
|
||||
const el = document.createElement("div");
|
||||
el.className = "item-card";
|
||||
el.dataset.itemtype = it.itemtype;
|
||||
el.dataset.itemnum = it.id;
|
||||
el.innerHTML = `<div class="nm">${it.name}</div><div class="id">type ${it.itemtype} · id ${it.id}</div>`;
|
||||
el.addEventListener("click", ()=>openConfigurator(it.itemtype, it.id, it.name));
|
||||
root.appendChild(el);
|
||||
});
|
||||
|
||||
if(total===0){
|
||||
root.innerHTML = `<div class="empty">No item matches.</div>`;
|
||||
}
|
||||
meta.textContent = total > CAP
|
||||
? `Showing first ${CAP} of ${total} matches — narrow your search to see more.`
|
||||
: `${total} item${total===1?"":"s"}`;
|
||||
}
|
||||
document.getElementById("q-items").addEventListener("input", renderItems);
|
||||
document.getElementById("item-cat").addEventListener("change", renderItems);
|
||||
|
||||
/* ---------------- MAPS TAB ---------------- */
|
||||
let MAPS = [];
|
||||
function renderMaps(){
|
||||
const root = document.getElementById("results-maps");
|
||||
const sort = document.getElementById("map-sort").value;
|
||||
const sorted = [...MAPS].sort((a,b)=>{
|
||||
if(sort==="monsters") return b.totalMonsters - a.totalMonsters;
|
||||
if(sort==="npcs") return b.npcCount - a.npcCount;
|
||||
return a.id - b.id;
|
||||
});
|
||||
root.innerHTML = "";
|
||||
sorted.forEach(m=>{
|
||||
const el = document.createElement("div");
|
||||
el.className = "map-card";
|
||||
const bounds = m.bounds
|
||||
? `<div>X range</div><b>${m.bounds.minX} – ${m.bounds.maxX}</b><div>Y range</div><b>${m.bounds.minY} – ${m.bounds.maxY}</b>`
|
||||
: `<div>Coordinates</div><b>no spawn data</b><div></div><b></b>`;
|
||||
el.innerHTML = `
|
||||
<h3>Map ${m.id}</h3>
|
||||
${m.note ? `<div class="note">${m.note}</div>` : ""}
|
||||
<div class="map-stats">
|
||||
<div>Spawn points</div><b>${m.spawnPoints}</b>
|
||||
<div>Monsters (max pop.)</div><b>${m.totalMonsters}</b>
|
||||
<div>NPCs</div><b>${m.npcCount}</b>
|
||||
${bounds}
|
||||
</div>`;
|
||||
root.appendChild(el);
|
||||
});
|
||||
}
|
||||
document.getElementById("map-sort").addEventListener("change", renderMaps);
|
||||
|
||||
/* ---------------- boot ---------------- */
|
||||
buildCommandsNav();
|
||||
renderCommands("");
|
||||
|
||||
fetch("items.json").then(r=>r.json()).then(data=>{
|
||||
ITEMS = data;
|
||||
const sel = document.getElementById("item-cat");
|
||||
sel.innerHTML = `<option value="__all__">All categories</option>` +
|
||||
Object.keys(data).sort((a,b)=>+a-+b).map(k=>`<option value="${k}">${data[k].name} (${data[k].items.length})</option>`).join("");
|
||||
renderItems();
|
||||
});
|
||||
|
||||
fetch("maps.json").then(r=>r.json()).then(data=>{
|
||||
MAPS = data;
|
||||
renderMaps();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
{"1": {"name": "masks", "items": [[1, "Flu Mask"], [2, "Black Mask"], [3, "ButterFly Mask"], [4, "Zorro Mask"], [5, "Persian Mask"], [6, "Wicked Mask"], [7, "Jolly Front Mask"], [8, "Iron Mask"], [9, "Totem Mask"], [10, "Goblin Mask"], [11, "Ashura Mask"], [12, "Orc Mask"], [13, "Mask of Lies"], [14, "Mask of Truth"], [15, "Mask of Venus"], [16, "Demon Mask"], [31, "Glasses Frame"], [32, "Dork Glasses"], [33, "Thick Framed Glasses"], [34, "Sunglasses"], [35, "Charming Sunglasses"], [36, "Swimming Goggles"], [38, "Cart Goggles"], [62, "Pierrot Nose"], [64, "Pinocchio Nose"], [73, "Santa Beard"], [121, "Ego Mask"], [151, "Scream Mask"], [152, "Nomadic Mask"], [153, "Mummy Face"], [156, "Jason Mask"], [157, "Scream Mask"], [161, "Star Glasses"], [162, "Matrix Eye"], [166, "Cherry Blossom Glasses"], [167, "ROSE Captain's Eyepatch"], [168, "Valentine Eyepatch"], [169, "Plastic Rimmed Glasses"], [170, "Black Mustache"], [171, "Rat Tail Mustache"], [172, "Oro's Present"], [176, "Pink Chibi Punk Bunny Eyepatch"], [177, "Chibi Punk Bunny Eyepatch"], [181, "Junon Mask"], [191, "Cheek Beauty Spot"], [194, "Silver Circlet"], [195, "Chicken Beak"], [196, "Gray Mustache"], [197, "Chin Beauty Spot"], [198, "Brown Tinted Glasses"], [199, "Librarians Spectacles"], [200, "Rolf Glasses"], [201, "Moses Beard"], [202, "Tribal War Mask"], [204, "Teacher Glasses"], [205, "Nautilus Goggles"], [206, "Identity Protector"], [207, "Potter Glasses"], [208, "Rose Tinted Glasses"], [209, "Pink Sunglasses"], [210, "See No Evil Mask"], [211, "Pink Germ Mask"], [212, "Blue Germ Mask"], [213, "Germ Mask"], [214, "Polka Dot Sleep Mask"], [215, "Half-Rim Glasses"], [216, "Medical Eyepatch"], [217, "Gold Scream Mask"], [218, "Silver Scream Mask"], [219, "Metal Half-Face"], [301, "Flu Mask"], [302, "Flu Mask"], [303, "Flu Mask"], [304, "Flu Mask"], [305, "Flu Mask"], [306, "Flu Mask"], [307, "Flu Mask"], [308, "Flu Mask"], [311, "Black Mask"], [312, "Black Mask"], [313, "Black Mask"], [314, "Black Mask"], [315, "Black Mask"], [316, "Black Mask"], [317, "Black Mask"], [318, "Black Mask"], [321, "ButterFly Mask"], [322, "ButterFly Mask"], [323, "ButterFly Mask"], [324, "ButterFly Mask"], [325, "ButterFly Mask"], [326, "ButterFly Mask"], [327, "ButterFly Mask"], [328, "ButterFly Mask"], [331, "Zorro Mask"], [332, "Zorro Mask"], [333, "Zorro Mask"], [334, "Zorro Mask"], [335, "Zorro Mask"], [336, "Zorro Mask"], [337, "Zorro Mask"], [338, "Zorro Mask"], [341, "Persian Mask"], [342, "Persian Mask"], [343, "Persian Mask"], [344, "Persian Mask"], [345, "Persian Mask"], [346, "Persian Mask"], [347, "Persian Mask"], [348, "Persian Mask"], [351, "Wicked Mask"], [352, "Wicked Mask"], [353, "Wicked Mask"], [354, "Wicked Mask"], [355, "Wicked Mask"], [356, "Wicked Mask"], [357, "Wicked Mask"], [358, "Wicked Mask"], [361, "Jolly Front Mask"], [362, "Jolly Front Mask"], [363, "Jolly Front Mask"], [364, "Jolly Front Mask"], [365, "Jolly Front Mask"], [366, "Jolly Front Mask"], [367, "Jolly Front Mask"], [368, "Jolly Front Mask"], [371, "Iron Mask"], [372, "Iron Mask"], [373, "Iron Mask"], [374, "Iron Mask"], [375, "Iron Mask"], [376, "Iron Mask"], [377, "Iron Mask"], [378, "Iron Mask"], [381, "Totem Mask"], [382, "Totem Mask"], [383, "Totem Mask"], [384, "Totem Mask"], [385, "Totem Mask"], [386, "Totem Mask"], [387, "Totem Mask"], [388, "Totem Mask"], [391, "Ashura Mask"], [392, "Ashura Mask"], [393, "Ashura Mask"], [394, "Ashura Mask"], [395, "Ashura Mask"], [396, "Ashura Mask"], [397, "Ashura Mask"], [398, "Ashura Mask"], [401, "Mask of Lies"], [402, "Mask of Lies"], [403, "Mask of Lies"], [404, "Mask of Lies"], [405, "Mask of Lies"], [406, "Mask of Lies"], [407, "Mask of Lies"], [408, "Mask of Lies"], [411, "Ego Mask"], [412, "Ego Mask"], [413, "Ego Mask"], [414, "Ego Mask"], [415, "Ego Mask"], [416, "Ego Mask"], [417, "Ego Mask"], [418, "Ego Mask"], [421, "Glasses Frame"], [422, "Glasses Frame"], [423, "Glasses Frame"], [424, "Glasses Frame"], [425, "Glasses Frame"], [426, "Glasses Frame"], [427, "Glasses Frame"], [428, "Glasses Frame"], [431, "Dork Glasses"], [432, "Dork Glasses"], [433, "Dork Glasses"], [434, "Dork Glasses"], [435, "Dork Glasses"], [436, "Dork Glasses"], [437, "Dork Glasses"], [438, "Dork Glasses"], [441, "Thick Framed Glasses"], [442, "Thick Framed Glasses"], [443, "Thick Framed Glasses"], [444, "Thick Framed Glasses"], [445, "Thick Framed Glasses"], [446, "Thick Framed Glasses"], [447, "Thick Framed Glasses"], [448, "Thick Framed Glasses"], [451, "Sunglasses"], [452, "Sunglasses"], [453, "Sunglasses"], [454, "Sunglasses"], [455, "Sunglasses"], [456, "Sunglasses"], [457, "Sunglasses"], [458, "Sunglasses"], [461, "Charming Sunglasses"], [462, "Charming Sunglasses"], [463, "Charming Sunglasses"], [464, "Charming Sunglasses"], [465, "Charming Sunglasses"], [466, "Charming Sunglasses"], [467, "Charming Sunglasses"], [468, "Charming Sunglasses"], [471, "Cart Goggles"], [472, "Cart Goggles"], [473, "Cart Goggles"], [474, "Cart Goggles"], [475,Line truncated
|
||||
@@ -0,0 +1,32 @@
|
||||
[
|
||||
{"id":1,"spawnPoints":45,"totalMonsters":225,"npcCount":22,"bounds":{"minX":5097,"maxX":5500,"minY":5050,"maxY":5448}},
|
||||
{"id":2,"spawnPoints":43,"totalMonsters":92,"npcCount":27,"bounds":{"minX":5075,"maxX":5767,"minY":5433,"maxY":5535}},
|
||||
{"id":3,"spawnPoints":0,"totalMonsters":0,"npcCount":1,"bounds":null},
|
||||
{"id":5,"spawnPoints":0,"totalMonsters":0,"npcCount":2,"bounds":null},
|
||||
{"id":6,"spawnPoints":0,"totalMonsters":0,"npcCount":1,"bounds":null},
|
||||
{"id":9,"spawnPoints":0,"totalMonsters":0,"npcCount":2,"bounds":null},
|
||||
{"id":11,"spawnPoints":0,"totalMonsters":0,"npcCount":4,"bounds":null},
|
||||
{"id":12,"spawnPoints":0,"totalMonsters":0,"npcCount":4,"bounds":null},
|
||||
{"id":13,"spawnPoints":0,"totalMonsters":0,"npcCount":4,"bounds":null},
|
||||
{"id":15,"spawnPoints":0,"totalMonsters":0,"npcCount":18,"bounds":null},
|
||||
{"id":20,"spawnPoints":46,"totalMonsters":455,"npcCount":5,"bounds":{"minX":5030,"maxX":5713,"minY":4738,"maxY":5375},"note":"default respawn/town map (townid=20 in characters table)"},
|
||||
{"id":21,"spawnPoints":43,"totalMonsters":183,"npcCount":3,"bounds":{"minX":5098,"maxX":5435,"minY":5053,"maxY":5505}},
|
||||
{"id":22,"spawnPoints":52,"totalMonsters":415,"npcCount":9,"bounds":{"minX":4983,"maxX":5748,"minY":4995,"maxY":5485}},
|
||||
{"id":23,"spawnPoints":44,"totalMonsters":201,"npcCount":4,"bounds":{"minX":5053,"maxX":5512,"minY":4900,"maxY":5498}},
|
||||
{"id":24,"spawnPoints":63,"totalMonsters":281,"npcCount":3,"bounds":{"minX":5053,"maxX":5685,"minY":4998,"maxY":5530}},
|
||||
{"id":25,"spawnPoints":74,"totalMonsters":232,"npcCount":3,"bounds":{"minX":5023,"maxX":5688,"minY":4871,"maxY":5520}},
|
||||
{"id":26,"spawnPoints":97,"totalMonsters":241,"npcCount":1,"bounds":{"minX":5053,"maxX":5695,"minY":4873,"maxY":5544}},
|
||||
{"id":27,"spawnPoints":100,"totalMonsters":250,"npcCount":5,"bounds":{"minX":5005,"maxX":5831,"minY":4979,"maxY":5509}},
|
||||
{"id":28,"spawnPoints":55,"totalMonsters":126,"npcCount":1,"bounds":{"minX":5027,"maxX":5567,"minY":4769,"maxY":5518}},
|
||||
{"id":31,"spawnPoints":27,"totalMonsters":75,"npcCount":0,"bounds":{"minX":5028,"maxX":5446,"minY":5057,"maxY":5449}},
|
||||
{"id":32,"spawnPoints":31,"totalMonsters":74,"npcCount":0,"bounds":{"minX":5079,"maxX":5523,"minY":5138,"maxY":5541}},
|
||||
{"id":33,"spawnPoints":2,"totalMonsters":20,"npcCount":0,"bounds":{"minX":5246,"maxX":5251,"minY":5352,"maxY":5372}},
|
||||
{"id":37,"spawnPoints":0,"totalMonsters":0,"npcCount":1,"bounds":null},
|
||||
{"id":38,"spawnPoints":7,"totalMonsters":53,"npcCount":2,"bounds":{"minX":5080,"maxX":5164,"minY":5371,"maxY":5478}},
|
||||
{"id":40,"spawnPoints":0,"totalMonsters":0,"npcCount":1,"bounds":null},
|
||||
{"id":51,"spawnPoints":35,"totalMonsters":103,"npcCount":9,"bounds":{"minX":5048,"maxX":5640,"minY":4710,"maxY":5296}},
|
||||
{"id":52,"spawnPoints":49,"totalMonsters":176,"npcCount":0,"bounds":{"minX":5041,"maxX":6111,"minY":4467,"maxY":5271}},
|
||||
{"id":54,"spawnPoints":56,"totalMonsters":156,"npcCount":1,"bounds":{"minX":5057,"maxX":5645,"minY":4285,"maxY":5313}},
|
||||
{"id":61,"spawnPoints":0,"totalMonsters":0,"npcCount":17,"bounds":null},
|
||||
{"id":62,"spawnPoints":0,"totalMonsters":0,"npcCount":8,"bounds":null}
|
||||
]
|
||||
Reference in new issue
Block a user