1 line
No EOL
16 KiB
JavaScript
1 line
No EOL
16 KiB
JavaScript
"use strict";(globalThis.webpackChunkwebsite=globalThis.webpackChunkwebsite||[]).push([[268],{7e3(e,t,n){n.r(t),n.d(t,{assets:()=>a,contentTitle:()=>d,default:()=>h,frontMatter:()=>r,metadata:()=>s,toc:()=>c});const s=JSON.parse('{"id":"development/commands-and-targeting","title":"Commands & Targeting","description":"Overview","source":"@site/content/development/commands-and-targeting.mdx","sourceDirName":"development","slug":"/development/commands-and-targeting","permalink":"/docs/development/commands-and-targeting","draft":false,"unlisted":false,"editUrl":"https://github.com/modernuo/ModernUO/tree/main/website/content/development/commands-and-targeting.mdx","tags":[],"version":"current","sidebarPosition":4,"frontMatter":{"sidebar_position":4,"title":"Commands & Targeting"},"sidebar":"docsSidebar","previous":{"title":"Timers","permalink":"/docs/development/timers"},"next":{"title":"Era & Expansions","permalink":"/docs/development/era-and-expansions"}}');var l=n(4848),i=n(8453);const r={sidebar_position:4,title:"Commands & Targeting"},d="Commands & Targeting",a={},c=[{value:"Overview",id:"overview",level:2},{value:"Registering a Command",id:"registering-a-command",level:2},{value:"CommandEventArgs API",id:"commandeventargs-api",level:2},{value:"Access Levels",id:"access-levels",level:2},{value:"Targeting System",id:"targeting-system",level:2},{value:"Target Implementation",id:"target-implementation",level:2},{value:"Command + Targeting Pattern",id:"command--targeting-pattern",level:2},{value:"TargetFlags",id:"targetflags",level:2},{value:"Best Practices",id:"best-practices",level:2}];function o(e){const t={admonition:"admonition",code:"code",h1:"h1",h2:"h2",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(t.header,{children:(0,l.jsx)(t.h1,{id:"commands--targeting",children:"Commands & Targeting"})}),"\n",(0,l.jsx)(t.h2,{id:"overview",children:"Overview"}),"\n",(0,l.jsxs)(t.p,{children:["ModernUO uses a command system where all player/staff commands are prefixed with ",(0,l.jsx)(t.code,{children:"["})," by default (this is configurable). Commands are registered in static ",(0,l.jsx)(t.code,{children:"Configure()"})," methods that the server discovers automatically at startup. Each command is bound to a minimum access level, so only authorized players can execute it."]}),"\n",(0,l.jsx)(t.hr,{}),"\n",(0,l.jsx)(t.h2,{id:"registering-a-command",children:"Registering a Command"}),"\n",(0,l.jsxs)(t.p,{children:["Commands are registered by calling ",(0,l.jsx)(t.code,{children:"CommandSystem.Register"})," inside a static ",(0,l.jsx)(t.code,{children:"Configure()"})," method. The server calls all ",(0,l.jsx)(t.code,{children:"Configure()"})," methods during initialization -- no manual wiring is needed."]}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-csharp",children:'public static class MyCommands\n{\n public static void Configure()\n {\n CommandSystem.Register("MyCommand", AccessLevel.GameMaster, MyCommand_OnCommand);\n }\n\n [Usage("MyCommand <name>")]\n [Description("Does something with a name")]\n public static void MyCommand_OnCommand(CommandEventArgs e)\n {\n var from = e.Mobile;\n if (e.Length < 1)\n {\n from.SendMessage("Usage: [MyCommand <name>");\n return;\n }\n\n var name = e.GetString(0);\n from.SendMessage($"Processing {name}");\n }\n}\n'})}),"\n",(0,l.jsxs)(t.p,{children:["The ",(0,l.jsx)(t.code,{children:"[Usage]"})," and ",(0,l.jsx)(t.code,{children:"[Description]"})," attributes provide help text that appears in the in-game help system."]}),"\n",(0,l.jsx)(t.hr,{}),"\n",(0,l.jsx)(t.h2,{id:"commandeventargs-api",children:"CommandEventArgs API"}),"\n",(0,l.jsxs)(t.p,{children:["When a command handler fires, it receives a ",(0,l.jsx)(t.code,{children:"CommandEventArgs"})," object with the following members:"]}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Member"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Type"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Description"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"Mobile"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"Mobile"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"The mobile that issued the command"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"Command"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"string"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"The command name that was typed"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"ArgString"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"string"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"The full argument string after the command name"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"Arguments"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"string[]"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Arguments split by spaces"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"Length"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"int"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Number of arguments (",(0,l.jsx)(t.code,{children:"Arguments.Length"}),")"]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"GetString(i)"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"string"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Get argument at index ",(0,l.jsx)(t.code,{children:"i"})," as a string"]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"GetInt32(i)"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"int"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Get argument at index ",(0,l.jsx)(t.code,{children:"i"})," as an integer"]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"GetBoolean(i)"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"bool"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Get argument at index ",(0,l.jsx)(t.code,{children:"i"})," as a boolean"]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"GetDouble(i)"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"double"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Get argument at index ",(0,l.jsx)(t.code,{children:"i"})," as a double"]})]})]})]}),"\n",(0,l.jsx)(t.hr,{}),"\n",(0,l.jsx)(t.h2,{id:"access-levels",children:"Access Levels"}),"\n",(0,l.jsx)(t.p,{children:"Each command requires a minimum access level. Players below that level cannot execute the command."}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Level"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Value"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Description"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"Player"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"0"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Normal player (default)"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"Counselor"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"1"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Support staff with limited powers"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"GameMaster"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"2"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"GM with full world interaction"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"Seer"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"3"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Event coordinator with extra tools"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"Administrator"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"4"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Server administrator"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"Developer"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"5"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Developer with access to debug commands"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"Owner"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"6"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Server owner with unrestricted access"})]})]})]}),"\n",(0,l.jsx)(t.hr,{}),"\n",(0,l.jsx)(t.h2,{id:"targeting-system",children:"Targeting System"}),"\n",(0,l.jsx)(t.p,{children:"The targeting system lets a command (or any code) ask a player to click on something in the game world. The flow is:"}),"\n",(0,l.jsxs)(t.ol,{children:["\n",(0,l.jsxs)(t.li,{children:["Code sets ",(0,l.jsx)(t.code,{children:"mobile.Target = new MyTarget()"}),"."]}),"\n",(0,l.jsx)(t.li,{children:"The client displays a targeting cursor."}),"\n",(0,l.jsx)(t.li,{children:"The player clicks on a mobile, item, land tile, or static object."}),"\n",(0,l.jsxs)(t.li,{children:["The ",(0,l.jsx)(t.code,{children:"OnTarget"})," method fires with what was clicked."]}),"\n"]}),"\n",(0,l.jsx)(t.hr,{}),"\n",(0,l.jsx)(t.h2,{id:"target-implementation",children:"Target Implementation"}),"\n",(0,l.jsxs)(t.p,{children:["A target class inherits from ",(0,l.jsx)(t.code,{children:"Target"})," and overrides ",(0,l.jsx)(t.code,{children:"OnTarget"}),". The ",(0,l.jsx)(t.code,{children:"targeted"})," parameter can be a ",(0,l.jsx)(t.code,{children:"Mobile"}),", ",(0,l.jsx)(t.code,{children:"Item"}),", ",(0,l.jsx)(t.code,{children:"LandTarget"}),", or ",(0,l.jsx)(t.code,{children:"StaticTarget"})," -- use a ",(0,l.jsx)(t.code,{children:"switch"})," to handle each case."]}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-csharp",children:'public class IdentifyTarget : Target\n{\n public IdentifyTarget() : base(12, false, TargetFlags.None)\n {\n }\n\n protected override void OnTarget(Mobile from, object targeted)\n {\n switch (targeted)\n {\n case Mobile m:\n {\n from.SendMessage($"That is a mobile named {m.Name}.");\n break;\n }\n case Item item:\n {\n from.SendMessage($"That is an item: {item.GetType().Name} (0x{item.ItemID:X4}).");\n break;\n }\n case LandTarget land:\n {\n from.SendMessage($"That is land tile at {land.Location}.");\n break;\n }\n case StaticTarget st:\n {\n from.SendMessage($"That is a static: 0x{st.ItemID:X4}.");\n break;\n }\n }\n }\n}\n'})}),"\n",(0,l.jsxs)(t.p,{children:["The ",(0,l.jsx)(t.code,{children:"Target"})," constructor takes three parameters:"]}),"\n",(0,l.jsxs)(t.ul,{children:["\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"range"})," -- Maximum distance the target can be from the player."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"allowGround"})," -- Whether clicking the ground is valid."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"flags"})," -- ",(0,l.jsx)(t.code,{children:"TargetFlags"})," value controlling criminal/beneficial checks."]}),"\n"]}),"\n",(0,l.jsx)(t.hr,{}),"\n",(0,l.jsx)(t.h2,{id:"command--targeting-pattern",children:"Command + Targeting Pattern"}),"\n",(0,l.jsx)(t.p,{children:"A common pattern is for a command to initiate targeting, then the target handler performs the actual work. This cleanly separates input from logic."}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-csharp",children:'public static class HealCommands\n{\n public static void Configure()\n {\n CommandSystem.Register("Heal", AccessLevel.GameMaster, Heal_OnCommand);\n }\n\n [Usage("Heal")]\n [Description("Fully heals the targeted mobile")]\n public static void Heal_OnCommand(CommandEventArgs e)\n {\n e.Mobile.SendMessage("Who do you want to heal?");\n e.Mobile.Target = new HealTarget();\n }\n\n private class HealTarget : Target\n {\n public HealTarget() : base(12, false, TargetFlags.Beneficial)\n {\n }\n\n protected override void OnTarget(Mobile from, object targeted)\n {\n if (targeted is Mobile m)\n {\n m.Hits = m.HitsMax;\n m.SendMessage("You have been fully healed.");\n from.SendMessage($"You healed {m.Name}.");\n }\n else\n {\n from.SendMessage("That is not a mobile.");\n }\n }\n }\n}\n'})}),"\n",(0,l.jsx)(t.hr,{}),"\n",(0,l.jsx)(t.h2,{id:"targetflags",children:"TargetFlags"}),"\n",(0,l.jsx)(t.p,{children:"Target flags tell the server what kind of action the player is performing, which affects criminal checks and other systems."}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Flag"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Description"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"None"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Neutral action -- no criminal or beneficial checks"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"Harmful"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Hostile action -- triggers criminal flagging if targeting innocents"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"Beneficial"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Helpful action -- triggers beneficial checks (healing, buffing)"})]})]})]}),"\n",(0,l.jsx)(t.admonition,{type:"tip",children:(0,l.jsxs)(t.p,{children:["Always set the correct flag. Using ",(0,l.jsx)(t.code,{children:"Harmful"})," on a healing target (or ",(0,l.jsx)(t.code,{children:"None"})," on an attack) bypasses important game mechanics like the criminal system."]})}),"\n",(0,l.jsx)(t.hr,{}),"\n",(0,l.jsx)(t.h2,{id:"best-practices",children:"Best Practices"}),"\n",(0,l.jsxs)(t.ul,{children:["\n",(0,l.jsxs)(t.li,{children:[(0,l.jsxs)(t.strong,{children:["Register in ",(0,l.jsx)(t.code,{children:"Configure()"})]})," -- The server discovers these methods automatically. Do not register commands in constructors or other lifecycle methods."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Validate argument count"})," -- Always check ",(0,l.jsx)(t.code,{children:"e.Length"})," before accessing arguments to avoid index-out-of-range errors."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Use appropriate access levels"})," -- Do not default to ",(0,l.jsx)(t.code,{children:"Owner"}),". Choose the lowest level that makes sense for the command."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsxs)(t.strong,{children:["Use ",(0,l.jsx)(t.code,{children:"Harmful"}),"/",(0,l.jsx)(t.code,{children:"Beneficial"})," flags correctly"]})," -- This ensures the criminal and notoriety systems work as intended."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Keep target handlers focused"})," -- Let the command set up the target, and let the target handler do the work."]}),"\n"]})]})}function h(e={}){const{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,l.jsx)(t,{...e,children:(0,l.jsx)(o,{...e})}):o(e)}}}]); |