Skip to content

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.killer and corpse.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 — the trap.type prop — 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.

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 durability and max_durability props of the piece, at the max_hits of 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_min and damage_max (what item.combat_stats reads 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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.”.

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.

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.

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).

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.

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.

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.

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 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.

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.

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.

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.

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.

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).

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 hide command 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 AwardKarma takes 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 notice attempting 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.

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 trying to steal from .”. The skill waits 10 seconds from the press of the button, even if the cursor is put away (ModernUO keeps the cursor 30 seconds and 10 after the try). Not there: factions and sigils, stealable artifacts, the Young, safe zones, an open container as a condition, the stolen item going back to its victim when the thief dies within two minutes, and the permanent grey toward the victim.

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.

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.

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.

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.

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.

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.swing and 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.arrows and butte.bolts): they go to the backpack and the scores are cleared.

The dart boards are not built.

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".

  1. 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.
  2. 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.

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.

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.

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/8570 that 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.find of the gold template), capped by 10 + fame/2500 from 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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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).

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.

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.

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.

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.

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.

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.

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.

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.

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.

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).

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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").

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.

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".

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.

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.

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.

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.

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.

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.

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.

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

Buy me a coffee