Shipped scripts
This page is part of Writing Lua scripts. The distribution ships these scripts under
scripts/, and mgctl init copies them into the root: each one is an example to read and to change. How a
script is bound to a template is told in Mobile scripts and Item scripts,
where wander.lua and potion.lua are listed.
common/cooldown.lua
Section titled “common/cooldown.lua”Use local cooldown = require("common.cooldown") to check a deadline with the same rules across scripts.
Pass item, mobile or npc as the store to use existing saved properties; keys are used exactly as supplied.
Create a separate cooldown.memory() store for temporary state, such as the healing-potion wait.
| Function | Result |
|---|---|
cooldown.ready(store, serial, key, now?) |
True when there is no valid deadline or it has been reached |
cooldown.remaining(store, serial, key, now?) |
Seconds until the deadline, never below zero |
cooldown.start(store, serial, key, seconds, now?) |
Stores now + seconds; true only when the store accepts the write |
cooldown.clear(store, serial, key) |
Removes the deadline; true only when the store accepts the write |
cooldown.memory() |
A new, independent store with no persistence |
local cooldown = require("common.cooldown")
cooldown.start(mobile, player, "daily.reward.next", 86400)local seconds_left = cooldown.remaining(mobile, player, "daily.reward.next")
local temporary = cooldown.memory()cooldown.start(temporary, player, "heal", 11)local can_heal = cooldown.ready(temporary, player, "heal")The optional now is a finite number for deterministic checks; otherwise the helper uses world.now(),
whole UTC seconds. It is ready at the exact deadline. Durations must be finite and nonnegative; zero is
ready immediately. Serials must be positive 32-bit integers and keys nonblank strings. Invalid arguments
raise a Lua error before writing. A missing, nonnumeric or nonfinite saved deadline is treated as ready.
A valid serial with no entity can therefore be ready, while start still returns false if the native store
cannot write it: this helper does not establish item access or reward eligibility.
Property deadlines follow the existing world-save persistence. Memory stores belong to the consumer that
holds them: a new store is empty, and they are not saved or automatically cleared at logout. Call clear
when state is no longer needed. Reloading a consumer that recreates its local store resets that state; merely
reloading the helper does not erase an existing store. Clock corrections affect UTC deadlines.
The shipped trick_or_treat.lua and fillable.lua keep their existing trick_or_treat.next and fill.next
properties. potion.lua keeps its heal wait in memory. This helper does not schedule a callback: use
timer.after or persistent item.start_timer when code must run later.
common/random.lua
Section titled “common/random.lua”Use local random = require("common.random") for reusable list choices. The helper uses math.random
by default and never seeds it. Each call accepts an optional RNG function, useful for deterministic tests.
| Function | Result |
|---|---|
random.choice(list, roll?) |
One value from a dense list; nil for an empty list |
random.chance(probability, roll?) |
True with probability from 0 to 1; 0 is always false and 1 always true |
random.weighted(entries, roll?) |
One value using its integer weight; nil when the total is zero |
local random = require("common.random")
local color = random.choice({ "red", "blue" })local succeeds = random.chance(0.25)local weighted_color = random.weighted({ { value = "red", weight = 60 }, { value = "blue", weight = 40 },})local predictable = random.choice({ "red", "blue" }, function(count) return count end)Lists must have consecutive integer keys starting at 1, with no holes or extra fields. Weights must be nonnegative integers, and their total must not exceed 2,147,483,647. A weighted entry needs a nonnil value; false is a valid value. Zero-weight entries are never selected. Invalid arguments or RNG results raise a Lua error.
For choice and weighted, roll(count) must return an integer from 1 to count inclusive. For chance,
roll() must return a number in [0, 1); success means the draw is strictly below the probability.
Empty lists, zero total weight and probability endpoints do not draw. The default RNG is resolved on each
call, so replacing a script’s test RNG remains possible without changing global seeds.
Halloween uses choice for its lines, candies and blood graphics. Christmas uses weighted for its
60/24/16 decoration distribution, preserving the original integer draw boundaries. For dice notation,
continue to use the native dice.roll and dice.try_roll module.
monster.lua
Section titled “monster.lua”The distribution’s scripts/mobiles/monster.lua is the script of the monsters that go for the players,
after ModernUO’s melee AI: it chases a player and fights it from beside it. A template takes it with
script_id = "monster"; the creatures whose UOX3 NPCAI is evil, evil caster or chaotic do (the
orcs, the ogres, the lizardmen, the dragons, the undead and the other 200 or so templates and those based on
them; UOX3 migration); the good fighters and casters, who fight criminals only, do not. The casters, among them the wraith, the spectre and the lich, cast the spells of Magery from a distance
(common/caster.lua) when their template sets spell_circles. The behaviour is the shared module
common/creature.lua, creature.new({ hunts = true }). A monster is in one of three states:
| State | What it does | It ends when |
|---|---|---|
| wander | Strolls in its home, the area of its spawn region: about a step every two seconds, mostly straight ahead. It strolls with npc.wander, which walks it back from outside, as after a chase. One think in twenty it rests 15 to 25 seconds, with its idle sound and a fidget |
It sees a player |
| chase | Threatens the player with its start_attack sound and an animation, goes into war mode and walks to it with npc.walk_to, a step every think, never running. Beside it, it faces it and fights it with combat.attack, once: the swings, the hits and the death are the combat service’s |
The player hides, leaves, is farther than 32 tiles, or gets no nearer for 20 seconds (out of reach) |
| guard | Stops fighting, stands in war mode for 10 seconds, looking around | It sees a player, or the time is over: back to wander, in peace |
A monster that is hit, or missed, fights back (the combat service sees to it) and turns on the one who fights it whatever it was doing, wandering or on guard, even if it had not seen it: it goes to war mode and chases it, without threatening it again. One that already fights turns on a new attacker that is not hidden from it and is closer than its target, as the combat service decides; a tamed creature keeps the target it was commanded.
A chase follows the path search, which goes round walls and closed doors and, when the prey cannot be reached, to the nearest place; it never finds prey it has no line of sight to, but it follows one it saw within 32 tiles. A step counts only when it brings the creature closer to the prey than it has been in this chase: 40 thinks, 20 seconds, with no such step, whether it stands still, is refused or paces along a wall after prey it cannot reach (inside a locked house, on a roof, across a river), make it give the prey up, stand guard and then wander. Prey that moves can be taken up again at the next look, as it is a new chase. Its state is not saved: after a restart a creature starts again from wandering, and what a mind left on it (running, war mode) is cleared.
It looks for prey every two seconds while it wanders and every second on guard, among the first six of
npc.mobiles_in_sight: within 16 tiles and in line of sight, from eye to eye, nearest first. Its prey is any player
and, of the NPCs, the ones with a blue name, the townsfolk, mobile.notoriety innocent: a monster in a town goes for
the people in the street as well as for the players. It leaves alone the yellow ones, the vendors, the bankers and
the guards, that cannot be hurt, and the other creatures, animals or monsters. It never sees a hidden player, a ghost, a
game master or an administrator. Once it chases its prey it follows it without seeing it (npc.can_see with
in_sight false), up to the leash. Prey it could not reach is left alone until it moves. Its threat and its fidget
are the actions of a monster body; a creature with a human or an animal body, such as a brigand, plays none, since
those bodies number their actions otherwise. What a monster is doing is kept in memory by its serial, not
saved, and forgotten when it dies: after a restart, or once no player is near enough for it to think, it starts again
from wandering. The numbers (16, 32, the times) are constants at the top of the file.
The town guards go for the monsters, as ModernUO’s: guard.lua counts as wanted any NPC whose
npc.script_id is monster, as a criminal, unless it is dead or tamed, and wherever it stands: a guard that a game
master spawned in a graveyard outside the towns fights the monsters there, with no post to keep it. A criminal that is no monster is only
wanted in a guarded region. The guard appears beside it, strikes it and kills it with one blow.
guard.lua
Section titled “guard.lua”The distribution’s scripts/mobiles/guard.lua is the script of the town guards: the ones that stand in
the towns by their spawn, and the ones a player calls by saying “guards” (see
ultima.crime). A template takes it with script_id = "guard"; guard,
m_guard and f_guard do. A guard kills a criminal that is an NPC with
one blow, as ModernUO’s does, and only stands on one that is a player: the script spares players, though players
can die now. A
guard is in one of two states:
| State | What it does | It ends when |
|---|---|---|
| post | Strolls around its post, the area of its spawn region, about a step every four seconds, with npc.wander, which also walks it back from outside |
It sees a criminal: to arrest |
| arrest | Goes into war mode; when it is not beside the criminal it appears on a free tile a step from it (world.spot_beside; on it when none is free), with a puff of smoke where it stood and where it comes and the teleport sound; says “Thou wilt regret thine actions, swine!” (message 30138). Then it stays on the criminal, facing it, and runs after it with npc.walk_to when it moves. Beside a criminal NPC it strikes (an attack animation) and a second later the NPC is dead: mobile.kill, with the guard as its killer, so it dies as any other and leaves its corpse |
The NPC is killed; or the criminal is pardoned or its time is over, hides, leaves the guarded region, goes farther than 24 tiles from the guard or from its post, or cannot be reached for 10 seconds: back to its post, in peace |
It looks for a criminal every second: the nearest player or NPC of npc.nearby within 12 tiles whose
mobile.criminal or mobile.is_murderer is true (a red name is wanted like a grey one), that stands in a guarded region (world.is_guarded; a monster that is not tamed is wanted wherever it stands) no farther than 24
tiles from the guard’s post (npc.home, none for a guard spawned by hand), and that it sees (npc.can_see); only a criminal costs the
look along the line of sight. The post is the measure, not the guard, so a criminal cannot lead a guard
out of town step by step. A criminal it could not reach is left alone until it moves. It never sees a
hidden player, a game master or an administrator. An NPC it has just killed is still there while it
falls: the guard does not turn on it again. A teleport that is refused leaves the guard where it is, to
run to the criminal.
The archer guard, template archerguard (the guards called in Ilshenar and Malas, after ModernUO’s ArcherGuard, without its horse and its stats: it is the guard’s stats with a bow), is
this guard with a bow in its hands and combat.range of 10. At an NPC it goes for, a criminal or a monster, that is
within its range and in its sight, it does not come beside it: it stands where it is, faces it and starts the fight
(combat.attack), and the combat service shoots with the Archery skill; the arrest is over when the target is dead.
Out of its range, or out of its sight, it comes beside the NPC as any guard does, and shoots from there. At a player it
is the same guard as the others: it comes beside it and stands on it.
A guard that was called bears the prop guard.summoned: it came beside its criminal and said its line
already, so it stays on it in silence and does not stroll, and once that criminal is let go it arrests
no other and waits to be sent away. What a guard is doing is kept in memory by its serial, not saved.
The numbers (12, 24, the 10 seconds) are constants at the top of the file.
animal.lua and scared_animal.lua
Section titled “animal.lua and scared_animal.lua”The animals, after ModernUO’s animal AI, use the same module with hunts = false: they stroll in their home, rest now and then
with their idle sound and a fidget, and never go for a player. scripts/mobiles/animal.lua (script_id = "animal", the
creatures whose UOX3 NPCAI is 6: bears, wolves and the like) fights back when it is hit, as a monster does.
scripts/mobiles/scared_animal.lua (script_id = "scared_animal", NPCAI 12) does not: a scared animal that is hit
stops fighting and runs, up to twelve cells and ten seconds at most, away from whoever hit it, then strolls again. The
combat service makes every hit NPC answer the blow except one that runs: the creature module sets the prop
combat.flees on a creature made with flees = true, at its spawn and again at each think (a tamed creature has it
cleared: it fights when hit, and so does one whose template no longer flees), and the service reads the prop, not the
script id, so any script with that option behaves the same.
common/creature.lua
Section titled “common/creature.lua”scripts/common/creature.lua is what the three scripts above share, taken with
local creature = require("common.creature"). creature.new(options) gives the table a mobile script defines, with its
on_think; the options are hunts (it goes for the players it sees), prey (a function that says who it goes for, in
place of the players and the townsfolk: the guards of the wilds use it), flees (it runs from a
blow instead of fighting back) and flee_at (see below). A creature whose template sets spell_circles also casts, see
common/caster.lua. The pieces of a mind that the guards share with it are functions of the
module: creature.is_given_up(mind, who) and creature.give_up(mind, who, there) (a mobile that could not be reached is
left alone until it moves), creature.forget_given_up(mind) (it forgets that mobile) and
creature.clear_stale_war_mode(serial) (a mobile left in war mode by a script reload).
pet_orders.is_owned(serial) says whether a creature has an owner (tamed or summoned).
Archers. A creature that holds a bow or a crossbow has combat.range of 10 or 8 (Combat):
its chase stops where the prey is within that range and in its line of sight, npc.can_see, faces it and starts the
fight, and the combat service shoots. Too far, it walks until the prey is a step inside its range; within range but
with no line of sight, it comes closer. It does not step back from a prey that comes close: ModernUO’s archers do not
either, unless their constructor asks.
Running when hurt, as ModernUO’s creatures: one that fights (not a scared animal, which runs from a blow) and has
under flee_at percent of its hit points runs with a chance of one in ten at each think, for 10 to 30 seconds,
straight away from its prey, running. The percent is the template’s flee_at, else the option of the script: 20 for a
monster, 10 for an animal; a template with flee_at = -1, as UOX3’s FLEEAT=-1 gives the undead, the elementals and the
daemons, never runs. While it runs it does not answer a blow: it sets the NPC prop combat.passive, which the combat
service reads, and it goes back to strolling, and so to hunting again, when it is over.
Bleeding bites, as the classic Bleed Attack: a creature whose template sets bleed_chance (the percent) makes the
victim of a landed blow bleed, by the bleed module — five ticks of two seconds, the damage slowing down from 5–10 to
1–2 and double for an NPC, a pool of blood under the victim each tick, and the fifth tick heals the wounds. The
bandage binds it. The dire wolf bleeds one blow in five, the grey wolf one in ten.
Calmed by a bard (Peacemaking): the prop bard.pacified_until holds the second (world.now()) the
calm ends. The combat service reads it for any NPC, scripted or not, so a calmed creature answers no blow; while it
holds, creature.lua also stops the creature’s fight and keeps it from hunting, and it only strolls (a creature that has
an owner goes on with its orders, as ModernUO’s calm only keeps a creature from taking a target). When the time is up the
prop is cleared. It outlives a script reload and a restart. A blow breaks the calm with a chance of
(hits_max - hits) * 0.001, one in a thousand for each hit point the creature lacks (on_damage of the table that
creature.new makes: a script that defines its own on_damage on it replaces this).
Provoked by a bard (Provocation): the props bard.provoker, bard.provoke_target and
bard.provoke_until set a creature against another. While they hold, creature.lua makes it chase and fight that
creature and no one else (a fight that someone who hit it began is stopped at each think), without running from a blow.
As ModernUO’s, the provocation ends at once when the target is lost (it left the world, died, is on another map or
farther than the 16 tiles the creature sees), and when the time is up and the bard is lost (gone, on another map or
farther than 16 tiles; a bard that is hidden, or dead, but stays near keeps it going past the 30 seconds). Death clears
the props.
A creature of your own takes it the same way: mycreature = creature.new({ hunts = true }) in
scripts/mobiles/mycreature.lua. A root whose scripts are replaced by hand needs scripts/common/ too, or the creatures
stop thinking.
common/caster.lua
Section titled “common/caster.lua”scripts/common/caster.lua is how a creature whose template sets spell_circles (the highest circle of Magery it casts
from, 1 to 8, UOX3’s SPATTACK) fights, by the rules of the classic non-smart mage. common/creature.lua calls
caster.think(serial, mind, target, distance) at each think of the chase of a creature that is not tamed or summoned;
it returns true when the creature keeps its place. A creature with no spell_circles, or with no Magery, takes no other
path: caster.highest_circle(serial) is 0 and the monster fights as before.
Where it stands. Within 12 tiles of its prey and in its line of sight it stops, faces the prey and casts when its
turn comes; out of range or sight it chases as any monster, and so it does when its mana is under that of a first circle
spell (the creature regenerates it by its Intelligence, as any NPC, and fights in melee meanwhile). Beside the prey it
fights in melee as a monster and still casts when its turn comes. While the delay of a spell runs it does not move
(npc.step is refused and npc.walk_to answers "blocked" at once, with its path kept), and a blow does not disturb it.
The choice. The highest circle is clamp(floor((Magery + 20) / (100 / 7)), 1, 8) and no more than spell_circles.
Poisoned, it casts Cure on itself, even when its prey is out of sight; otherwise, with a chance of a tenth of its Magery
as a fraction, Greater Heal when it lacks more than 50 hit points (Heal when that circle is out of its reach or the mana
is short) and Heal when it lacks more than 10. A wild creature has no wait between heals: the cooldown of the classic
mage, sqrt(600 - Intelligence) seconds, is for the tamed ones, which never cast here. Else one of sixteen: Poison (two, unless the prey is poisoned), a buff on itself (Bless, Strength, Agility or
Cunning), a curse (Curse one time in four from Magery 40, else Weaken, Clumsy or Feeblemind; two), Paralyze (Magery above
50, a prey not paralyzed), Mana Drain (Mana Vampire half the time from Magery 80), and the rest damage: one of twice as
many places as the circles, Magic Arrow, Harm, Fireball, Lightning and Mind Blast twice each, then Energy Bolt, Explosion
and Flame Strike. A spell above the circles it has, or one it has no mana for, is replaced by the highest damage spell
that fits, or no cast; with no spell chosen, or a cast the engine refused, the creature fights as a monster (chases and
swings) and chooses again at its next think. Only spells that hit one mobile are chosen: no summons, fields, Teleport, Recall, Invisibility or
Resurrection.
The pause. After a cast, counted from its start, so that the delay of the spell is part of it, the next comes no
earlier than 6 - 1.25 s to 6 - 0.75 s seconds, s = 3 * Magery / 100: about 2.25 to 3.75 seconds at 100 (never
shorter than the delay of the eighth circle, 2.25 seconds). Thinks are two a second, so it is counted in thinks. An NPC
has no recovery after a cast in the engine: this pause is its only one.
The casting is spell.cast_at (Magery): the spell scripts are the player’s, and
so are the mana and the Magery check (a fizzle); no reagents and no words. caster.random(count) and caster.chance()
are the dice, math.random, which a test replaces.
orione.lua and vega.lua
Section titled “orione.lua and vega.lua”The repository also ships two cats of Moongate v2, orione and vega (templates/mobiles/moongate_cats.toml with scripts/mobiles/orione.lua and vega.lua): spawn them with .spawn orione or .spawn vega.
door.lua
Section titled “door.lua”The distribution also ships scripts/items/door.lua, the script of the decoration_door
template that .decorate gives to doors and gates. Double clicking
a closed door opens it and its linked door (prop door.link): the graphic goes to the next
one, the door swings aside by its facing prop and plays the sound of its
decoration_type (metal, wood, gate or secret). Double clicking an open door closes both
when nobody stands in either doorway. An open door closes by itself after 20 seconds, then
tries again every 10 seconds while the doorway is taken. A door that cannot swing aside, such
as one at the edge of the map, stays closed. An NPC that walks to a place and finds a closed door in its way opens it through on_npc_use(serial, opener): the door and its linked door open and close by themselves as for a player, and a locked door, or a double door with either leaf locked, stays shut without a word. The open state is the prop door.open, with the
closed spot in door.x, door.y and door.z, saved with the door, and so is the auto-close timer (the door’s close timer, started with
item.start_timer): a door left open when the server stops closes once the server is back. A door saved
open by an older version has no timer and stays open until someone uses it. A closed door with the prop locked does not
open for players, who read “That is locked.” (message 398, in the server language), unless
they carry anywhere in their backpack a key whose prop key.value is the door’s key.value
(message 405: they open it and it stays locked); game masters and administrators open it
(message 404). The prop comes from the decoration data
(props = { facing = "west_cw", locked = true }), such as the side doors of the New Haven
bank. .lock gives a door a key number and .key makes its key.
light.lua
Section titled “light.lua”scripts/items/light.lua lights and douses candles, candelabras, lanterns, lamp posts, wall
sconces and torches: the decoration_light template and the light templates of
templates/items use it. Double clicking an unlit light gives it the lit graphic (ModernUO’s
pairs), a light shape if it has none, and sound 0x47; double clicking a lit one gives the
unlit graphic and sound 0x3BE, keeping the shape for the next time. A light without an unlit
graphic, such as a brazier, stays as it is. The lights .decorate places have the prop
protected: only game masters and administrators light or douse them. The town lamp posts
light and douse themselves: every 30 seconds the server calls on_darkness(serial, dark) on a
lamp post whose spot turned dark or light (ultima.world.lamp_post_light), and light.lua
switches its graphic silently.
kindling.lua and campfire.lua
Section titled “kindling.lua and campfire.lua”scripts/items/kindling.lua is the Camping skill, as ModernUO’s Kindling: the skill button does
nothing, the kindling does. A double click on kindling at hand — carried, or lying within 2 tiles —
tries skill.check(user, "camping", 0, 100); a failure reads “You fail to ignite the campfire.”
(501696). On a pass one piece of the stack is spent and a campfire is lit where the kindling lies,
for the kindling on the ground, or on the first side of the player that can be stood on and where
nobody stands (world.standing_z and world.can_fit; the fire takes the height of that floor), and
none is refused with “There is not a spot nearby to place your campfire.”
(501695). Kindling lifted onto the cursor is not used. The fire is the campfire template (0xDE3,
immovable; its script and decays = false were added to the template in
templates/items/building/lighting.toml), lit circle300.
scripts/items/campfire.lua walks the fire to its end, as ModernUO’s, by the named timers the
kindling starts: 60 seconds burning, 30 dying (0xDE9, circle150), 10 out (0xDEA, no light), and
the item is deleted. While it burns or dies it is a source of heat to cook on
(common/heat.lua counts both its graphics among the fires). The bedroll and
the instant logout of the later eras are not built.
food.lua
Section titled “food.lua”scripts/items/food.lua is the script of what can be eaten, as ModernUO’s Food: the converted
food templates carry script_id = "food". Double clicking a piece eats one: the player’s hunger rises
by the item’s prop food.fill (3 without it), 20 at most; it gets 6 to 8 points of stamina back, plus one more for each 5 points of the fill, makes
the sound and, with a body of the Human kind (mobile.body_type), the gesture of eating, and reads how full it feels
in the language of its client (messages 500868 to 500872). A full player reads “You are simply too full
to eat any more!” (500867) and eats nothing. A poison.level prop — what the Poisoning
skill puts there — poisons whoever eats the piece.
drink.lua
Section titled “drink.lua”scripts/items/drink.lua is the script of what can be drunk, as ModernUO’s beverages: the converted
drink templates carry script_id = "drink". Double clicking one drinks a sip: the player’s thirst rises
by the item’s prop drink.fill (3 without it), 20 at most, and it makes the sound and, with a body of
the Human kind, the gesture of drinking. The graphic says how many sips a full container holds: a
pitcher or a bottle 5, a jug 10, a glass or a mug 1; the sips left are kept in the prop drink.uses.
Once empty, a pitcher, a glass or a mug turns into its empty graphic, is renamed and stays; a bottle or a jug
is gone. A quenched player reads “You are simply too full to drink any more!” and drinks nothing.
Refilling, pouring and drunkenness are not there yet.
The lore skills
Section titled “The lore skills”Five skills of the skill window, in scripts/skills/, as ModernUO’s. The player picks a target and reads the
client’s own texts as system messages (ModernUO shows them over the one examined). Each waits the delay of
data/skills.toml. The fifth, Arms Lore, reads an item: arms_lore.lua.
anatomy.lua: a mobile within 8 tiles; the check from 0 to 100 that passes reads how strong and how dexterous it looks, and from 65 points how much endurance it has left. What it reads is off by up to 25 less one for every 4 points of the skill. A failed check reads that it cannot get a sense of its physical characteristics; oneself, an invulnerable NPC and an item have their own texts.evaluating_intelligence.lua: the same for the mind: from 0 to 120, “He”, “She” or “It” (mobile.is_female) and, from 76 points, the mana left; off by up to 20 less one for every 5 points.forensic_evaluation.lua: a corpse within 10 tiles, from 0 to 100: a human corpse tells whom it was killed by (corpse.killerandcorpse.killer_name, “no one” when it was not by someone); that of an animal or a monster reads “You notice nothing unusual.”. A mobile, from 40 to 100, “You notice nothing unusual.”, since there is no thieves’ guild. Who disturbed the corpse and who studied it before are not kept yet.detecting_hidden.lua: a place within 12 tiles, or oneself: every hidden player or NPC within a tenth of the skill in tiles of it (half when the check fails, and none under 10 points) is shown if the detector’s skill plus a roll of -10 to 10 is not under its Hiding plus its own; it reads “You have been revealed!”. Staff are found by staff only; the skill waits 10 seconds. A trapped container picked directly — thetrap.typeprop — is named “[trapped]” (500813), as a label over the chest, when the detector stands within a tenth of its skill in tiles of it and the check from 0 to 100 passes: named, not disarmed (remove_trap disarms). Houses and factions are not there yet.
arms_lore.lua
Section titled “arms_lore.lua”scripts/skills/arms_lore.lua is the Arms Lore skill, as ModernUO’s. on_use asks “What item do you wish to get
information about?” (500349) and gives the cursor: the piece must be the player’s own, carried or worn, or lie
within 2 tiles (what item.in_range says of a ground item); anything else picked says nothing, and a thing that
is neither weapon nor armor reads “This is neither weapon nor armor.” (500352). The check from 0 to 100 that
fails reads “You are not certain…” (500353). A success reads the condition of the piece and then its worth in a
fight, as the client’s own texts:
- The condition, when its maximum is known — the
durabilityandmax_durabilityprops of the piece, at themax_hitsof its template — is the tenth of its hits left (1038285 + it): the ninth of a whole piece, and 1038294 reads “is in perfect condition.” - The worth of a weapon is the middle of its
damage_minanddamage_max(whatitem.combat_statsreads of its template), a tier of every five of it up to thirty and none under three, one more for a two-handed one, over the base of its kind: a sword, an axe or a pole arm slashes (1038220), a mace bashes (1038222), a fencing weapon pierces (1038218), a bow, a crossbow or a thrown weapon is shot (1038224), a weapon with no kind has the plain base (1038216). - The protection of a piece of armor is the fifth of its rating, which the texts stop at 35 (1038295 + it).
Not there yet: the pieces carried or worn by someone else, which the Snooping skill is for (ModernUO reads them
too), the poison of a weapon (Moongate has none to read), the barding of a swamp dragon, and the item
properties of AOS. The skill waits the 1 second of data/skills.toml.
tracking.lua
Section titled “tracking.lua”scripts/skills/tracking.lua is the Tracking skill, as ModernUO’s. on_use takes the arrow of the last tracking away
(arrow.clear), says “What do you wish to track?” (1011350) and opens the gump track_what
(templates/gumps/track_what.xml): animals, monsters, human NPCs or players. The gump is not named tracking because a
gump answers to the script table of its own id, and that name is the skill’s. Its script, scripts/gumps/track_what.lua,
tries skill.check(user, "tracking", 0, 21.1): a failure reads “You see no evidence of those in the area.” (1018092),
and a success tries the skill again from 21.1 to 100 (the passive gain of ModernUO).
A success looks as far as 10 tiles plus 10 for every 10 points of skill (110 at 100 points), through
world.mobiles_in_range in looks of at most 32 tiles. The scan starts at the 10 tiles of the base range and doubles the
box until the list is full of mobiles nearer than its edge, so a crowd close by is never walked through to the end of
a range of a hundred tiles; it goes through 1500 mobiles at most, which keeps it inside the 150000 instructions of one
script resume. It lists the 12 nearest of the kind, nearest first, with the picture of the body (gump.portrait), the name
and the direction (the messages 30264 to 30271 of data/messages). The kinds go by mobile.is_player and
mobile.body_type: an animal, a monster or a human that is not a player, or a player. Not listed: the tracker itself,
the dead, a hidden member of the staff that the tracker does not out rank (only an administrator tracks a hidden game
master, nobody a hidden administrator, as ModernUO’s access level check), and for a player the one whose Hiding and
Stealth are too much for the tracker’s Tracking and Detecting Hidden (the chance is the whole number of 50 * (2 *
tracking + detecting hidden) / (hiding + stealth) against a roll of 0 to 99, ModernUO’s Samurai Empire rule). Nobody of
the kind reads “You see no evidence of animals in the area.” (502991), of creatures (502993) or of people (502995).
A button of the list shows the arrow at the place of that mobile (arrow.show, with its serial for clients from 7.0.9.0)
and tells its distance and direction (message 30272; message 30273 for a mobile on the very tile, which has no
direction). A mobile that left the world, is farther than twice the range, or is a hidden member of the staff out of the
tracker’s reach reads “You have lost your quarry.” (503177) and takes the arrow of the one before away. The arrow is
not tried with a real client; it stays where it was put, the server does not follow its quarry, and the client’s click
on it (to put it away) is not read. The skill waits 10 seconds. The elves needing twice the skill, the changes of the
necromancer’s forms and the stalking bonus are not there.
instrument.lua and common/bard.lua
Section titled “instrument.lua and common/bard.lua”scripts/items/instrument.lua is the item script of the nine musical instruments (the drum, the two tambourines, the
standing and the lap harp, the two lutes and the two bamboo flutes; script_id = "instrument", which the UOX3 converter
gives by graphic), as ModernUO’s BaseInstrument. A double click on one that is carried, or within a tile, plays it: it
rolls Musicianship (skill.check(user, "musicianship", 0, 120), which teaches the skill) and the playing is good with
a chance of the skill in a hundred, with the sound of that instrument by its graphic (the drum 0x38 or 0x39, the
tambourines 0x52 or 0x53, the standing harp 0x43 or 0x44, the lap harp 0x45 or 0x46, the lutes 0x4C or 0x4D, the bamboo
flutes 0x504 or 0x503). Farther away it reads “That is too far away.” (500446), and the same player waits 6 seconds
before it plays again (“You must wait to perform another action.” 500119). Playing by hand takes no use of the
instrument, as ModernUO’s; the bard skills do: an instrument rolls 350 to 450 uses at its first use in a skill (the item
prop instrument.uses), each roll of a skill takes one, and at the last one “The instrument played its last tune.”
(502079) is told and the instrument is deleted.
scripts/common/bard.lua is what the bard skills share. The instrument a player plays is the one it double clicked
last, for as long as it is in the backpack or a bag of it (not the bank box, and not lifted onto the cursor); otherwise the skill asks “What instrument
shall you play?” (500617) and takes the one picked (500619 for what is no instrument, 1062488 for one that is not in the
pack). The music reaches 8 + skill / 15 tiles, with the points of the skill that is played (Peacemaking for
Peacemaking, not Musicianship). The difficulty of a creature, as ModernUO’s GetBaseDifficulty, is
hits * 1.6 + stamina + mana + skill points (the plain sum of the points of its skills), compressed above 700 to
700 + (value - 700) * 3 / 11, plus 100 for a creature with spells, divided by 10 and at most 160; ModernUO’s terms for
a fire breath, a poison immunity, the strength of a poison and a paragon are not there. A roll against it is the skill
between diff - 25 and diff + 25, with diff lowered by half of the Musicianship above 100: the duration of what the
roll gives is counted from that same number. Vendors, bankers, healers, stable masters and guards cannot be charmed.
Instruments and bard skills are not tried with a real client; slayer and exceptional instruments, crafting
instruments and bards among the NPCs are not there.
peacemaking.lua
Section titled “peacemaking.lua”scripts/skills/peacemaking.lua is the Peacemaking skill, as ModernUO’s. on_use reveals the bard, takes the
instrument (see above), asks “Whom do you wish to calm?” (1049525) and a target. The instrument must still be in the
pack when the cursor answers (1062488 otherwise), and a bard who died meanwhile does nothing. Itself is the area
form: Musicianship is rolled (a poor playing reads “You play poorly.” 500612), then the skill from 0 to 120 (“You attempt
to calm everyone, but fail.” 500613). A success stops the fight and the war mode of every mobile in range except the
bard, the dead, the invulnerable and those that cannot be charmed, tells the players “You hear lovely music, and forget
to continue battling!” (500616) and calms the creatures for one second (500615 when something was in range, a creature
already calm among it, or 1049648 when nothing was). One target that is no mobile, or is dead or invulnerable, reads
1049528; farther than the range, “That is too far away.” (500446); out of sight, “Target can not be seen.” (500237); a vendor or guard, 1049526; a creature already calm,
1049527. Musicianship is rolled first, then the skill from diff - 25 to diff + 25 with diff the difficulty of the
target less 10 (for a player too, from its stats and skills), lowered as above; a failure reads 1049531 and a success
1049532, and a creature is calmed for clamp(100 - diff / 1.5, 10, 120) seconds (see
common/creature.lua). A player only has its fight stopped. Every roll takes a use of the
instrument and plays the good or the bad sound. The skill waits 10 seconds (delay of data/skills.toml) from the
press of the button, even if the cursor is put away; ModernUO’s 5 seconds after a success are not there, nor the safe
zones.
provocation.lua
Section titled “provocation.lua”scripts/skills/provocation.lua is the Provocation skill, as ModernUO’s. on_use reveals the bard, takes the instrument
(see above), asks “Whom do you wish to incite?” (501587) and a creature, then “Whom do you wish them to attack?”
(1008085) and a second one. The first must be a creature that can be harmed, within the range and in sight (501589
otherwise, “That is too far away.” 500446, “Target can not be seen.” 500237): a blue townsman only says “Think not that I
fail to see what thou art doing!” (501591) and costs the bard 75 karma through common/karma.lua,
and nothing else happens; one that has an owner reads 501590; otherwise the instrument is heard well played at once and
takes no use. The second is refused when it is no creature (501589), when either one cannot be charmed (vendors,
bankers, healers, stable masters, guards) or runs none of the scripts built on common/creature.lua (monster,
animal, scared_animal: 1049446), when they are on another map or farther from each other than the range (1049450),
when it is the first one (501593), and when it is farther from the bard than the range or out of its sight (500446,
500237). Musicianship is rolled first (500612), then the skill from diff - 25 to diff + 25, with diff the mean
difficulty of the two less 5, lowered by half of the Musicianship above 100 (failure 501599, success 501602). A success
takes a use and sets both creatures against each other: the props bard.provoker, bard.provoke_target and
bard.provoke_until (world.now() plus 30), and a calm is over (see common/creature.lua).
Players cannot be provoked. The skill waits 10 seconds from the press of the button. The kills of the provoked are
their own: the bard gets no credit, and the paragons are not there.
discordance.lua
Section titled “discordance.lua”scripts/skills/discordance.lua is the Discordance skill, as ModernUO’s in the form it has without resistances (Moongate
has none). on_use reveals the bard, takes the instrument (see above), asks “Choose the target for your song of
discordance.” (1049541) and a target; a click on the ground does nothing. The instrument must still be in the pack when
the cursor answers (1062488). The bard itself and what is no mobile read “A song of discord would have no effect on
that.” (1049535); farther than the range, “That is too far away.” (500446); out of sight, “Target can not be seen.”
(500237). A player, dead or not, is only played at, badly: no roll, no use, no words. A dead or invulnerable creature, a
vendor, banker, healer, stable master or guard read 1049535, and one already in discord 1049537. Musicianship is rolled
first (“You play poorly, and there is no effect.” 500612), then the skill from diff - 25 to diff + 25, with diff
the difficulty of the creature less 10, lowered by half of the Musicianship above 100 (failure 1049540, success
1049539). A success takes a use and puts a curse on the creature’s strength, dexterity and intelligence: a percent of
each base value, as many percent as a fifth of the points of Discordance (24 percent at 120 points, 20 at 100). The
curse (mobile.add_stat_curse with refresh) lasts 4 seconds and a timer of the discord renews it every 2 seconds. The
discord holds while the bard is alive, not hidden, on the creature’s map and within the range of its music, measured in a
straight line; 15 seconds after that stops being true (the count starts over when the bard comes back), or at once when
the creature dies or goes, it ends and the last curse is gone within 4 seconds. The skill waits 12 seconds from the press
of the button, whatever the outcome. On a creature only the strength weighs on its blows (its hits, stamina and mana do
not follow its stats), and ModernUO’s lowering of the creature’s skills is not there, nor the effect it shows on the
creature.
spirit_speak.lua
Section titled “spirit_speak.lua”scripts/skills/spirit_speak.lua is the Spirit Speak skill, as ModernUO’s before AOS. on_use shows the player first
(mobile.set_hidden, as using a skill does in ModernUO) and tries skill.check(user, "spirit_speak", 0, 100): a
failure reads “You fail your attempt at contacting the netherworld.” (502443), a success plays the sound 0x24A, reads
“You establish a connection to the neitherworld.” (502444) and keeps, in the mobile’s spirit_speak.until prop, the
world.now() second until which the player understands the dead: a minute and a half for every 50 points of skill,
in its whole seconds, the least 15 (18 at 10 points, 90 at 50, 180 at 100), as ModernUO’s base / 50 * 90.
What the prop does is in the speech handler: a ghost’s words are sent as they were spoken, and not as “oOo”, to the
dead, to the staff, to a living player whose spirit_speak.until is still in the future, and to one whose base
Spirit Speak is 100 or more, who understands the dead for good without using the skill. A success pushes the prop
further; a failure leaves the time it had. The skill waits
the 1 second of data/skills.toml. The heal of AOS (10 mana, free near an un-channeled corpse), the reveal of
hidden spirits and the racial languages of UOX3 are not built; nothing sends a “the connection fades” message when
the prop runs out.
animal_lore.lua
Section titled “animal_lore.lua”scripts/skills/animal_lore.lua is the Animal Lore skill: on_use says “What animal should I look at?” (500328), gives a
cursor, and refuses a creature out of reach or sight (500446, 1049654), one that is not an animal (mobile.body_type; a dead creature has left the world) and, by the skill, a creature that is not
tamed under 100 points or not tameable under 110. The check is skill.check(user, "animal_lore", 0, 120); a pass builds a
two-page gump with gump.create from pet.lore, mobile.stats and mobile.skills, all labels clilocs of the client. See
Animal taming.
pickaxe.lua and ore.lua
Section titled “pickaxe.lua and ore.lua”scripts/items/pickaxe.lua is the script of the pickaxes and the shovels (script_id = "pickaxe") and
scripts/items/ore.lua that of the four piles of iron ore (script_id = "ore"): see Mining and smelting.
A dig picks a place (target.pick_location), which gives the land of the cell and the graphic of a static picked
there: the script holds the lands that are rock and the statics that are a cave floor. The character swings
(mobile.animate, mobile.play_sound, timer.after), the place must have ore left (harvest.amount), the Mining
skill is tried between the bounds of the place’s metal (skill.check; 0 and 100 for iron), and a dig that works takes from the place (harvest.take) and gives
a pile (item.give). A smelt picks a forge, an item (item.item_id, item.in_range) or a static, tries the skill
between 25 and 75, and turns the pile into ingots (item.consume, then item.give) or burns half of it away; a single iron ore
that fails gets smaller, and a metal above the miner’s skill is refused without a try. A pile on a cursor is refused (item.is_held). The metal of a place is the vein of its area
(harvest.vein): scripts/common/metals.lua holds the ore and ingots of each metal, the Mining it asks for, the bounds of a dig
and the difficulty of a smelt. The constants at the top of each script are its numbers and its lists.
axe.lua
Section titled “axe.lua”scripts/items/axe.lua is the script of the axes (script_id = "axe" on nine axe bases, which their axes take from
their base): see Lumberjacking. The axe must be in the hands of who double clicks it
(item.worn_by). The place picked must be a tree within 2 tiles: target.pick_location gives the graphic of the
static that was picked, and the script holds the graphics that are trees. The character swings one to three times
(mobile.animate, mobile.play_sound, timer.after), the place must have wood left (harvest.amount), the
Lumberjacking skill is tried between the bounds of the kind of wood of the place (skill.check; see below), and a cut that works takes from the place
(harvest.take) and gives 10 logs (item.give). The constants at the top of the script are the range, the swings
and the logs; the trees are in scripts/common/trees.lua, shared with scripts/items/blade.lua, the script of the knives, daggers and swords (script_id = "blade"), which hacks one kindling off a tree and carves corpses (see blade.lua and corpse.lua). Picked onto logs in the backpack, the axe saws the stack into boards (item.template, item.consume, then item.give). A place is of one kind of wood, the vein of its area (harvest.vein): scripts/common/woods.lua holds the logs and boards of each kind, the Lumberjacking it asks for (mobile.skills) and the bounds its cut is tried between, shared with carpentry, and the table FINDS what a master finds with the logs. Who is chopping is kept in memory by serial: a restart frees everyone.
blade.lua and corpse.lua
Section titled “blade.lua and corpse.lua”scripts/items/blade.lua is the script of the knives, daggers and swords (script_id = "blade"). A player double
clicks one it carries and picks, within 2 tiles, a tree (target.pick_location; one kindling is hacked off it, at the
cost of a cut of its wood) or a corpse, which must also be within one storey in height. A corpse is carved by
carve.corpse(corpse, user): the table the corpse kept in corpse.carve is rolled, on_carving is asked of the
corpse’s script, the items are made inside the corpse, and the corpse is marked corpse.carved; a corpse is carved
once, and is shown to the carver again with what was found (item.show_contents). Nothing useful, or a corpse already
carved, says the cliloc 500485; a player’s corpse, a pet’s, or one its script refused, says 500494. See
Carving.
scripts/items/corpse.lua is the script of the corpse template and defines no hook of its own: it is where a script of
your own defines on_carving to cancel or change what a knife takes.
crafting.lua and carpentry_tool.lua
Section titled “crafting.lua and carpentry_tool.lua”scripts/common/crafting.lua holds the rules every craft shares: see Carpentry. It reads the
recipes with craft.get and the resource lists with craft.resource, counts and takes the resources across the stacks
the player carries (item.find, item.amount, item.consume; a pile on the cursor is left out with item.is_held),
plays the two strokes (mobile.play_sound, timer.after), tries the other skills of the recipe and then the main one
between twice its least minus its most and its most (skill.check), so the chance is one in two at the least, and
makes the item (item.give, else item.create at the player’s feet), with the hue of the kind of wood or metal
picked (passing it to item.give, or item.set_hue at the feet): the engine’s table MATERIALS ties the resource wood
to scripts/common/woods.lua and metal to scripts/common/metals.lua, and the kind picked is kept by craft. A success may be exceptional (crafting.roll, the props quality, crafter_id, crafter_name), and every attempt whose skill is tried takes a use of the tool (the prop uses_remaining, drawn 25 to 75, item.delete at the last). Who is making something, the group and the kind of wood or metal each player picked, by craft, and the last recipe each started (crafting.make_last) are kept in memory.
scripts/items/carpentry_tool.lua (script_id = "carpentry_tool" on the carpentry tools) opens the crafting gump
from the backpack; the gump is templates/gumps/craft_menu.xml with scripts/gumps/craft_menu.lua, one for every craft.
Every tool script of a craft is a table with the id of its craft and one line, crafting.use_tool(user, serial, CRAFT):
a tool that is not carried is refused with the client’s text, a new one draws its uses and the gump opens. A craft of
your own needs only that line.
repair.lua
Section titled “repair.lua”scripts/common/repair.lua, used by the crafting gump: repair.mends(craft_id) says whether a craft has the Repair item
button, repair.start(user, craft_id) gives the player a cursor and mends the piece picked, repair.mend(user, craft_id, serial) is one attempt (true when the piece was mended) and repair.roll() is the draw of the weaken chance.
It reads and writes the hits with item.durability and item.set_durability, and finds the pieces the craft makes in
the recipes (craft.get). See Repair for the rules.
smithing_tool.lua and common/smithy.lua
Section titled “smithing_tool.lua and common/smithy.lua”scripts/items/smithing_tool.lua is the script of the smith’s hammers, sledge hammers and tongs
(script_id = "smithing_tool"): it opens the crafting gump of blacksmithing (see Blacksmithing),
the rules being those of crafting.lua. scripts/common/smithy.lua holds the graphics of the anvils and forges and
finds them within a range of a player, among the statics of the map (world.statics) and the ground items
(world.items_in_range, item.item_id); the engine’s table NEEDS asks blacksmithing for an anvil and a forge within
2 tiles, and ore.lua reads its forges from there.
tailoring_tool.lua
Section titled “tailoring_tool.lua”scripts/items/tailoring_tool.lua is the script of the sewing kits (script_id = "tailoring_tool"): from the backpack it
opens the crafting gump of tailoring (see Tailoring), with the rules of crafting.lua.
tinkering_tool.lua
Section titled “tinkering_tool.lua”scripts/items/tinkering_tool.lua is the script of the tinker’s tools and tool kits (script_id = "tinkering_tool"):
from the backpack it opens the crafting gump of tinkering (see Tinkering), with the rules of
crafting.lua and the metal picked.
fletching_tool.lua
Section titled “fletching_tool.lua”scripts/items/fletching_tool.lua is the script of the fletcher’s tools (script_id = "fletching_tool"): from the
backpack it opens the crafting gump of bowcraft and fletching (see Bowcraft and fletching), with the
rules of crafting.lua and the wood picked.
cooking_tool.lua
Section titled “cooking_tool.lua”scripts/items/cooking_tool.lua is the script of the skillets, flour sifters and rolling pins
(script_id = "cooking_tool"): from the backpack it opens the crafting gump of cooking (see Cooking),
with the rules of crafting.lua and the ovens and fires of scripts/common/heat.lua.
map_item.lua
Section titled “map_item.lua”scripts/items/map_item.lua is the script of the maps (script_id = "map_item"): from the backpack or the ground
within 2 tiles it opens the map on its area with the map module (see Maps); farther away it says
“That is too far away.”.
cartography_tool.lua
Section titled “cartography_tool.lua”scripts/items/cartography_tool.lua is the script of the pens (script_id = "cartography_tool"): from the backpack
it opens the crafting gump of cartography (see Cartography); a map made is drawn by
scripts/common/cartography.lua.
inscription_tool.lua
Section titled “inscription_tool.lua”scripts/items/inscription_tool.lua is the script of the pen and ink (script_id = "inscription_tool"): from the
backpack it opens the crafting gump of inscription (see Inscription). scripts/common/crafting.lua
asks the book and the mana of a recipe with the spell and mobile modules.
potion.lua
Section titled “potion.lua”scripts/items/potion.lua is the script of the potions a player drinks (script_id = "potion"): heal, refresh,
strength, agility and night sight, chosen by the template in its table EFFECTS; it checks the reach and a free hand,
and leaves an empty bottle (see Potions).
alchemy_tool.lua
Section titled “alchemy_tool.lua”scripts/items/alchemy_tool.lua is the script of the mortar and pestle (script_id = "alchemy_tool"): from the backpack
it opens the crafting gump of alchemy (see Alchemy). scripts/common/potions.lua tells which potion an
item is, the plain potions vendors sell included, for potion.lua and explosion_potion.lua.
test_kit.lua
Section titled “test_kit.lua”scripts/items/test_kit.lua is the script of the staff’s test bags (templates/items/test_kits.toml,
script_id = "test_kit"): .add test_kit_alchemy gives a bag that fills, the first time it is opened, with the tool, the
materials and the items to try a craft, the potions or the spells of Magery; then it is a plain bag. The bags are
test_kit_alchemy, test_kit_potions, test_kit_explosion, test_kit_cartography, test_kit_tailoring,
test_kit_tinkering, test_kit_fletching, test_kit_cooking, test_kit_inscription (a pen and ink, 100 blank scrolls, 50 of each reagent and a full spellbook) and test_kit_magery (a full spellbook, 20 of each
reagent, three scrolls of each built spell of the first four circles and two recall runes); the table KITS says what each holds. The skill is set apart, as
.set skill alchemy 100.
spellbook.lua and spell_scroll.lua
Section titled “spellbook.lua and spell_scroll.lua”scripts/items/spellbook.lua is the script of the spellbooks (script_id = "spellbook"): a double click opens the book
when the player wears it or carries it in its backpack, else it says the book must be carried. A scroll dropped on the
book is written in it by the server. scripts/items/spell_scroll.lua is the script of the scrolls of the spells
(script_id = "spell_scroll"): a double click casts the spell of the scroll, which must lie in the backpack. Both only
call the spell module; see Magery.
The spell scripts and common/magic.lua
Section titled “The spell scripts and common/magic.lua”scripts/spells/<key>.lua is the script of a spell, a global table named after the key of
spells.toml. cast(caster, target, info) is the effect, called once the cast succeeded;
check(caster, target, info) may refuse before anything is spent by returning a cliloc number or a text. The target is
{ kind = "mobile" | "item", serial }, { kind = "location", map, x, y, z } or { kind = "none" }, and info the data
of the spell with scroll true for a cast from a scroll. The scripts built are the first circle (clumsy,
create_food, feeblemind, heal, magic_arrow, night_sight, weaken), reactive_armor, the second circle
(agility, cunning, cure, harm, magic_trap, magic_untrap, protection, strength), the third (bless,
fireball, magic_lock, poison, teleport, telekinesis, unlock, wall_of_stone) and the fourth (arch_cure,
arch_protection, curse, fire_field, greater_heal,
lightning, mana_drain, recall), the fifth (blade_spirits, dispel_field, incognito, magic_reflection,
mind_blast, paralyze, poison_field, summon_creature), the sixth (dispel, energy_bolt, explosion,
invisibility, mark, mass_curse, paralyze_field, reveal), the seventh (chain_lightning, energy_field,
flame_strike, gate_travel, mana_vampire, mass_dispel, meteor_swarm, polymorph) and the eighth (earthquake,
energy_vortex, resurrection, summon_air_elemental, summon_daemon, summon_earth_elemental,
summon_fire_elemental, summon_water_elemental); a spell without a script says it is disabled.
The spells of Necromancy — the school without circles — are scripts too, each carrying its own mana, least skill and
delay in spells.toml (a mana, required_skill, cast_skill, scale_skill and cast_delay that win over the circle
tables): cast by Necromancy, scaling by Spirit Speaking, with the reagents of the necromancers and the spells the
client numbers from 100. Sixteen of the seventeen are built (animate_dead, blood_oath, curse_weapon, evil_omen,
exorcism, horrific_beast, lich_form, mind_rot, pain_spike, poison_strike, strangle, summon_familiar,
vampiric_embrace, vengeful_spirit, wither, wraith_form): the wounds of Pain Spike and
Poison Strike, the rounds of Strangle, the three the fight reads by props — a Blood Oath makes the caster’s damage
against the one it is bound to a fifth worse and sends the wound back, a Mind Rot makes the cursed one’s spells cost a
quarter more, a cursed weapon drinks half of its wounds back — and the four forms of common/necromancy.lua, a body
each taken by mobile.disguise for a week and ended by a second cast of the same form, another form, a death or the
week: the Horrific Beast (746), the Lich (749) that pays one hit every two and a half seconds for its body, the Vampire
(745 for a woman, 744 for a man, hue 0x847E) that no poison takes, and the Wraith (747 for a woman, 748 for a man) that
drinks 5 to 20 percent of its wounds back as mana. The three summons rise their creatures on the follower system
(common/summon.lua): Animate Dead points a corpse and rises an undead of the ladder of the necromancers by the
caster’s Necromancy and Spirit Speaking two seconds later, a body that decays hit by hit and of which the fourth
dismisses the oldest; Summon Familiar opens the list of the five familiars, one at a time beside the caster for a day;
Vengeful Spirit sends a revenant, a summon that hears no word of a pet, against one victim until it is dead or its time
is up. The Evil Omen curses its target for one harmful event: the next wound a quarter worse, the next poison one level
higher, the first of the two spending it; the Wither is a frost around the caster, five tiles of the valid indirect
targets, each hurt the more the worse its karma is. The Exorcism, at last, asks a grandmastery of Spirit Speaking and a
caster inside a champion spawn, and carries every dead player of the altar’s reach to the shrine nearest to each — the
champions Lua module over the spawn service; Corpse Skin is out for good, resist offsets only. The necromancer’s book (0x2253) opens like any spellbook; its
contents go by the newer packet, and its scrolls drop into it as the magery ones do.
The four spells of the lock and of the trap read the chest’s props, on a container: Magic Trap arms a magic trap of
power 1 (502942 for a thing that is not a container, a fizzle for a trap of another kind), Magic Untrap spends a magic
trap (502373 for a thing that is not trapped, a fizzle for a trap of another kind), Magic Lock sets locked with a
lock.magic prop — a lock no lockpick opens, Unlock alone does (501762 for a thing that is not a container or one
already locked, and 501763 on the cast) — and Unlock opens a lock of the caster’s Magery times 0.8 less 4 points
(501666 for a thing that is not a container, 503101 for one that is not locked, 503099 for one above it, the mana
spent).
scripts/common/magic.lua is what they share: the chance a target resists, the damage scalar, the curse and the buff
with their time, the armor of a Protection, the refusal of a heal, the effect and the sound of a spell, and, for the
spells that hit a place, who they may hit (valid_indirect, indirect_targets), the damage that is done later
(harm_after), the chance of a Dispel, and aggress, which makes the caster the aggressor of the target and knows the
spell that Magic Reflection turned back (info.reflected, info.reflector): the caster is then its own target and hurts
itself with no crime. scripts/common/field.lua puts down the line of pieces of a field: field.cast(caster, target, info, options) plays the sound, puts the line down and shows a puff over each piece, with options.kind (a key of field.kinds:
energy, fire, paralyze, poison, stone) in place of the two templates; field.is_field(template) is what Dispel
Field undoes.
A spell script keeps only what is its own (its numbers, its texts, its extras) and calls the rest. A check that only refuses
a dead target is X.check = magic.check_alive (a heal, magic.check_healable; a field, field.check): the spell host finds
check as a member of the spell’s table, so a function assigned there is the same as one written in place. The other
helpers of magic.lua: sound_on and sound_at (the sound of the spell, nothing for a spell with none), select_in_range
(the mobiles near a place for which a function says yes), refuse_indirect (the guarded town, then no one to hit),
refuse_weight and refuse_landing (nothing to stand on, a full place, and the travel rule of the region, each with its
own text as a parameter), refuse_active (a time prop still to come), refuse_disguised and disguise (Incognito and
Polymorph), heal, cure_chance and paralyze_seconds; magic.curse takes a stat or a list of them. A harmful spell that ruins what its target was doing (Curse and the stat curses, Mana Drain, Mana Vampire, Poison) begins
with magic.begin_assault(caster, who, info): aggress, then the target’s own cast is disturbed and its paralysis ended,
and false, with nothing done, for a target that cannot be harmed (Paralyze, which ends no paralysis and disturbs only after
the resist, does not use it; Mind Blast and the damage spells only aggress). magic.resist_and_tell(caster, who, info, percent) rolls the resist of the spell’s circle
(percent is a chance of the spell’s own, as Mana Drain has) and tells a target that resists; what the spell does of its
own to it stays in the script. The three bolts (Magic Arrow, Fireball, Energy Bolt) are one call,
magic.bolt(caster, who, info, least, most, delay, rand): aggress (nothing else, and no dice, for a target that cannot
be harmed), the projectile of the spell flying from the wearer of a reflection, or the caster, to the target, the sound on
the caster, then rand(least, most) (the spell’s own random) is rolled, resisted and scaled by magic.damage, and the
harm lands delay seconds later. A spell that hurts many, or does its steps in another order (Lightning, Harm, Flame
Strike, Explosion, Meteor Swarm, Chain Lightning), keeps its own cast. A spell that
reads or marks a recall rune takes scripts/common/rune.lua: rune.place(serial), rune.mark(serial, here) and
rune.in_pack(caster, serial). A root whose spell scripts are replaced by hand needs common/rune.lua with Recall, Gate
Travel and Mark.
The texts the spells say are numbers of the client, kept in the table magic.cliloc (WONT_WORK, BLOCKED, IN_EFFECT,
RESISTING, CANNOT_DISPEL, CURED_TARGET, NOT_ALLOWED, TOO_ENCUMBERED, CRIMINAL, FIZZLE): a spell script says
magic.cliloc.WONT_WORK, never the number. The sound, the effect and the projectile of a spell are those of
data/spells.toml (info.sound, info.effect, info.projectile); a script keeps no value of its own for them.
The cliloc module
Section titled “The cliloc module”The numbers of the client’s texts that many scripts say are constants of the cliloc module, which the server’s C#
code reads from the same source: cliloc.TOO_FAR (500446), cliloc.CANNOT_SEE (500237), cliloc.DEAD (1019048),
cliloc.IN_BACKPACK (1062334), cliloc.USE_ON_WHAT (1010018) and cliloc.NOT_CARRIED (1042010). Pass one to
mobile.message_cliloc, as mobile.message_cliloc(user, cliloc.TOO_FAR). A number only one script says stays in that
script; the spells’ own are in magic.cliloc. The list is in the
Lua API reference.
common/summon.lua
Section titled “common/summon.lua”scripts/common/summon.lua is what the spells that call a creature share:
summon.create(caster, template, place, seconds, sound, uncontrollable) asks for the creature with npc.spawn and, once
it is there, makes it a follower of the caster (the prop owner, the pet order guard), sets summon.until,
summon.difficulty and summon.focus, and starts the timer that sends it away; with uncontrollable it gets the prop
pet.uncontrollable, and common/pet_orders.lua lets it hear no word and take no food (a blade spirit, an energy vortex). summon.refuse and summon.refuse_template say, with the cliloc 1049645, that the followers of the caster
have no room for it (pet.followers, pet.slots_of). The whole spell of a creature called beside the caster is
summon.check_near(caster, slots_or_template) and summon.cast_near(caster, info, template, options) (the elementals, the
daemon, Summon Creature), and the one of a creature called at the picked place is summon.check_at and summon.cast_at
(Blade Spirits, Energy Vortex): each script keeps its template, its seconds and its extras. summon.tick is called by common/creature.lua on every think of
a creature: it sends away one whose time is up or whose master is dead, gone or let it go. summon.dismiss takes it away
in a puff (effect.at, npc.delete); a Dispel uses it. A summoned creature that dies leaves no corpse (the on_death of
creature.lua). See Magery.
magic_field.lua
Section titled “magic_field.lua”scripts/items/magic_field.lua is the script of the pieces of the fields (script_id = "magic_field", templates in
templates/items/magic/fields.toml): the item timers expire and tick end a piece when its field.until is reached,
and a piece with field.damage burns, with combat.harm and the caster as the attacker, whoever steps onto it
(on_move_over, on_npc_move_over) or stands in it at each tick, once a second. A try of Resisting Spells lessens the
burn to one point; an invulnerable or a dead mobile is left alone. A piece with field.effect = "poison" poisons, at the
level of field.power, and one with "paralyze" freezes for field.power seconds (mobile.paralyze) whoever steps onto
it, when the caster is in the game and may harm them (magic.valid_indirect, which spares the caster’s party), the caster being their aggressor. The
fields of energy and the wall of stone have no effect but their weight. See Magery.
fishing_pole.lua
Section titled “fishing_pole.lua”scripts/items/fishing_pole.lua is the script of the fishing poles (fishing_pole, fishing_pole_2,
script_id = "fishing_pole"): see Fishing. Double click the pole and pick water within 4 tiles and
in sight (target.pick_location, world.is_water, world.line_of_sight). The character casts (mobile.animate),
the water splashes 1.5 seconds later (effect.at, world.play_sound) and the result comes after 8 seconds
(timer.after). The place must have fish left (harvest.amount), the Fishing skill is tried between 0 and 100
(skill.check), and a catch is given into the backpack (item.give) and taken from the place (harvest.take).
The constants at the top of the script are the range, the seconds and what comes out. Who is fishing is kept in
memory by serial: a restart frees everyone.
bandage.lua
Section titled “bandage.lua”scripts/items/bandage.lua is the script of the clean bandage (clean_bandage, script_id = "bandage"),
as ModernUO’s classic Healing. Double click it, pick who it is for and wait: the one picked is healed, or raised.
- Reach: the bandage in the backpack, and who it is for within 1 tile; farther, the client’s “too far away”. The healer must see who it is for: a hidden one or one behind a wall “can not be seen”. Using a bandage reveals a hidden healer. The bandage is taken out of the stack when the healing begins. A second bandage of the same healer replaces the first.
- The wait: 3 seconds for a healer with 100 dexterity or more, 4 from 40, 5 under it; 5 more to raise a ghost; 9.4 + 0.6 × (120 − dexterity) / 10 on itself. The healer has to stay within 1 tile and alive, or the healing is lost with its bandage.
- A living one that is hurt: it works with a chance of (Healing + 10) %, and heals from Anatomy / 5 + Healing / 5 + 3 up to Anatomy / 5 + Healing / 2 + 10 points; a roll under 1 heals 1 and says the bandages barely helped. A creature with the body of a monster or an animal is a case for Veterinary and Animal Lore, with a point more per 100 of its hit points. One that is not hurt reads “That being is not damaged!” and keeps the bandage — unless its poison is at work, which is a case for the cure below.
- A poisoned one: cured, not healed — no hit point comes back. The bandage begins even at full hits. The healer reads “You finish applying the bandages.”, then the cure is rolled behind a gate of 60 points of both of the healer’s skills, as a chance of (the main skill − 30) / 50 less a tenth of a point per level of the poison: a failed roll reads “You have failed to cure your target!” (1010060), a passed one cures — “You have cured the target of all poisons.” (1010058) to the healer unless it cures itself, and “You have been cured of all poisons.” (1010059) to the patient.
- A bleeding one: bound — the wounds close and no hit point comes back, and no skill is asked. The healer reads “You bind the wound and stop the bleeding” (1060088) and the patient that its bleeding wounds have healed (1060167). A poisoned patient is cured first, and a bleeding one is exempt from “That being is not damaged!” too.
- A ghost: it needs 80 points of Healing and of Anatomy and a chance of (Healing − 68) / 50; then the ghost is asked in the gump of the ankhs whether to come back, and it costs a tenth of its fame, as at an ankh.
- A bonded pet’s corpse: it needs 80 points of Veterinary and of Animal Lore and a chance of (Veterinary − 68) / 50, the wait is the one of a ghost (5 seconds more), and the owner must be the healer or within 3 tiles of the corpse; the pet is born again where the corpse lies, with its owner, loyalty and bond, and 10 hit points. The corpse of a pet that was not bonded cannot be bandaged.
- Skills: both are tried for a rise after a healing, whether the roll worked or not, and after a raise that worked.
There is no poison and no bleeding in the game yet, so there is no cure; and the ground where a ghost is raised is not checked, as it is not at an ankh.
stealth.lua
Section titled “stealth.lua”scripts/skills/stealth.lua is the script of Stealth, as ModernUO’s classic one. A hidden player uses the skill and,
if the check passes, may take a few steps without being shown: a tenth of its Stealth in steps, at least one
(mobile.set_stealth_steps; the server counts them in MoveRequestPacketHandler). Running always shows the player.
Hiding or being shown again clears the steps.
- Before the check: a player that is not hidden is told to hide first (502725); one with under 80 points of Hiding
is “not hidden well enough” (502726), and one whose armor rating (
combat.armor_rating) is 26 or more “could not hope to move quietly” (502727): both are shown. - The check runs from -20 to 80 points, each raised by twice the armor rating. A success reads “You begin to move quietly.” (502730); a failure reads “You fail in your attempt to move unnoticed.” (502731) and shows the player. The skill waits 10 seconds either way.
- Mounted: a rider is refused with “You cannot stealth while mounted.” (500837).
- Not there yet: the rules of Stealth of the later versions (the steps cost by armor).
snooping.lua
Section titled “snooping.lua”scripts/skills/snooping.lua is the script of Snooping, as ModernUO’s. It is not used from the skill window: a double
click on the backpack of another mobile does not open it, the server calls on_snoop(user, owner, container) of this
script (ISkillScriptService.Call). It does so for the backpack and for a bag inside it, and not for a dead player.
- Staff snoops anyone, always: a game master or an administrator needs no distance, no skill, no rule, loses no
karma and is not noticed, and may snoop a dead owner and another staff member. The
hidecommand hides it first. - Rules for the others: within a tile of the owner; nothing for a dead owner; a game master or administrator cannot be snooped, nor an invulnerable player (“You cannot perform negative acts on your target.”; Moongate has no rules of harmful acts by map yet, which this stands in for). An NPC in a guarded region of another map than Felucca is snooped only when it is not human, or attackable, or a murderer: ModernUO’s comment says so, though its code lets anyone snoop an NPC in an active town.
- A player who is not staff loses 4 karma, as ModernUO’s
AwardKarmatakes a loss (more from a good name, nothing under -400, never under -15000, and the loss is told), and is noticed by the players within 8 tiles (“You noticeattempting to peek into ’s belongings.”): always under 100 points of Snooping, with a chance of the points in a hundred of passing unnoticed. - The check runs from 0 to 100 points: a success opens the backpack on the client of the player (
item.show_contents); a failure reads “You failed to peek into the container.” and shows the player, more likely the less it has of Hiding. Staff always see. - Not there yet: the traps of containers. The items seen cannot be lifted: a lift of an item the player does not own is refused as before.
stealing.lua
Section titled “stealing.lua”scripts/skills/stealing.lua is the Stealing skill, as ModernUO’s in its classic mode. on_use asks “Which item do you
want to steal?” (502698) with both hands free (“Both hands must be free to steal.” 1005584; a shield counts,
mobile.hands_empty) and reveals the thief. The target is an item inside a container, or a mobile, which gives an item
of its backpack at random (an empty backpack gives nothing). The checks, in this order: an item of a player’s, the thief
not of the thieves guild, 1005596 “You must be in the thieves guild to steal from other players.”; a guild thief with
kills (mobile.murders, ModernUO’s Kills > 0), 502706; an invulnerable shopkeeper, 1005598; an item in a bank box,
one on the ground, a fixed one (item.is_movable), a blessed or newbie one (item.loot_type), the one in a corpse, a
staff member’s or a dead one’s, 502710 “You can’t steal that!”; a worn one, 1005585; the thief’s own, 502704; farther
than a tile, 502703; out of sight, 500237; heavier than 10 stones (item.weight: a container with what is in it, a pile
by one unit), 502722; a backpack that cannot hold it (item.can_hold, the weight limits, asked before the roll),
1048147. ModernUO asks the range first and ModernUO’s own order differs in these edge cases. The skill check is the
weight in stones times 10, from 22.5 less to 27.5 more; a pile is cut to a random amount up to a tenth of the skill
over its unit weight, and the check is of that amount. A success moves the item to the thief’s backpack: a pile is cut
with item.split (the rest keeps the serial and stays with its owner, and the part taken is a new item) and the item goes
by item.move_into with its last argument, transfer, which a theft needs to take it from a mobile (502724); a failure
reads 502723. The item is saved by its new owner’s leave or the world save: a crash before then gives a whole item back to the
victim. A pile cut in two is two rows, the victim’s under the old serial, rewritten smaller by its next save, and the
thief’s under the new one: a crash between the two saves can lose the part taken, or leave it in both packs when the
thief’s save came first. An item its owner holds on the cursor is refused before the roll (502710), and a mobile never
picks it among the items of its backpack. A theft costs 50 karma however it ends (common/karma.lua), and the thief is caught with a
chance of one in 150 for each point of skill it lacks: it is a criminal when its victim is a mobile that is innocent
(by its own notoriety, not toward the thief) and not of the thieves guild, and the players within 8 tiles read “You
notice
item_identification.lua
Section titled “item_identification.lua”scripts/skills/item_identification.lua is the Item Identification skill, as ModernUO’s in its classic mode. on_use
asks “What do you wish to appraise and identify?” (500343) and a target within 8 tiles (“That is too far away.” 500446
beyond them; an item the player carries is always within reach). What it shows is what a single click shows
(item.look): the name line of the tooltip over the item or the mobile, with the amount of a pile, the name its
template gives and the title of a mobile, seen by that player only. A weapon, a piece of armor and an item with the item
prop unidentified are tried with skill.check(user, "item_identification", 0, 100), as ModernUO’s identifiables are,
and that is how the skill is trained: a success takes the prop away (an item that cannot be changed stays unidentified)
and says no more; anything not identified reads “You are not certain…” (500353) after the label, an ordinary item
included, as ModernUO’s does. Nothing sets unidentified yet: it is the hook for magic wands and loot to come, and the
properties of such an item are not hidden in its tooltip. The cursor checks no line of sight. The skill waits the 1
second of data/skills.toml.
taste_identification.lua
Section titled “taste_identification.lua”scripts/skills/taste_identification.lua is the Taste Identification skill, as ModernUO’s. on_use asks “What would you
like to taste?” (502807) and a target: an item or a mobile within 2 tiles and in sight (“You are too far away to taste
that.” 502815 beyond, “Target can not be seen.” 500237 out of sight; an item the player carries is always within reach).
A mobile reads “You feel that such an action would be inappropriate.” (502816). Food (the script food) is tried with
skill.check(user, "taste_identification", 0, 100), which is how the skill is trained: a success reads, as a label over
the food, “It appears to have poison smeared on it.” (1038284) when its item prop poison.level is there (the lesser
poison is level 0) and “You detect nothing unusual about this substance.” (1010600) when it is not; a failure reads “You cannot discern anything about this
substance.” (502823), poisoned or not. A potion (the scripts potion and explosion_potion) is already known: “You
already know what kind of potion that is.” (502813), then the client’s name of its kind, 1041314 plus ModernUO’s potion
effect (so a lesser heal potion reads “Lesser Heal”, not the yellow potion every heal potion is called; a potion of no
known kind shows its name through item.look). A potion keg is empty, “There is nothing in the keg to taste!” (502228),
as no keg holds potions yet. Anything else reads “That’s not something you can taste.” (502820). The
Poisoning skill sets poison.level on food. A click on the ground gets no answer (the cursor gives
none), and the bunches of bananas and the tribal berry are no food in the UOX3 data, so they read 502820. The skill waits
the 1 second of data/skills.toml.
lockpick.lua and treasure_chest.lua
Section titled “lockpick.lua and treasure_chest.lua”scripts/items/lockpick.lua is the script of the lockpicks (0x14fb, 0x14fc, 0x14fd and 0x14fe,
script_id = "lockpick"), as ModernUO’s Lockpick. Double click it, pick a locked item within a tile, and three
seconds later the skill is tried; the player must stay within a tile.
What can be picked is an item with these props: locked (true while it is locked), lock.level and lock.max, the
points of Lockpicking where the try may just succeed and where it never fails, and lock.required, the least points
to try at all. A lock without lock.level “cannot be picked by normal means”; a player under lock.required “does
not see how that lock can be manipulated”. A success unlocks the item for good (locked is false, lock.picker is
the player); a failure breaks the lockpick one time in four, which is taken out of its stack. Lockpicking is a skill
that rises with use, as the others.
scripts/items/treasure_chest.lua is the script of the four treasure chests of the dungeons
(templates/items/treasure_chests.toml, script_id = "treasure_chest"), as ModernUO’s TreasureChestLevel1 to 4.
A chest is made locked: it asks 57, 72, 84 and 92 points of Lockpicking by level, and the try runs from that less a roll
of 1 to 10 to that plus a roll of 1 to 10. A locked chest does not open (“It appears to be locked.”); a game master opens
it (“That is locked, but you open it with your godly powers.”); a picked chest opens as any container. Nobody but a
game master drops an item into a locked chest (can_insert), and what is already inside a chest open on a client can
still be lifted. Chests made before this release have no lock and stay as they were until they decay.
A chest is also armed with the trap of its level (common/trap.lua, as ModernUO’s FillableContainer
arms them): a level-2 chest always, a level-1 four times in five, with a poison for the deepest chests and an explosion
otherwise, and trap.power is the level times a roll of 10 to 30. Who opens a picked chest springs it — “You set off a
trap!” — takes its burst within 3 tiles (an explosion rand(10, 30) a level, a dart rand(5, 15) a level, a poison of
the level, a magic trap its power within 1 tile) and the chest stays shut that one click; a sprung trap is spent (all
three props gone). The staff opens with its godly powers and the trap stays armed, and a trap sprung from afar, as
Telekinesis does, reaches nobody out of its tiles.
common/trap.lua
Section titled “common/trap.lua”scripts/common/trap.lua is the trap of a container, kept in its props: trap.type (explosion, dart, poison or
magic), trap.level and trap.power. trap.arm(chest, level) arms the chest of that level (level 2 and up always,
level 1 four times in five; a poison when the level beats a roll of five, else an explosion; the power is the level times
a roll of 10 to 30). trap.spring(chest, who) is who opens it: the staff bypasses (“That is trapped, but you open it
with your godly powers.”, the trap stays), anyone else reads “You set off a trap!” (502999) and takes the type’s burst —
combat.harm with no attacker, “as for a trap” — within its tiles, and the trap is spent. The
Remove Trap skill disarms these props.
training_dummy.lua
Section titled “training_dummy.lua”scripts/items/training_dummy.lua is the script of the training dummies (0x1070 and 0x1071 facing south, 0x1074
and 0x1075 facing east), as ModernUO’s TrainingDummy: the templates carry script_id = "training_dummy", and so does
decoration_training_dummy, which .decorate gives the dummies of the files. Double
click a dummy with a melee weapon in hand, or none: the player turns and swings at it (combat.swing), the dummy
shows its swinging graphic from a quarter of a second, with the sound of a hit, and rests again after three seconds,
and the skill of the weapon (Wrestling for fists) is tried from -25 to 25 points, so it may rise up to 25.
A bow or a crossbow cannot practice on it (“You can’t practice ranged weapons on this.”), the weapon must reach it (a
tile, combat.range), a dummy that still swings makes the player wait, and a skill at 25 reads “Your skill cannot
improve any further by simply practicing with a dummy.”. There is no check for a mounted player: the script lets a rider practice.
archery_butte.lua
Section titled “archery_butte.lua”scripts/items/archery_butte.lua is the script of the archery buttes (0x100A facing east, 0x100B facing south), as
ModernUO’s ArcheryButte: the templates carry script_id = "archery_butte" and use_range = 6, as does
decoration_archery_butte, which .decorate gives the buttes of the files, so the player may double click it from where it
shoots. A world decorated before this release has them as plain decoration: .decorate again turns them into these.
- Shooting: with a bow or a crossbow, stand in front of the butte, in line with it, five or six tiles away, and
double click it. An arrow or a bolt is spent (
combat.spend_ammo), the player shoots (combat.swingand the arrow flying,effect.moving) and the archery of the weapon is tried from -25 to 25 points: it may rise, and the shot may miss. Two seconds go between two shots at a butte. The texts say what is wrong when the player stands behind, off the line, too far or too near. - Score: a shot that hits scores 50 (the bullseye, one in ten), 10, 5 or 2 points, and the player is told its total at that butte and how many shots. The arrow may split, which is likelier the more ammunition is stuck in the butte (2 percent for each), and then it scores more and is lost. The texts are told to the shooter, not to the others.
- Gathering: double click the butte within a tile, when arrows or bolts are stuck in it (props
butte.arrowsandbutte.bolts): they go to the backpack and the scores are cleared.
The dart boards are not built.
dyes.lua and dye_tub.lua
Section titled “dyes.lua and dye_tub.lua”scripts/items/dyes.lua and scripts/items/dye_tub.lua dye clothes in two steps, as ModernUO does;
the converted templates of the dyes (dyes) and of the tub (dying_tub) carry
script_id = "dyes" and script_id = "dye_tub".
- Double click the dyes and pick a dye tub: the client’s hue picker opens with the tub in it, and the tub takes the hue picked, from 2 to 1001, as its own hue.
- Double click the tub and pick what to dye: it takes the hue of the tub, with the sound of dyeing
(
0x23E).
What can be dyed is an item whose template says dyeable = true, as the clothing
converted from UOX3 does. It must not be worn, and the player must reach it, the tub and the dyes:
carried, or on the ground within 1 tile. Neither the dyes nor the tub is used up, and a tub never
dyed has hue 0, which takes the colour off. An item held on the cursor is not dyed (“You can not dye
that.”), and one inside a container on the ground counts as too far: take it first.
The texts are the client’s own, read in its language:
| Text | Cliloc |
|---|---|
| Select the dye tub to use the dyes on. | 500856 |
| Use this on a dye tub. | 500857 |
| Select the clothing to dye. | 500859 |
| Can’t Dye clothing that is being worn. | 500861 |
| You can not dye that. | 1042083 |
| That is too far away. | 500446 |
The player may answer the hue picker much later, or never. When the answer comes the script checks
again that the dyes and the tub are within reach, and the server takes an answer only for the picker
it opened: the other emulators take it as it comes. scripts/common/dye.lua holds what the two
scripts share (dye.reach, dye.worn, dye.tell). The special tubs (leather, furniture, black,
metallic) and the hair dyes are not there yet.
hiding.lua
Section titled “hiding.lua”scripts/skills/hiding.lua is the skill script of Hiding, as ModernUO’s without
what needs a fight or a house. A player that uses the skill is tried at it with
skill.check(user, "hiding", 0, 100): the chance is its points in a hundred, and the try may raise
the skill.
- Passed: the player is hidden (
mobile.set_hidden), out of war mode, and reads “You have hidden yourself well.” (cliloc 501240). - Failed: the player is shown, also when it was hidden, and reads “You can’t seem to hide here.” (501241).
Either way it waits before another skill the delay of hiding in
data/skills.toml, 10 seconds. Its first step shows it again, with “You have
been revealed!” (500814): the server does that for every hidden player of a regular account, unless
Stealth allowed the step; a turn on the spot does not. The staff hides to watch and stays hidden.
Speaking, being hit and the sight of who stands near do not show it yet.
meditation.lua
Section titled “meditation.lua”scripts/skills/meditation.lua is the skill script of Meditation, as ModernUO’s
before AOS. on_use shows the player first and refuses it: at full mana (“You are at peace.”,
501846), under a tenth of its hits (“The mind is strong but the body is weak.”, 501849), wearing
any armor (“Regenerative forces cannot penetrate your armor!”, 500135 — there is no meditation
allowance here, so any worn armor rating blocks the trance, ModernUO’s AOS rule) and with no hand
free (“Your hands must be free to cast spells or meditate.”, 502626 — the drinking rule of
mobile.has_free_hand; ModernUO’s spellbook and runebook are not read). Otherwise the check is
skill.check(user, "meditation", 0, 100): a pass sets the meditating prop, plays the sound 0xF9
and reads “You enter a meditative trance.” (501851); a failure reads “You cannot focus your
concentration.” (501850). The skill waits 10 seconds, the delay of
data/skills.toml.
While the prop is set, mana comes back twice as fast: the rate by intelligence and Meditation of the regeneration is halved (the passive effect of the skill is there without the trance too). The trance ends when the prop is taken away, as it is by a step, a wound, a swing, a spell, putting an item on and the use of another skill, but not of Meditation itself; it also ends when the mana is full (“You are at peace.”, 501846, checked by the regeneration every second) and when the character enters the world, as the prop is saved with it. ModernUO’s refusal of a busy player (501845) is not there: a script cannot ask of a pending cursor, and using a skill overrides it.
begging.lua
Section titled “begging.lua”scripts/skills/begging.lua is the skill script of Begging, as ModernUO’s
before ML. on_use shows the player first and asks “To whom do you wish to grovel?” (500397) with
a cursor. The refusals, each with the client’s own text: a thing that is not a living one and a
body that is not human (500399), a player (“Perhaps just asking would work better.”, 500398), a
target more than 2 tiles away (500401 him, 500402 her) and a mounted beggar (500404, a rule
ModernUO dropped at ML only). Otherwise the player bows (HumanAnimationType.Bow) and, two
seconds later, the try happens.
- The ill-reputed: with karma under 0, a chance of
0.5 - karma/8570that the NPC refuses the whole try, saying “Thou dost not look trustworthy… no gold for thee today!” (500406) overhead. - The roll is
skill.check(user, "begging", 0, 100); a failure reads “They seem unwilling to give you any money.” (500404) — ModernUO and ServUO send this one to the NPC, where nobody sees it, an inherited RunUO bug. - The gold: a tenth of the coins the NPC carries (
item.findof the gold template), capped by10 + fame/2500from 10 to 14, moves from the NPC’s backpack to the player’s (to the ground at its feet when its backpack has no room) with the sound of the pile (0x2E4 one coin, 0x2E5 up to five, 0x2E6 more). The NPC says “I feel sorry for thee…” (500405), or “I have not enough money to give thee any!” (500407) with no coins; an NPC without a backpack gets the 500404 refusal instead. - The karma: on gold received, what the beggar has above -3000, at most 40, through
common/karma.lua, which tells how much it lost. UOX3’s gold minted from thin air and Sphere’s begging stub (which gives nothing) are not followed.
The skill waits 10 seconds, the delay of data/skills.toml —
ModernUO’s effective wait from the button; its 30-second targeter placeholder is not kept. The
refusal roll of the ill-reputed is begging.chance, which a test replaces.
shepherds_crook.lua
Section titled “shepherds_crook.lua”scripts/items/shepherds_crook.lua is the Herding skill, as ModernUO’s ShepherdsCrook: the skill
button does nothing, the crook does. A double click on a crook at hand (the base_crook and
base_shepherds_crook templates, which every crook inherits) asks “Target the animal you wish to
herd.” (502464). The refusals: a thing that is not a living one (502472), an animal farther than 10
tiles (500446), one that cannot be tamed — pet.lore names no min_skill (502468) — and one that
belongs to a player already (502467). Then the place: “Click where you wish the animal to go.”
(502475), and the try between the animal’s taming difficulty less 30 and that more 30 and a roll
of 0 to 9 points; when even the top of the window is under the shepherd’s skill it reads “That
wasn’t even challenging.” (502471) anyway. The animal is asked again when the place is picked: one
that died or was tamed meanwhile is refused with the same texts. On a pass the herding.x and herding.y props are set
on the animal and the shepherd reads “The animal walks where it was instructed to.” (502479); a
failure reads 502472.
The walk itself is the animal’s: common/creature.lua walks a creature with
the props a step a think (npc.walk_to), until it is there or no way leads there, and then
clears them; a refused step (blocked) is only a delay and keeps them, up to 20 thinks in a row
(the herding.blocked count), and then the order is given up; a fight or a flee comes first, and
the walk resumes after, and a tamed one drops it.
UOX3’s rule of herding the animal to yourself (it follows you) is not kept.
poisoning.lua
Section titled “poisoning.lua”scripts/skills/poisoning.lua is the skill script of Poisoning, as ModernUO’s before AOS. The
player picks the poison to use (502137) — one of the four poison potions of alchemy
(potions.kind), carried or within 2 tiles; anything else is not a poison potion (502139). Then
it picks what to poison (502142): a one-handed weapon that slashes or pierces — a sword, an axe, a
pole arm or a fencing weapon (item.combat_stats), as ModernUO asks pre-AOS — or food (a template with script_id = "food");
anything else is refused (502145). The poison sounds (0x4F), the potion is spent and an empty bottle comes
back, and two seconds later the skill is tried between the least and the most of the potion’s
window: lesser 0 to 60, regular 30 to 70, greater 60 to 100, deadly 95 to 100 points.
The potion is checked again at the second pick (still at hand, not on a cursor), and the target when the try comes: one that is gone, out of reach or held on a cursor takes nothing (“You fail to apply a sufficient dose of poison”, 1010518, and the dose is lost), and a poisoner that died or left the world is told nothing.
On a pass the poison rides the target: the poison.level prop, the level the
PoisonService counts — and a weapon also poison.charges, 18 less
two for every level — and the player reads “You apply the poison” (1010517) and loses 20 karma
through common/karma.lua. Every blow that lands
spends one charge, and the poison passes to whoever is struck on a half chance (CombatService), as the classic rules
have it; both props are gone at the last charge. Only a sword and a fencing weapon pass it: an axe and a pole arm take the
poison but keep it; a poisoned piece of
food poisons whoever eats it (food.lua). On a failure a poisoner under 80 points
makes a grave mistake one time in twenty (poisoning.mistake, a test replaces it): it reads so
(502148) and is poisoned itself; otherwise a blade reads 1010516 and anything else 1010518. The
skill waits 10 seconds, the delay of data/skills.toml. No fukiya
darts or shuriken (no ninjitsu), no AOS infectious strike.
remove_trap.lua
Section titled “remove_trap.lua”scripts/skills/remove_trap.lua is the skill script of Remove Trap, as ModernUO’s. It asks for a
locksmith and a scout: 50 points of Lockpicking (502366) and 50 of Detect Hidden (502367) before
the cursor, and less of one is sent away to train it. The player picks the trap to disarm
(502368) — a mobile is “inappropriate” (502816), a thing neither carried by the player nor
within 2 tiles too far, and one without the trap.type prop “doesn’t appear
to be trapped” (502373).
The work sounds (0x241) — a chest the player carries is bent over first, as ModernUO’s
Direction rule turns one toward the thing at hand — and the skill is tried between the trap’s
power and that more 30 points
(trap.power): a pass clears the three trap props — the next open of the chest springs nothing —
and reads “You successfully render the trap harmless” (502377); a fail reads “You fail to disarm
the trap… but you don’t set it off” (502372). A failed disarm never springs the trap, as
ModernUO’s. The skill waits 10 seconds, the delay of data/skills.toml.
No faction traps.
Regeneration props
Section titled “Regeneration props”Hit points, mana and stamina come back by themselves (see
ultima.regeneration). A script changes the rate of one mobile with its
props, in seconds for a point: mobile.set_prop(who, "regen.hits", 2) heals it five times faster than
the default; nil gives it the configured rate back. The props are regen.hits, regen.mana and
regen.stamina.
Moving takes stamina from a player (see fatigue_enabled in
ultima.regeneration): running, and every step when it carries more than
mobile.max_weight. A script that gives or takes items changes what the mobile carries at once; the
status bar of its player follows at the next status update.
common/teleport.lua
Section titled “common/teleport.lua”The two teleporter scripts below share scripts/common/teleport.lua, a Lua module they take with
local teleport = require("common.teleport"): teleport.send(serial, who) sends a mobile where the
item’s props say (teleport.x, teleport.y, teleport.z, teleport.map), with the smoke of
source_effect and dest_effect and the sound of sound_id, and teleport.is_on(value) reads a flag
the decoration files carry as text. A script of your own that teleports can take it the same way.
A script keeps the module it took: after script reload common/teleport.lua, reload the scripts that
use it too (see Reload and ownership). mgctl init adds scripts/common/ to an
existing root and keeps the scripts already there; a root whose item scripts are replaced by hand needs
scripts/common/ as well, or its teleporters stop.
common/numbers.lua
Section titled “common/numbers.lua”scripts/common/numbers.lua is how the shipped scripts write a number a player reads:
numbers.with_thousands(1234567) gives "1,234,567". The banker and the bank check take it with
local numbers = require("common.numbers"), and a script of your own can do the same. numbers.in_ranges(list, value)
says whether a number lies in one of the { first, last } pairs of a list, which is how the smithy, the oven and the
fires, the trees and the pickaxe know a graphic. A root whose scripts are replaced by hand needs it too, or its bankers,
its bank checks and the scripts that know a graphic stop.
common/karma.lua
Section titled “common/karma.lua”scripts/common/karma.lua takes the karma of an act off a player, as ModernUO’s Titles.AwardKarma does for a loss:
karma.lose(user, cost) takes cost and a hundredth of the karma held when it is positive (a good name pays more, a
bad one pays less, and nothing when its karma is under -cost * 100), never below -15000, and tells the player how much
it lost (“You have lost a little / some / good / a lot of karma”: 1019063 to 1019066), “a little” when the loss came to
nothing. Snooping (4) and Provocation (75)
use it.
common/geo.lua, speech.lua and skill_points.lua
Section titled “common/geo.lua, speech.lua and skill_points.lua”Three small modules hold what many scripts used to write each for itself. scripts/common/geo.lua counts tiles as the view
range does, the larger of the two coordinate differences: geo.distance(a, b) for two places { x, y, map } (math.huge
when one is nil or the maps differ, so > range is true for both), geo.within(a, b, range) for the same as a boolean,
geo.near(serial_a, serial_b, range) for two serials, over world.distance, which is false when there is no distance, and
geo.same_storey(a, b) for whether two places are at most 16 apart in height, so on one floor, geo.top_container(serial)
for the topmost container an item lies in (the item itself when it lies in none) and geo.place_of(serial) for where an item
is: where the mobile that carries it stands, or where the container it lies in lies on the ground.
scripts/common/speech.lua has speech.has_keyword(keywords, wanted) for the keywords the client heard and
speech.addressed(serial, text), true when the words begin with the name of the NPC, case apart.
scripts/common/skill_points.lua has skill_points.of(who, name), the points of a skill by the name data files give it
(magery, evaluating_intelligence), 0 for one never trained and an error for a name that is no skill; a script that has the skill in hand writes
mobile.skill_points(who, SkillType.Mining).
The three functions under them are in the modules: world.distance(a, b) gives the tiles between two mobiles or items on
the ground by serial (nil for an unknown one, an item in a container, or two maps), mobile.skill_points(serial, skill)
gives one skill as a number without building a table, and pet.owner(serial) gives the owner of a creature, nil for none
(a prop of 0 is no owner). A root whose scripts are replaced by hand needs scripts/common/geo.lua, speech.lua and
skill_points.lua with the scripts that ask for them, or those scripts stop.
common/gump_util.lua, skill_ui.lua and the menu of common/shop.lua
Section titled “common/gump_util.lua, skill_ui.lua and the menu of common/shop.lua”Three more modules hold what the gumps, the skills that pick something and the vendors wrote each for themselves.
scripts/common/gump_util.lua is for the scripts that fill a slot of a gump: gump_util.guard_staff(player, admin) is
the rank check of the top of a script (an administrator when admin is set), gump_util.staff_only(fn, admin) and
gump_util.staff_button(g, spec, fn) make a button check it again at the click, since the rank may have gone while the gump
was open, and pass every argument of the click through. gump_util.row(g, y, text, on_click, opts) is the button with its
text cropped beside it, with the art gump_util.ART.pick, act, release or back; gump_util.paged(g, list, opts, fn)
sets the pager and calls fn(entry, y, index) for each entry of a paginated list, and gump_util.page_arrows draws the
n / m and the arrows of a list that is paged by opening the gump again. gump_util.text(response, index, trim) reads
what was typed in an entry, cut at both ends only when trim is set. The gumps go, pages, pages_detail, gmtools,
jail_sentence, stable_claim, items, npcs and craft_menu take them; the tutorial list keeps the bare builder, as the
example of the gump tutorial.
scripts/common/skill_ui.lua is for the skills that ask the player to pick: skill_ui.pick_object(user, prompt, fn) tells
the client text and gives the cursor, and calls fn(serial) with what was picked. skill_ui.pick_living(user, opts, fn)
adds the checks of Anatomy and Evaluating Intelligence, in this order and each with its own text: the player itself, a
thing that is not alive, an invulnerable one and one farther than opts.sight, which says nothing.
skill_ui.margin(points, base, step) and skill_ui.read_tenth(value, margin) blur a number read by what the skill lacks.
scripts/common/shop.lua also has shop.menu(serial, player, range), the Buy and Sell entries followed by the Train
entries of training.lua, and shop.select_any(serial, player, id), which hands the choice to the shop and then to the
lessons: the shopkeeper, the healer and the stablemaster use the two. A root whose scripts are replaced by hand needs
scripts/common/gump_util.lua and skill_ui.lua with the scripts that ask for them, and training.lua with shop.lua.
teleporter.lua
Section titled “teleporter.lua”scripts/items/teleporter.lua is the script of the decoration_teleporter template that
.decorate gives to ModernUO’s Teleporter: on on_move_over it
teleports the player to the props teleport.x, teleport.y and teleport.z with
mobile.teleport, shows a puff of smoke where the player left (prop source_effect) and
arrived (prop dest_effect), then plays the prop sound_id there when the teleporter has one. The prop
active = false turns a teleporter off, and the prop deny_mounted makes it refuse a rider (“You must dismount before proceeding.”, 1077252). Only players travel, unless the prop creatures is true: then an NPC that steps on it travels too. A teleporter with the prop teleport.map, a MapType
number, takes the player to that map: the client changes map, then gets the season when it differs
from the one it shows, the light, the weather and the music of the place; when the map is not loaded nothing happens. The template has visibility = "game_master": a ground item is sent only to
the accounts its visibility allows, so players walk onto a teleporter they never see.
keyword_teleport.lua
Section titled “keyword_teleport.lua”scripts/items/keyword_teleport.lua is the script of the decoration_keyword_teleporter
template that .decorate gives to ModernUO’s KeywordTeleporter, such as the mantra of a
shrine: on on_speech it teleports the player who says the prop substring (found anywhere in
the text, in any case) or whose client sends the speech keyword of the prop keyword, standing
within range cells (0, the default, is the teleporter’s own cell). With a delay
("0:0:1", or a number of seconds) the teleport happens later, if the player still stands in
range. The destination, the smoke, the sound and active are those of the plain teleporter.
public_moongate.lua
Section titled “public_moongate.lua”scripts/items/public_moongate.lua is the script of the decoration_public_moongate template
that .decorate puts on every destination of moongates.toml, as
ModernUO’s PublicMoongate: on on_move_over, and on on_use from the next cell, it builds a
gump with gump.create, one page per map of moongates.facets() and one button per city, the
page of the player’s own map first, and plays the sound 0x20E. A button teleports the player
with mobile.teleport, to another map too, and plays 0x1FE there. A player who walked more
than a cell away while the gump was open is told so and stays; choosing the city of the gate
itself does nothing.
healer.lua
Section titled “healer.lua”scripts/mobiles/healer.lua is the script of the healers (script_id = "healer"): on every think it asks
npc.ghosts_in_sight(serial, 4) for the ghosts within 4 cells in its line of sight. A ghost that was not
there at the last think is turned to (npc.look_at), gets the sound 0x1F2 and the sparkles SparkleHeal, and
the gump resurrect with the argument healer, as ModernUO’s BaseHealer. A healer waits 2 seconds (4 thinks)
between two offers, and a ghost met during the wait is offered when it is over. A criminal is refused with the
client text 501222 and a murderer (red) with 501223, and a player of negative karma is told 501224 and offered all
the same. An evil healer, whose template id starts with evil (evilhealer, evilwhealer), refuses
nobody and says nothing. A healer of a
template ending with whealer, a wandering one, takes a step with npc.wander every fourth think. A healer with a shop sells and buys as a vendor does, through scripts/common/shop.lua: bandages, potions, ginseng and garlic. It also teaches the skills it has, through scripts/common/training.lua (see Trainers).
ethereal_mount.lua
Section titled “ethereal_mount.lua”scripts/items/ethereal_mount.lua is the script of the ethereal statuettes (script_id = "ethereal_mount"): its
on_use calls mount.ride_ethereal(user, serial), which says in the client’s words why it refuses, and returns true
so the double click opens nothing else. See Mounts.
mount_stone.lua
Section titled “mount_stone.lua”scripts/items/mount_stone.lua is the script of the mount stones (script_id = "mount_stone"): its on_use calls
mount.toggle_stone(user, serial), which rides the mount of the stone or, when the player already rides the mount of
this very stone, gets off; the stone stays in the backpack. A puff of smoke and its sound (0x1FE) are shown where the
rider gets on or off. It returns true so the double click opens nothing else. See
Mounts.
engraved_stone.lua
Section titled “engraved_stone.lua”scripts/items/engraved_stone.lua is the script of the engraved stone (script_id = "engraved_stone"). on_use opens
the read only gump stone_read (templates/gumps/stone_read.xml) with the title and the lines kept in the props
stone.title and stone.text, or says that nothing is written yet; it returns true. can_pick_up lets the owner (the
prop owner, stamped on the buyer by the vendor) and the staff lift the stone and tells anyone else it is not theirs.
can_drop refuses a drop into a container that the dropper does not carry, so the stone cannot leave inside a bag.
The limits and the rules the script and the gumps share are in scripts/common/engraved_stone.lua. The gump to write,
stone_write (templates/gumps/stone_write.xml, opened by .write), has its fields built by
stone_write.rows in scripts/gumps/stone_write.lua, which checks the owner again and cleans the texts when it saves.
See Engraved stones.
engraved_ring.lua and common/engraving.lua
Section titled “engraved_ring.lua and common/engraving.lua”scripts/items/engraved_ring.lua is the script of the rings a jeweler engraves (script_id = "engraved_ring"). The text
is kept in the prop ring.text: on_tooltip adds the client’s line “Engraved: …” with it, and on_use tells it to
the player. scripts/common/engraving.lua is what the jewelers share: the “Engraving:” entry of the context menu, the
word “engrave” said to a jeweler by name within 4 tiles, the order taken from the gump ring_engrave
(templates/gumps/ring_engrave.xml, built by scripts/gumps/ring_engrave.lua) and the exact gold dropped on the jeweler
that pays it: 500 gold for a ring, 1000 for a wedding band. shopkeeper.lua and common/shop.lua call it. See
Engraved rings.
stablemaster.lua and stable_claim.lua
Section titled “stablemaster.lua and stable_claim.lua”scripts/mobiles/stablemaster.lua is the script of the animal trainers (script_id = "stablemaster"). The words
stable and claim, said within 12 cells, and the entries Stable and Claim All of the context menu drive the
stable module: stable gives a cursor (target.pick) and calls stable.stable on the pet picked, answering with
the client’s text for each StableResultType; claim says the list intro and opens the gump stable_claim
(templates/gumps/stable_claim.xml), or says there are no pets; Claim All calls stable.claim on the first place
until the list is empty. Several trainers hear the same words and stable.attend lets one answer. An animal trainer
keeps its shop and its lessons through scripts/common/shop.lua and training.lua.
scripts/gumps/stable_claim.lua fills the gump with one button and the name of the pet for each pet of stable.pets,
eight a page; a button checks the player is within 12 cells of the trainer and calls stable.claim; a list that
changed since it was shown is shown again.
hireling.lua
Section titled “hireling.lua”scripts/mobiles/hireling.lua is the script of the ten hirelings of Jhelom (script_id = "hireling"), made with
common/creature.lua, whose on_think, on_speech, on_drag_drop and on_death it wraps. A free one answers the words
hire, servant and a greeting (SpeechKeywordType.Hire, Servant, Greeting) within 6 cells, and the entry Hire
(cliloc 6120) of its context menu, with its pay: the ten skills of its template added, over 35, plus 1. Gold dropped on
it (item.item_id 0x0EED) of at least that pay calls pet.hire, takes the gold with item.consume and keeps it in the
prop hire.gold, with hire.next_pay set pet.hire_period() seconds on. Its on_think settles the periods that went by in
one step, by world.now(), and ends the contract with npc.delete and the client’s text 503235 when the gold does not
cover the next period; on_release clears both props and on_death keeps what it wore out of the corpse. A hired one is
owned (pet_orders.is_owned), so the words of its owner are the words of a pet. See Hirelings.
pet_orders.lua and pet_release.lua
Section titled “pet_orders.lua and pet_release.lua”scripts/common/pet_orders.lua is what common/creature.lua runs for a creature that has an owner: is_owned(serial)
says whether a creature has one (tamed or summoned); think follows the
order in the prop pet.order (follow, come, stay or guard; follow when it has none), and listen, from the
on_speech of the creature scripts, reads the words of the owner (SpeechKeywordType.PetCome, AllStay and the others)
within 14 tiles. Of the “all” words only all kill is carried out by the first pet that asks pet.attend(owner), for every pet of the owner
within reach, since there is one cursor; every other “all” order is obeyed by each pet on its own. kill asks for a target with target.pick; release opens the gump pet_release
(templates/gumps/pet_release.xml), whose Release button calls pet.release after checking the pet is still the player’s and
within 14 tiles. Every order but release first rolls pet.obey(owner, pet) (the chance is pet.control_chance): a pet that
refuses growls and fidgets and does not take the order. feed(serial, giver, given) is what the creature scripts return from
on_drag_drop: the owner’s food goes to pet.feed, which takes the stack and raises the loyalty (pet.loyalty); food the
creature does not eat is given back. The food can also bond the pet (the owner is told 1049666). menu(serial, player)
and select(serial, player, id) are what creature.new returns from on_context_menu and
on_context_menu_select: the owner’s nine commands (Kill, Follow, Guard, Stop, Stay, Rename, Add Friend, Remove Friend,
Release) and a friend’s three, each the same as its word; Rename only tells to edit the name in the status bar. The
friends are the prop pet.friends (whole serials joined by commas, five at most); is_friend(serial, who) reads it, and
listen hears a friend for follow, stay and stop, rolled by pet.obey with the friend’s own skills; a friend’s
follow keeps the friend in the prop pet.follow, which think follows instead of the owner until an order of the owner
removes it. See Animal taming and
loyalty, food and obedience.
animal_taming.lua
Section titled “animal_taming.lua”scripts/skills/animal_taming.lua is the Animal Taming skill: on_use says “Tame which animal?” (502789), gives a cursor
(target.pick) and returns a wait of 1 second. The pick is refused with the client’s text if it is no creature, a player,
not in taming.toml (pet.info), owned, too many followers (pet.followers and pet.max_followers), above the skill
or more than 3 tiles away. Then three or four times of 3 seconds (timer.after) check again the distance (7), the
tamer alive, the line of sight (world.line_of_sight), the creature still wild and not hurt (mobile.stats), and say a
kind line; the last rolls skill.check(user, "animal_taming", min - 0.1, min + 49.9) and pet.tame. See
Animal taming.
polymorph_forms.lua
Section titled “polymorph_forms.lua”scripts/gumps/polymorph_forms.lua is the script of the list of forms of the spell Polymorph
(templates/gumps/polymorph_forms.xml). Each of its 18 buttons calls the function of its name (chicken, dog, wolf,
panther, gorilla, black_bear, grizzly_bear, polar_bear, human_male, slime, orc, lizardman, gargoyle,
ogre, troll, ettin, daemon, human_female), which keeps the body in the props magic.polymorph_body and
magic.polymorph_until (fifteen seconds) and casts the spell again, from the book or from a scroll of the backpack. The
spell script scripts/spells/polymorph.lua then changes the body with mobile.disguise. See Magery.
ankh.lua and resurrect.lua
Section titled “ankh.lua and resurrect.lua”scripts/items/ankh.lua is the script of the decoration_ankh template, the two pieces of each
AnkhWest and AnkhNorth that .decorate places. It has no on_use: the living have nothing to do
with an ankh. Its on_ghost_use runs when a dead player double clicks it (Death and
resurrection): from more than 2 cells it says “That is too far away.”
(client text 500446), else it opens the gump resurrect (templates/gumps/resurrect.xml). Its
Continue button calls resurrect.accept in scripts/gumps/resurrect.lua, which, if the player is
still dead and within 2 cells of the ankh (8 of the healer, when it was a healer that
asked), calls mobile.resurrect, plays the sound 0x214 and the effect SparkleHeal on the player and
takes a tenth of its fame; from five short-term murders it takes skills and stats too (Murder counts).
Cancel does nothing.
moongate.lua
Section titled “moongate.lua”scripts/items/moongate.lua is the script of the moongate template, the gate with one
destination that the command moongate puts at a game master’s feet, as
ModernUO’s Moongate. On on_move_over, and on on_use from the next cell, it waits one second
with timer.after, then takes the player, if it still stands there, to the props teleport.x,
teleport.y and teleport.z, on the map of the prop teleport.map (a MapType number or its
name; the player’s own map without it), and plays 0x1FE. A gate without the three
numbers, with a map that does not exist or is not loaded, or with a spot outside the map tells the
player “This moongate does not seem to go anywhere.” (message 30114). Touching the gate again
during the second starts nothing. When the gate
stands in a guarded region and the destination does not (world.is_guarded), it asks first: a
gump with OKAY and CANCEL and the sound 0x20E; OKAY from more than a cell away tells “That is
too far away.” (message 393) with mobile.message. ModernUO’s rules about sigils, young
players, murderers and casting are not there yet; the followers of the player within 3 tiles go through with it
(mobile.teleport with its last argument true). The gates of the spell Gate Travel are of the template
magic_gate, which uses this script: its item timer expire (on_timer) takes the gate away after 30 seconds, and the
Dispel Field spell takes it, with the gate at its other end (the prop gate.partner).
bulletin_board.lua
Section titled “bulletin_board.lua”scripts/items/bulletin_board.lua is the script of the bulletin boards
(the item template decoration_bulletin_board that .decorate places, and bulletin_board and
bulletin_board_2 for a board added by hand). Its on_use calls board.open(serial, user)
and returns true: the player’s client gets the board and the list of its messages, and from
there reads, posts, replies and removes its own. Any item template with this script is a board,
each item with its own messages.
clock.lua
Section titled “clock.lua”scripts/items/clock.lua is the script of the clocks (the item templates clock and
clock_2, and decoration_clock for those .decorate places), as ModernUO’s Clock: on
on_use the player reads over the clock the part of the day (“It’s the afternoon”) and the time to
the minute (“1:07 to be exact”) where they stand, from world.time, as texts of the client sent
with item.message_cliloc.
fillable.lua
Section titled “fillable.lua”scripts/items/fillable.lua is the script of the decoration_fillable template that .decorate
gives to the town containers, ModernUO’s FillableContainer: the crates, boxes, chests and barrels
of the shops and the bookcases of the libraries. On on_use, before the container opens, a
container whose time has come (prop fill.next, as world.now() counts) and that holds two things
or fewer, a pile counting for its amount, gets up to twice what it misses to hold three, each one a
roll of the loot table of its kind with item.add_loot; a bookcase fills up to five books. It then waits 60 to 90 minutes; a fill that could add nothing is tried again at the next opening.
Nothing runs while nobody opens the container, and the times survive a restart. The kind is the
prop content_type, such as baker for the table fillable_baker of
templates/loots/fillable_containers.toml; without it the container takes the kind of the nearest
vendor within 20 tiles, told by mobile.template, and keeps it. With no vendor around it stays
empty and looks again five minutes later. ModernUO starts the wait when an item is taken out, and
locks and traps the container: those are not there yet. The town tables use
templates/loots/randomshields.toml (one plain shield, ModernUO’s Loot.ShieldTypes) and the two goods of
templates/items/town_goods.toml (mallet and chisel, arrow shafts) that the converted item files lack.
gmtools.lua
Section titled “gmtools.lua”scripts/gumps/gmtools.lua is the script of the gump of the game master’s tools
(templates/gumps/gmtools.xml), which .gmtools opens. The gump has two
slots, filled by two functions: tools draws the sidebar, a button for each entry of the table
tools in the script, and panel draws the panel of the selected one (args.tool, the first when
none or an unknown one is given). A click on the sidebar opens the gump again on that tool.
There are four tools, the weather, the season, the time and the events. The weather panel reads world.weather_profile and world.weather and
has a button for each kind, none, rain, snow and storm, that calls world.set_weather on the
player, tells it The weather of temperate is now storm until the next hour. and opens the gump again.
The season panel reads world.season_here and the season of the map the player stands on
(world.season of mobile.location(player).map) and has a button for each season and one for
auto, that call world.set_season or world.clear_season, tell the player The season of your map is now winter. and open the gump again. The time panel reads world.time, world.moon,
world.light_here and world.global_light and has a button for each of four light levels and one for
auto, that call world.set_global_light or world.clear_global_light and tell the player The global light is now 26.. The events panel (an entry with admin = true, so only for the administrators) reads schedule.events and has auto, on and off buttons for each event, that call schedule.set_event, tell the player Halloween is now off. and open the gump again. Staff only: the slots are empty for anyone else, and every
button checks world.is_staff again, and world.is_administrator for the events.
To add a tool, write a panel function with the signature function(g, player) and add
{ id = "...", title = "...", panel = ... } to tools.
items.lua
Section titled “items.lua”scripts/gumps/items.lua is the script of the gump of the item search (templates/gumps/items.xml), which
.items opens. The gump has one slot, filled by body(g, player, args): a title, a text
entry bound to query with a Search button that opens the gump again with it, and then the matches of item.search
(the first 40; the title says when there are more), five to a page chosen by args.page, each drawn with g:item of
its graphic, its name and id, and two buttons that call item.give and item.create at mobile.location(player). The
arrows and n / m open the gump again on the page before or after, so a Give keeps the page. A typed ${name} is
shown as typed. Staff only: the slot is empty for anyone else, and every button checks world.is_staff again.
npcs.lua
Section titled “npcs.lua”scripts/gumps/npcs.lua is the script of the gump of the NPC search (templates/gumps/npcs.xml), which
.npcs opens, built like items.lua. Its slot holds a title, a text entry bound to
query with a Search button, and then the matches of npc.search (the first 40, the title says when there are more),
five to a page chosen by args.page, each drawn with g:portrait of its body and with a Spawn button that calls
npc.spawn at mobile.location(player). The arrows open the gump again on the page before or after, so a Spawn keeps
the page. Staff only: the slot is empty for anyone else, and every button checks world.is_staff again.
guard_outsider.lua
Section titled “guard_outsider.lua”scripts/mobiles/guard_outsider.lua is the script of the guards of the wilds, for the places outside the guarded
towns: the templates guard_outsider (a halberd) and guard_outsider_archer (a bow) use it with
script_id = "guard_outsider", and a game master puts one down with .npcs. Unlike the town
guards of guard.lua, which kill a monster with one blow and cannot be hurt, these fight for real: a
blue name (notoriety = "innocent"), so the monsters go for them as they do for the townsfolk, 151 to 191 hit points,
damage 10 to 24 (before the bonuses of its skills and strength), armor 20, fighting skills 85 to 95, and the swings,
hits and death are the combat service’s: an NPC fights with its wrestling skill and the damage of its template, the
halberd and the armor it wears are only dressing (and drop with its corpse), and the archer shoots with its bow. The
script is common/creature.lua with its prey option: it goes for the wild monsters it sees within 16 tiles (the NPCs
with the script monster that nobody tamed, that are not dead and have no blue name: a fighter or a paladin of the
towns has the monster script too), and never starts a fight with a player, a townsperson, an animal or a pet; it answers a blow as any creature does, and its template sets flee_at = -1, so it
never runs. One with a bow stops where the bow reaches and shoots.
help_menu.lua
Section titled “help_menu.lua”scripts/gumps/help_menu.lua is the script of the help menu
(templates/gumps/help_menu.xml), which the Help button of the paperdoll opens. stuck is the “I am stuck”
button: it refuses a character in jail (jail.sentence) or fighting (combat.target), one that already
waits, and one whose pause (the prop help.stuck_until, seconds since 1970 from world.now) is not over,
the staff excepted; it takes the nearest starting city from help.nearest_city, tells the wait from
help.settings, and after timer.after of that many seconds moves the character with mobile.teleport
if it stands where it did and is still allowed. commands runs help with commands.execute_as, rules
tells message 30200 and call opens help_page_kind.
help_page_kind.lua
Section titled “help_page_kind.lua”scripts/gumps/help_page_kind.lua is the script of the second step of Call a game master
(templates/gumps/help_page_kind.xml). question, bug, suggestion and harassment ask help.can_page;
a player that may not is told why (messages 30213 and 30214) before anything is typed. Otherwise it is told
to type a line (30211), prompt.ask waits for it, and help.create_page sends the request with the
HelpPageKindType of the button; Escape or an empty line says 30215, a refusal at the end says its reason again,
and a sent request says 30212. A player in jail may call.
pages.lua
Section titled “pages.lua”scripts/gumps/pages.lua is the script of the queue of the staff (templates/gumps/pages.xml, opened by
.pages). rows fills the slot with help.pages(), the oldest first, ten a page, one
button and one line a request (#1 Gino, Bug, 3 min, open); a row opens pages_detail on that request.
Staff only: the slot stays empty for anyone else, and every button checks world.is_staff again.
pages_detail.lua
Section titled “pages_detail.lua”scripts/gumps/pages_detail.lua is the script of one request (templates/gumps/pages_detail.xml). go takes
the game master to the player (mobile.location) or, when it is offline, to where it asked; take calls
help.take; answer takes the text of the field (response.text[1]), refuses an empty one and otherwise calls
help.answer and returns to the queue; close calls help.close. A request that was closed meanwhile says so
and changes nothing.
common/help_pages.lua
Section titled “common/help_pages.lua”scripts/common/help_pages.lua is the Lua module the two staff gumps share, taken with
require("common.help_pages"): the words for a kind, a map, an age and a status, the line of a request and the
arguments of its detail gump.
jail_sentence.lua
Section titled “jail_sentence.lua”scripts/gumps/jail_sentence.lua is the script of the gump of the jail
(templates/gumps/jail_sentence.xml), which .jail opens with no target,
and .jail <name> on the player of that name.
Its rows function fills the slot: first a button that gives the cursor with target.pick and opens
the gump again on the character picked, then the cells of jail.cells(), ten per page. With a
character picked, a free cell has a button that reads the days typed in the gump and calls
jail.send(target, cell, days, who, reason), the reason being what is typed in the second field; days that are empty, not a number, a fraction or beyond
jail.max_days() jail nobody, and the gump opens again with the reason. A cell that holds someone
shows its name, the time left as 2d 4h, 5h 10m or 12m, and a button that calls
jail.release. Every cell has a button that takes the game master into it with
mobile.teleport(who, cell.x, cell.y, cell.z, cell.map), on the map of the jail. A target that is
already in jail has its release on a line of its own at the top.
A target that is not in the world, a player .jail <name> found, is shown as (offline):
mobile.name is nil for it. jail.send then answers JailResultType.Pending and the game master
is told the cell is kept until the login; such a cell reads waits for login, from the pending
of jail.cells(). When the gump is opened with candidates, several players of one name, the
function lists them, ten at most, with their account and whether they are online, each with a
button that opens the gump on it, and leaves the cells out until one is picked.
Every button checks world.is_staff again: the rank may have gone while the gump was open.
jail_note.lua
Section titled “jail_note.lua”scripts/items/jail_note.lua is the script of the jail_release_note template, the note a
prisoner finds in its backpack when its sentence ends. On on_use it delegates to
book.open, displaying saved book.content or legacy jail.text: the days served, cell, dates
and fine paid. The shared parchment escapes plain text and scrolls long bodies. A note with no
text, such as one made with .add, shows nothing.
readable_scroll.lua
Section titled “readable_scroll.lua”scripts/items/readable_scroll.lua delegates double click to book.open. The unstackable
readable_scroll template is used by the text catalog.
Create a personalized letter with book.give(player, "welcome_letter", { contact_name = "Vega" }).
Its saved title, author and body remain fixed when another player reads it.
readable_book.lua
Section titled “readable_book.lua”scripts/items/readable_book.lua delegates double click to book.open as the scroll does; for an
item of the readable_book template book.open sends the client’s book, its cover and every page,
instead of the parchment (books and parchments).
The imported ModernUO texts use it: book.give(player, "grammar_of_orcish").
bank_check.lua
Section titled “bank_check.lua”scripts/items/bank_check.lua is the script of the bank checks, which a banker writes for gold of the bank (“check 5000”). A
double click on a check inside the open bank box turns it back into coins of the box, in piles of 60000. A box with room for
part of the gold takes what fits and the check keeps the rest. Outside the bank box a check is only a piece of paper worth
what its tooltip says. See Bank.
snow_pile.lua
Section titled “snow_pile.lua”scripts/items/snow_pile.lua is the snowball of the Christmas event. A double click on a pile in the backpack asks for a
target; the snowball flies to a mobile that carries a pile too, hits it, and both read it. A player waits 5 seconds between
two snowballs and cannot throw one while mounted. An item template uses it with script_id = "snow_pile".
banker.lua
Section titled “banker.lua”scripts/mobiles/banker.lua is the script of the bankers. A player within 12 tiles says a word and the banker opens its bank
box (“bank”), tells the balance (“balance”), hands out gold (“withdraw 500”), takes it (“deposit 500”) or writes a bank check
(“check 5000”). The client turns most of these into speech keywords in any language; “deposit” is English only. Gold and
checks dropped on the banker go into the bank (on_drag_drop). A banker does no business with a criminal. How much it hands
out at one time is the setting ultima.bank.max_withdraw. See Bank.
quest_giver.lua
Section titled “quest_giver.lua”scripts/mobiles/quest_giver.lua is the script of the NPCs that give the quests migrated from ModernUO (script_id = "quest_giver" of the
template ml_questgiver, see Quests): they stand where they are (the script has no on_think).
on_mobile_in_range hands the mobile that came near to quest.on_near of scripts/events/quest.lua, which asks common/quest_shout.lua:
a player that is alive and seen by the NPC when it comes within ultima.npcs.sense_range (8 tiles by default, as ModernUO’s shout range),
who has a quest available from the NPC’s template, makes it turn and say, with npc.say_cliloc_to, to that player alone, one of the numbers
its template has in scripts/quests/ml_shouts.lua, and the NPC keeps quiet for a minute (60 seconds of world.now()). An NPC with no
lines costs the queued hook, one Lua call and a template lookup for each mobile that comes near. It teaches the skills it has through
common/training.lua, as shopkeeper does: on_speech (the word train, to which an NPC with nothing to teach says nothing),
on_context_menu and on_context_menu_select (the Train entries) and on_drag_drop (gold for a lesson). The quest entries of the context menu and the double click are the engine’s own.
shopkeeper.lua
Section titled “shopkeeper.lua”scripts/mobiles/shopkeeper.lua is the script of the NPC vendors. A player picks Buy or Sell in the vendor’s context menu,
or says “vendor buy” or “vendor sell” within 4 tiles, and the shop window opens. What a vendor sells is its shop in
templates/shops; the window, the prices and the purchase are the work of the server (the vendor module), not of the script.
Vendors also teach skills (common/training.lua), the jewelers engrave rings (common/engraving.lua), and while the event
halloween is on they answer “trick or treat” (common/trick_or_treat.lua). See Vendors.
common/guild.lua
Section titled “common/guild.lua”scripts/common/guild.lua is what the scripts of the guildmasters share. A player within 2 cells says the guildmaster’s name
and “join” or “member” to be told the price of its guild (500 gold), drops exactly that gold on it to join, and says its name
and “resign” or “quit” to leave, a week after joining at the earliest. The server keeps the membership (the npcguild
module). guild.listen(serial, speaker, text, keywords) handles the words and guild.drop(serial, giver, item) the gold.
guildstone_deed.lua, guildstone.lua and gumps/guild_menu.lua
Section titled “guildstone_deed.lua, guildstone.lua and gumps/guild_menu.lua”The player guilds, as the classic shard’s, against the NPC guildmasters above: a guild is founded on a stone, not joined by
gold. scripts/items/guildstone_deed.lua spends the deed, carried or on the ground within 2 tiles: a player in no guild is asked the
name (30275 to 30279 tell of the trouble) and picks the ground, a spot within 3 tiles that is standable and free (30293 when it
is not), and a guildstone is placed there that holds the guild by its guild and guild_founded props, the founder its
first member; the guild is made after the stone, and a refusal leaves the deed and no stone. scripts/items/guildstone.lua opens guild_menu for a member of the stone’s guild from within 2 tiles; a
stranger is told 30281 and a dissolved stone 30280. When another stone stands within 5 tiles the gump is opened with the
other guild, so the leader may declare on it. scripts/gumps/guild_menu.lua fills the gump: the roster paged, the leader
first, a click gives fealty, the leader writes the charter, the website and the hue and gives titles and ejection, and the
war and the alliance buttons work the other guild. The gump is named after the menu, not the module: a gump script owns the
global table of its own name. See Guilds.
common/holiday_decor.lua
Section titled “common/holiday_decor.lua”scripts/common/holiday_decor.lua puts the decorations of a holiday event around the center of the main towns, on Felucca and
on Trammel. place(event_id, templates) runs when an event starts, remove(event_id) when it ends. The serials are kept in the
world prop holiday.<event_id>.items, so the items go away even after a restart, and placing twice puts nothing twice.
common/trick_or_treat.lua
Section titled “common/trick_or_treat.lua”scripts/common/trick_or_treat.lua is the Halloween game. While the event halloween is on (data/schedule.toml), a player
who says “trick or treat” within 4 tiles of a shopkeeper gets a candy, or a trick. Each shopkeeper rests 5 to 10 minutes after
answering, and one saying is answered by one shopkeeper only. shopkeeper.lua calls listen from its on_speech.
events/christmas.lua and events/halloween.lua
Section titled “events/christmas.lua and events/halloween.lua”The hooks of the seasonal events of data/schedule.toml: on_start(id, name) decorates the towns
(common/holiday_decor.lua) and tells everybody, on_end(id, name) takes the decorations away and tells everybody. Halloween
has only these two; the game itself is common/trick_or_treat.lua. Christmas also has on_login(id, name, player): a
character that logs in during the event gets a gift once (two piles of snow, a holiday candle and one decoration), and not
again for 200 days. See Schedule.
common/quest.lua, common/quest_ui.lua and events/quest.lua
Section titled “common/quest.lua, common/quest_ui.lua and events/quest.lua”The quest engine, its gumps and the script that starts it and answers the server; see Quests for the format of a quest and how the pieces work.
gumps/go.lua
Section titled “gumps/go.lua”scripts/gumps/go.lua is the script of the gump of the named places (templates/gumps/go.xml) that .go
opens. rows(g, player, args) fills the “rows” slot with one level of data/locations.toml, the categories first and then the
places, twelve per page. A category opens the gump one level down, a place takes the traveller there. Staff only: anyone else
sees an empty gump.
definitions.lua
Section titled “definitions.lua”There is no definitions.lua among the shipped scripts: the engine generates it at startup, for editor completion, from the
modules, functions and enums that are registered (see Writing a Lua module). Do not edit it.
Made with ❤️ by Tom
