Create virtual (data-driven) items and mobs for Oxidus. Covers LPML data files, extension-based routing, virtual compile modules, base classes, and how to add new virtual object types.
You are helping create virtual items and mobs for Oxidus. Virtual objects are data-driven — instead of writing a .c file with code, you write an LPML data file containing properties. The system compiles it into a game object by cloning the appropriate base class and passing the data to virtual_setup().
/obj/food/cheese.food.c file exists at that path — the driver calls master->compile_object()VIRTUAL_D->compile_object(file).food) and routes to adm/daemons/modules/virtual/food.c/obj/food/cheese.lpml)new("/obj/food/food.c", data)virtual_setup() and configures itselfVIRTUAL_D (adm/daemons/virtual.c)
│
├── Extension routing: .food → modules/virtual/food.c
├── Extension routing: .loot → modules/virtual/loot.c
├── Extension routing: .mob → modules/virtual/mob.c
│
└── Directory routing: /d/ → modules/virtual/d.c (zones)
/player/ → modules/virtual/player.c
etc.
| File | Purpose |
|---|---|
adm/daemons/virtual.c | Central router — extracts extension, loads module |
adm/daemons/modules/virtual/ob.c | Base compile module for items (food, loot inherit from this) |
adm/daemons/modules/virtual/food.c | Compile module for .food files (inherits ob.c) |
adm/daemons/modules/virtual/loot.c | Compile module for .loot files (inherits ob.c) |
adm/daemons/modules/virtual/mob.c | Compile module for .mob files |
obj/food/food.c | Base class for food items (inherits STD_FOOD) |
obj/loot/loot.c | Base class for loot items (inherits STD_ITEM) |
std/mobs/monster.c | Base class for mobs (inherits STD_NPC) |
Virtual objects use LPML files (see the lpml skill for full syntax). Key points:
{} for objects, [] for arraysadditional ids, weapon name//// obj/food/cheese.lpml
{
id: ["cheese"],
additional ids: ["food", "snack"],
adj: ["small"],
name: "cheese",
short: "a small piece of cheese",
long: "This is a small, round piece of cheese. It looks tasty and would"
"make a good snack.",
mass: 10,
value: 15,
uses: 1,
properties: {
edible: true,
},
}
.food)Data file location: obj/food/{name}.lpml
Base class: obj/food/food.c (inherits STD_FOOD)
Compile module: adm/daemons/modules/virtual/food.c (inherits ob.c)
| Property | Type | Purpose |
|---|---|---|
id | string[] | Object identifiers |
additional ids | string[] | Extra IDs added via add_id() |
adj | string[] | Adjectives |
name | string | Object name |
short | string | Short description |
long | string | Long description |
mass | int | Weight |
value | int | Monetary value |
uses | int | Number of times it can be consumed |
properties | mapping | Custom properties (e.g., edible: true) |
.loot)Data file location: obj/loot/{name}.lpml
Base class: obj/loot/loot.c (inherits STD_ITEM)
Compile module: adm/daemons/modules/virtual/loot.c (inherits ob.c)
| Property | Type | Purpose |
|---|---|---|
id | string[] | Object identifiers |
additional ids | string[] | Extra IDs |
adj | string[] | Adjectives |
name | string | Object name |
short | string | Short description |
long | string | Long description |
mass | int | Weight |
value | int | Monetary value |
material | string[] | Material tags |
properties | mapping | Custom properties (e.g., crafting material: true) |
custom setup | function | Optional function called with the object |
Example (obj/loot/rabbit_fur.lpml):
{
id: ["fur", "patch"],
adj: ["rabbit"],
name: "rabbit fur",
short: "a patch of soft rabbit fur",
long: "This is a small patch of soft, fluffy fur.",
mass: 10,
material: ["fur"],
properties: {
autovalue: true,
crafting material: true,
},
}
.mob)Data file location: d/mobs/{name}.lpml
Base class: std/mobs/{type}.c (e.g., std/mobs/mammal.c inherits std/mobs/monster.c)
Compile module: adm/daemons/modules/virtual/mob.c
The type field in the LPML determines which mob base class is used.
| Property | Type | Purpose |
|---|---|---|
type | string | Mob type — maps to std/mobs/{type}.c (e.g., "mammal") |
name | string | Mob name |
short | string | Short description |
long | string | Long description |
id | string[] | Identifiers |
weapon name | string | Name of natural weapon |
weapon type | string | Damage type (e.g., "piercing") |
level | int or int[] | Fixed level or [min, max] range |
gender | string or string[] | Fixed or random from array |
race | string | Race classification |
loot | string[] | Paths to loot/food items dropped on death |
loot chance | float | Percentage chance to drop loot |
coins | mapping | Coin drops: { copper: [min, chance], ... } |
Example (d/mobs/waste_rat.lpml):
{
type: "mammal",
name: "waste rat",
short: "waste rat",
long: "A scrappy waste rat scurries through the debris, searching for "
"scraps to eat.",
id: ["waste rat", "rat"],
weapon name: "teeth",
weapon type: "piercing",
level: [1, 3],
gender: ["male", "female"],
race: "mammal",
loot: [
"/obj/food/rat_meat.food",
"/obj/loot/rat_fur.loot",
],
loot chance: 70.0,
coins: {
copper: [1, 20.0],
},
}
The base compile module (ob.c) follows this pattern:
/obj/{module}/{name}.lpmllpml_decode(read_file(lpml_file))new("/obj/{module}/{module}.c", data)The base class receives this data in virtual_setup():
varargs void virtual_setup(mixed args...) {
mapping data;
if(!args || !mapp(args[0])) return;
data = args[0];
if(!nullp(data["id"])) set_id(data["id"]);
if(!nullp(data["name"])) set_name(data["name"]);
if(!nullp(data["short"])) set_short(data["short"]);
if(!nullp(data["long"])) set_long(data["long"]);
if(!nullp(data["mass"])) set_mass(data["mass"]);
if(!nullp(data["value"])) set_value(data["value"]);
// ... type-specific properties
}
// Clone a virtual food item
object cheese = new("/obj/food/cheese.food");
// Clone a mob
object rat = new("/d/mobs/waste_rat.mob");
// Add to loot tables, room inventory, etc.
add_inventory("/obj/loot/rabbit_fur.loot");
No registration needed — the virtual system handles compilation automatically based on file extension.
To add an entirely new type (e.g., .potion):
obj/potion/potion.c:
inherit STD_ITEM;
varargs void virtual_setup(mixed args...) {
mapping data;
if(!args || !mapp(args[0])) return;
data = args[0];
if(!nullp(data["id"])) set_id(data["id"]);
if(!nullp(data["name"])) set_name(data["name"]);
if(!nullp(data["short"])) set_short(data["short"]);
if(!nullp(data["long"])) set_long(data["long"]);
if(!nullp(data["mass"])) set_mass(data["mass"]);
if(!nullp(data["value"])) set_value(data["value"]);
// ... potion-specific setup
}
adm/daemons/modules/virtual/potion.c:
inherit __DIR__ "ob";
That's it — inheriting from ob.c gives you the standard compile flow that reads /obj/potion/{name}.lpml and creates /obj/potion/potion.c with the data.
obj/potion/healing.lpml:
{
id: ["potion", "vial"],
adj: ["healing"],
name: "healing potion",
short: "a vial of {{CC0000}}healing potion{{res}}",
long: "A small glass vial filled with a glowing red liquid.",
mass: 5,
value: 50,
properties: {
hp restore: 25,
},
}
object potion = new("/obj/potion/healing.potion");
If no extension module matches, VIRTUAL_D tries:
/player/gesslar → modules/virtual/player.c/d/forest/5,10,0 → modules/virtual/d.cThis is how non-extension-based virtual objects (players, ghosts, zones) are routed.
.food extension routes to food.c module, which reads .lpml files. The extension in the path (cheese.food) is different from the data file (cheese.lpml).{}, [], unquoted keys, spacey keys) not raw LPC mapping syntax (([, ({).type property must match a file in std/mobs/ (e.g., "mammal" → std/mobs/mammal.c).virtual_setup(), which runs after the normal setup chain.inherit __DIR__ "ob" — the base module handles LPML reading, path construction, and object creation.