KhymChanur: Here's what I've worked out for a unified magic device (and even non-magic device) system, so that wands, rods, orbs/staves and activatable equipment can all be handled by the same code; it will allows for a single object to have multiple spells/activations/etc. It should be generic enough that it can be placed in the engine for non-ToME modules to use.

Per-tval functions

There'll be a saying that a certain group of functions handles a particular tval (or groups of tvals), so you can have one set of functions handling activatable armor, another handling wands, etc (I'll be dealing with multiple spells/activations/etc per object later)

For each function, there's a flag-stored-function an object can have which, if presnet, will be used instead of the per-tval function.

choose_spell(obj): If the object contains multiple spells, choose which one to use.

can_use(obj): Can the object currently be aimed/zapped/activated/etc? This is primarily for things that can be activated every X + YdZ turns, so they can only be activated when the mana is fully recharged, but it could be used for other things, like non-magical mechanical devices that can be used even if the player has an antimagic field or shell.

get_spell_name(obj): Returns the name of the spell/activation (i.e., "identify", "beam of fire", etc)

get_spell_info(obj): Returns a string describing the spell (i.e., "radius 4", "duration 10 + 1d5", etc)

get_skill_level(obj): Returns the skill level with which the player uses the object. This could let us, for example, have the activation of swords be based on Sword Mastery rather than Magic Device, or have the skill level for wands, rods and orbs be the sum of Magic Device and Alchemy, to let alchemists be really really good at using them.

The return value may be altered by per-object hooks.

get_fail_pct(obj, level): Returns the failure percentage for using the device when used with the given skill level.

The return value may be altered by per object hooks, to enable things like the "of Simplicity" ego.

use_device(obj, level): Have the object be used. Wands use thaumaturgy spells, rods use runecraft spells, activatable equipment might use functions stored in the flag registry, and so on, so different tvals will need different. The device framework will handle consuming charges.

Egos like "of Cheapness" are already handled by the object mana framework.

get_energy_use(obj, level): How much time (energy) it took the player to use the object.

The return value may be altered by per-object hooks, to enable things like the "of Quickness" ego.

Object flags

DEVICE_COST: The amount of mana it takes to use the device.

DEVICE_COST_DICE and DEVICE_COST_SIDES: Random mana cost to add to the fixed DEVICE_COST value. Primarily for use by "can be activated every X + YdZ turns" type objects, but could be used to make wands which consume a variable amount of mana per zap.

Multiple spells per object

There are two methods obvious to me as to how to do multiple spells per object (though there are probably more I haven't thought of). One is that, for multiple spells, the device information is stored in flagsets-as-array of integers (or whatever data type), rather than integers, like thus:

    THAUM_SPELL = getter.array{thaum_spell1, thuam_spell2, ...}
    DEVICE_COST = getter.array{cost1, cost2, ...}

The other is to store an array of flagsets into a single flag, like thus:

    DEVICE_USE = getter.arry{
        getter.flags{THUAM_SPELL=thaum_spell1, DEVICE_COST=cost1},
        getter.flags{THAUM_SPELL=thaum_spell2, DEVICE_COST=cost2},
        ...
    }

In the first instance, if an object has multiple spells, the can_use(), get_skill_level(), etc functions will be passed an extra idx parameter to indicate which spell the code is interested in. In the second case, each function will always be given as a parameter the flagset of the chosen spell, even if the object only had one spell.

A default function will be supplied, which will ask the user to choose a spell in the same manner as choosing a spell from a spellbook. However, other methods can be used, so you could have a rod with a dial on it which choose the spell to use, and setting the dial and using the rod are two different actions.

Chatter

Orvin: Can get_fail_pct be made to depend on other player properties, besides skill level(s)? For example, the success of activating the Armor of the Rohirrim might depend on the player's horseback riding skill and whether the player's race is RACE_ROHIRRIM (separate from any horseback riding skill benefits that race might confer).

DarkGod: Orvin, if it is a funciton, you can do whatever you want in it :)

Khym, I like the proposal a lot! Just one thing about DEVICE_COST, some objects might have variable mana cost(orbs do, they contain school spells whose mana cost varies with the spell level, and thus the skill level), how do you propose to handle that ?

As for how to store the spells info, I prefer the second method, it makes it more obvious which spell is associated with which cost. We could actually add in the fields info on the activation too, so we could have multi-type spells in the same objects, like:

    DEVICE_USE = getter.arry{
        getter.flags{THAUM_SPELL=thaum_spell1, DEVICE_COST=cost1, DEVICE_TYPE=devices.THAUM_SPELL},
        getter.flags{SCHOOL_SPELL=FIREFLASH, DEVICE_COST=cost2, DEVICE_TYPE=devices.SCHOOL_SPELL},
        ...
    }

DEVICE_TYPE would point to a previously registered device subsystem which would define all the functions you mention in the first part of the spec. This way you don't even need to rely on TVAL(which is kinda ugly), just make DEVICE_TYPE=devices.THAUM_SPELL a default flag for wands in items/items.lua

KhymChanur: Ah, good point, hadn't thought about having thuam spells and school spells in the same object. In that case, I guess instead of having a registered choose_spell() function, that the user interface for choosing a spell would be outside of the system, and the system would just have to deal with executing the spell that was chosen.

For the mana cost, each device subsystem could have a get_cost() function, and some of those function could use the DEVICE_COST flag, while others could figure it out by the school spell and casting level.

DarkGod: Sounds good then :)

Developers Corner/Proposals/Unified magic device system (last edited 2006-05-29 08:07:14 by DarkGod)