Difference between revisions of "Modding:Tutorial/Custom Items"

From DoomRL Wiki

Jump to: navigation, search
(some armor/boots stuff)
(added a bunch of other stuff, still need examples for some)
Line 39: Line 39:
 
*''firstmsg'' is what will appear in the message area the very first time any item from this object prototype is picked up.
 
*''firstmsg'' is what will appear in the message area the very first time any item from this object prototype is picked up.
 
*''color_id'' sets the id of the item for color-binding purposes. DoomRL levers, for instance, are all set to the same color_id, so that they cannot be disguished by a clever player manipulating color.lua carefully.
 
*''color_id'' sets the id of the item for color-binding purposes. DoomRL levers, for instance, are all set to the same color_id, so that they cannot be disguished by a clever player manipulating color.lua carefully.
*''res_[damage_type]'' sets the resistance for a particular damage type onto the item. This is only important for weapons, armor, and boots, as the resistance can only help if the item is equipped.
+
*''res_[damage_type]'' sets the resistance for a particular damage type onto the item. This is only important for weapons, armor, and boots, as the resistance can only help if the item is equipped. (Note that DAMAGE_IGNOREARMOR has no resistance.)
 
*''type'' is, quite possibly, the most important key for an item: it sets the item's type, which then determines what additional keys are required/allowed, what engine hooks it can use, and what properties it has. Each type will be explained over the course of the tutorial.
 
*''type'' is, quite possibly, the most important key for an item: it sets the item's type, which then determines what additional keys are required/allowed, what engine hooks it can use, and what properties it has. Each type will be explained over the course of the tutorial.
 
*''ascii'' is the character that is used for the item on the map. Although this isn't explicitly a part of the base prototype, it exists across all item types, the only difference being its default character (which will be included with each type in this tutorial).
 
*''ascii'' is the character that is used for the item on the map. Although this isn't explicitly a part of the base prototype, it exists across all item types, the only difference being its default character (which will be included with each type in this tutorial).
Line 51: Line 51:
 
All items, both map and inventory, come with two properties and two engine hooks:
 
All items, both map and inventory, come with two properties and two engine hooks:
  
*Prototype Keys:
+
*Properties:
 
**''itype'' is the item type in property form. As _NONE, an item cannot be picked up or used.
 
**''itype'' is the item type in property form. As _NONE, an item cannot be picked up or used.
 
**''proto'' is the prototype of the instantiated item.
 
**''proto'' is the prototype of the instantiated item.
Line 110: Line 110:
 
</source>
 
</source>
  
*First, we use OnCreate() to determine a random location. This is done by using coord.random(), which returns a random coordinate between the two given coordinates. area.get() returns the upper-left and bottom-right coordinates of a given area: by selecting area.FULL (which is a pre-defined area that covers the entire map), we return the boundary coordinates of the map, which are then called into coord.random() to give us a random coordinate anywhere on the map.
+
*First, we use OnCreate() to determine a random location. This is done by using the coord.random() method, which returns a random coordinate between the two given coordinates. area.get() returns the upper-left and bottom-right coordinates of a given area: by selecting area.FULL (which is a pre-defined area that covers the entire map), we return the boundary coordinates of the map, which are then called into coord.random() to give us a random coordinate anywhere on the map.
*After we grab this location, we use thing:add_property() which takes in the key of the property to be added and the value it should be given. For our cases, we make a key called "exit_pt" (exit point) and set its value to the random coordinate we found.
+
*After we grab this location, we use the thing:add_property() method, which takes in the key of the property to be added and the value it should be given. For our cases, we make a key called "exit_pt" (exit point) and set its value to the random coordinate we found.
*Finally, we use the function thing.displace() to move the being, and place it within OnEnter() so that it will occur whenever the being enters the same tile as the teleporter item.
+
*Finally, we use the method thing.displace() to move the being, and place it within OnEnter() so that it will occur whenever the being enters the same tile as the teleporter item.
  
 
This example doesn't handle problems such as locations that exist in a cell that blocks movement, however, so it should be improved upon if you want a more useful random teleporter.
 
This example doesn't handle problems such as locations that exist in a cell that blocks movement, however, so it should be improved upon if you want a more useful random teleporter.
Line 139: Line 139:
 
</source>
 
</source>
  
This is about as basic as you can get, using an almost-minimal number of prototype keys. the OnPickup() function displays a message identical to the envirosuit pack's, and then the player is given the enviro status with player:set_affect(status,duration). Note that duration is based on how many actions the player takes, not how many scounts/turns/seconds (that is, affects work on those on OnAction() hook).
+
This is about as basic as you can get, using an almost-minimal number of prototype keys. the OnPickup() function displays a message identical to the envirosuit pack's, and then the player is given the enviro status with the player:set_affect(status,duration) method (and receives a message using the ui.msg() method). Note that duration is based on how many actions the player takes, not how many scounts/turns/seconds (that is, affects work using an OnAction() hook).
  
 
===Lever (_LEVER)===
 
===Lever (_LEVER)===
Line 212: Line 212:
 
==Inventory Items==
 
==Inventory Items==
  
Inventory items, in addition to the keys and hooks mentioned with map items, has the ''desc'' prototype key, which is a required string that describes the item in the player's inventory.
+
Inventory items, in addition to the keys and hooks mentioned with map items, has the ''desc'' prototype key, which is a required string that describes the item in the player's inventory on the sidebar, and the ''desc'' property, which is the name of the item in the inventory (e.g., modified chaingun (1d7x6) [40/40] (P1F1)).
  
 
The more significant items are inventory items, most importantly equipment. There is, however, a lot to keep track of when creating these items.
 
The more significant items are inventory items, most importantly equipment. There is, however, a lot to keep track of when creating these items.
  
 
===Consumable (_PACK)===
 
===Consumable (_PACK)===
 
 
Consumable items are added into the inventory and directly used from it some number of times. In the base game, there are either items that are used once (at which point they are consumed), or can be used any number of times so long as the conditions are correct. Consumables use '+' as the default ASCII character.
 
Consumable items are added into the inventory and directly used from it some number of times. In the base game, there are either items that are used once (at which point they are consumed), or can be used any number of times so long as the conditions are correct. Consumables use '+' as the default ASCII character.
  
Line 275: Line 274:
  
 
===Ammunition (_AMMO) and Ammo Pack (_AMMOPACK)===
 
===Ammunition (_AMMO) and Ammo Pack (_AMMOPACK)===
 
 
Ammunition is an inventory item that automatically adds itself to weapons as necessary. (See Ranged Weapon for details regarding how this is accomplished.) They are a fairly simple item that uses a few extra prototype keys in order to keep track of the numbers regarding its capacity. Ammunition uses '|' as the default ASCII character.
 
Ammunition is an inventory item that automatically adds itself to weapons as necessary. (See Ranged Weapon for details regarding how this is accomplished.) They are a fairly simple item that uses a few extra prototype keys in order to keep track of the numbers regarding its capacity. Ammunition uses '|' as the default ASCII character.
  
Line 285: Line 283:
 
Items{
 
Items{
 
     ....
 
     ....
     type = ITEMTYPE_AMMO, --use this or ITEMTYPE_AMMOPACK
+
     type = ITEMTYPE_AMMOPACK, --use this or ITEMTYPE_AMMOPACK
     ammo = 50,           --required field
+
     ammo = 50,               --required field
     ammomax = 100,       --required field
+
     ammomax = 100,           --required field
     ammoID = "shell",     --required field (ammo packs only)
+
     ammoID = "shell",         --required field (_AMMOPACK only)
 
}
 
}
 
</source>
 
</source>
Line 295: Line 293:
 
*''ammomax'' is how much ammo can fit into a single ammo or ammo pack item. For ammo packs, this should be equal to ''ammo'' as they cannot be added to.
 
*''ammomax'' is how much ammo can fit into a single ammo or ammo pack item. For ammo packs, this should be equal to ''ammo'' as they cannot be added to.
 
*''ammoID'' is the kind of ammo that the ammo pack uses, for reloading and unloading purposes. Only include this key with ammo packs.
 
*''ammoID'' is the kind of ammo that the ammo pack uses, for reloading and unloading purposes. Only include this key with ammo packs.
 +
 +
These keys also correspond exactly to the ammo and ammo pack properties for a particular item instance: the only slight exception is that ammoID as a property is called "ammoid".
  
 
There isn't a lot you can do with ammunition other than create the necessary rounds for any custom weapons. You will be able to copy and paste a basic ammo prototype, for the most part:
 
There isn't a lot you can do with ammunition other than create the necessary rounds for any custom weapons. You will be able to copy and paste a basic ammo prototype, for the most part:
Line 300: Line 300:
 
<source lang="lua">
 
<source lang="lua">
 
Items{
 
Items{
     name = "some kinda ammo",
+
     name = "some kind of ammo",
 
     id  = "anammo",
 
     id  = "anammo",
 
     sprite = 0,
 
     sprite = 0,
Line 312: Line 312:
 
</source>
 
</source>
  
Ammo packs, on the other hand, can be customized a little bit if you want to be creative. Since they can be equipped, you can technically make use of the resistance prototype keys to grant extra resistance for equipping an ammo pack.  
+
Ammo packs, on the other hand, can be customized a little bit if you want to be creative. Since they can be equipped, you can technically make use of the resistance prototype keys to grant extra resistance for equipping an ammo pack. In addition, the equipping hooks( OnEquip(), OnEquipCheck(), and OnEquipTick()) can also be used.
  
 
===Armor (_ARMOR) and Boots (_BOOTS)===
 
===Armor (_ARMOR) and Boots (_BOOTS)===
 
+
Armor and boots items are purely meant to be equipped, and use the various properties that come with equipping protective gear. Armor uses '[' as its default ASCII character, and boots use ';' as its default ASCII character.
Armor and boots items are purely meant to be equipped, and use the various properties that come with equipping protective gear. Armor uses '[' as its default ASCII character, and boots uses ';' as its default ASCII character.
+
  
 
Armor and boots share the following additional prototype keys:
 
Armor and boots share the following additional prototype keys:
Line 336: Line 335:
 
*''movemod'' is the movespeed modifier of the item. Increased movespeed makes your movement faster.
 
*''movemod'' is the movespeed modifier of the item. Increased movespeed makes your movement faster.
  
 +
The most common hooks used with armor and boots are OnEquip(), OnEquipCheck(), and OnEquipTick(), since their primary function is to be equipped to their appropriate slots. They also come with the following properties:
 +
 +
*''armor'' is the protection value of the item instance, as determined by the ''armor'' prototype key.
 +
*''durability'' is the current value of the item instance's durability.
 +
*''maxdurability'' is the maximum value of the item instance's durability, as determined by the ''durability'' prototype key.
 +
*''movemod'' is the movespeed modifier of the item instance, as determined by the ''movemod'' prototype key.
 +
*''knockback'' is the knockback modifier of the item instance, as determined by the ''knockmod'' prototype key.
 +
 +
=== Weapon (_MELEE, _RANGED _NRANGED)===
 +
Weapon items are what allow beings to attack with a greater ferocity than their ToDam and ToHit modifiers will allow. They replace the [[fists]] "weapon" when equipped in the weapon slots, and can also be equipped to the prepped slot. There are three item types that all count as weapons:
 +
 +
*Melee weapons (_MELEE) only allow the being to attack as they would with their fists (i.e., adjacent to their position). The alt fire can be a ranged attack of sorts (e.g., [[alternate fire#throw|throw]]) but cannot use ammo to do so. They are the most limited weapon type in terms of additional functionality. A melee weapon uses "\" as its default ASCII character.
 +
*Ranged weapons (_RANGED) allow the being to attack anything within its vision and the weapon's own maximum range. This type of weapon requires ammo in order to fire. A ranged weapon uses "}" as its default ASCII character.
 +
*Natural ranged weapons(_NRANGED) are similar to ranged weapons, except that a few prototype keys (such as ammoID) are avoided. The main purpose of natural ranged weapons is to give non-player beings a means to attack at range (i.e., an imp's fireball attack is a natural ranged weapon). A natural ranged weapon uses "?" as its default ASCII character.
 +
 +
Weapons share the following additional prototype keys:
 +
 +
<source lang="lua">
 +
Items{
 +
    ....
 +
    type          = ITEMTYPE_RANGED, --use this, or ITEMTYPE_MELEE, or ITEMTYPE_NRANGED
 +
    damage        = "4d3",            --required field
 +
    damagetype    = DAMAGE_BULLET,  --required field
 +
    group        = "weapon-pistol", --default is "weapon-other"
 +
    fire          = 12,              --default is 10
 +
    acc          = 6,              --default is 0
 +
    radius        = 0,              --default is 0 (_RANGED and _NRANGED only)
 +
    shots        = 1,              --default is 0
 +
    ammoID        = "ammo",          --required field (_RANGED only)
 +
    ammomax      = 10,              --required field (_RANGED only)
 +
    reload        = 12,              --default is 10 (_RANGED only)
 +
    shotcost      = 1,              --default is 0 (_RANGED only)
 +
    altfire      = ALT_AIMED,      --default is ALT_NONE (_MELEE and _RANGED only)
 +
    altfirename  = "aimed shot",    --default based on altfire (_MELEE and _RANGED only)
 +
    altreload    = RELOAD_FULL,    --default is RELOAD_NONE (_RANGED only)
 +
    altreloadname = "full",          --default based on altreload (_RANGED only)
 +
    soundID      = "pistol",        --default is id (_RANGED and _NRANGED only)
 +
    missile      = "bullet",        --required field (optional for _MELEE)
 +
}
 +
</source>
 +
 +
*''damage'' is the weapon's damage, written in [[dice notation]].
 +
*''damagetype'' is the weapon's [[damage type]], one of seven possibilities. (See[[Modding:Constants#DamageType|DamageType]] for identifiers.)
 +
*''group'' is the group that the weapon belongs to, for statistical purposes. Note that setting this has no effect on weapon-specific traits like [[Son of a Gun]] or [[Shottyman]]: these are determined by the weapon's flags.
 +
*''fire'' is how long it takes to attack with the weapon, in game turns.
 +
*''acc'' is the [[accuracy]] of the weapon.
 +
*''radius'' is the [[explosion]] blast radius of the weapon's projectile. If radius is set to 0, no explosion occurs.
 +
*''shots'' is the number of times the weapon attacks or fires projectiles. The default, 0, still causes one attack to occur.
 +
*''ammoID'' is the identifer of the ammo that is to be used in this ranged weapon. This is what the weapon will look for in the player's inventory (or prepped slot in the case of ammo packs) when reloading.
 +
*''ammomax'' is the maximum amount of ammo that the weapon can hold, also known as its capacity or clip size.
 +
*''shotcost'' is the amount of ammo used to fire a single projectile of the weapon. The default, 0, still takes one ammo per projectile.
 +
*''altfire'' is the identifer of an alternate fire mode for the weapon. This can either be one of several [[Modding:Constants#AltFire|pre-defined alternate fires]] or the name of a custom function.
 +
*''altfirename'' is the name of the alternate fire mode, as it appears in the inventory screen. Most of the pre-defined alternate fires have such a name as its default, and in those cases this field can be ignored.
 +
*''altreload'' is the identifer of an alternate reload mode for the weapon. This can either be one of several [[Modding:Constants#AltReload|pre-defined alternate reloads]] or the name of a custom function.
 +
*''altreloadname'' is the name of the alternate fire mode, as it appears in the inventory screen. As with altfirename, this can likely be ignored if using a pre-defined alternate reload.
 +
*''soundID'' is the sound of the projectile as the weapon fires. By default, it uses the same ID as the weapon's, so there is little reason to include your own.
 +
*''missile'' is the identifier of the missile that this weapon fires. There are a few pre-defined missiles in the base game that can be used, or you can use your own. In addition, the missile's definition can be inlined into the weapon (see [[Modding:Tutorial/Game_Objects#Prototype|here]] for the basic structure).
 +
 +
Weapons also come with quite a few properties, although many can be directly related to a prototype key. The following are exactly related:
 +
 +
*''ammo''
 +
*''ammomax''
 +
*''acc''
 +
*''missile''
 +
*''shots''
 +
*''shotcost''
 +
*''damagetype''
 +
*''altfire''
 +
*''altreload''
 +
 +
The following properties are different only by name:
 +
 +
*''ammoid'' is the property of ''ammoID''
 +
*''blastradius'' is the property of ''radius''
 +
*''reloadtime'' is the property of ''reload''
 +
*''usetime'' is the property of ''fire''
  
 +
Finally, the damage prototype key is broken into three separate properties:
  
===Melee Weapon (_MELEE)===
+
*''damage_sides'' is the number after the "d"
 +
*''damage_dice'' is the number before the "d"
 +
*''damage_add'' is a constant added to all damage rolls (acts like the ToDam prototype field for beings)
  
===Ranged Weapon (_RANGED) and Natural Ranged Weapon (_NRANGED)===
+
Weapons are a very important facet of the game, and so a separate tutorial will be prepared to handle examples and explanations for many different kinds of weapons.
_ARMOR uses '['
+
_BOOTS uses ';'
+
_AMMO uses '|'
+
_AMMOPACK uses '!'
+
_RANGED uses '}'
+
_NRANGED uses '?'
+
_MELEE uses '\'
+

Revision as of 19:13, 23 September 2011

In the following tutorial you will learn the basics of item objects. Unique to item objects is the object's type, which carries with it a number of different prototype keys, engine hooks, and propertes. We will learn about each item type and what can be done with them to fit your particular item needs. In some cases items will appear roughly indistinguishable from cells: the defining factor here is that they can both exist on the same tile, whereas two cells or two items cannot.

Contents

Base Prototype

Although this was more-or-less explained in the Game Objects tutorial, we will further explore each key in the base prototype.

Items{
    name         = "an item",          --required field
    id           = "generic",          --defaults to 'name'
    sprite       = 0,                  --required field; always set to 0 for now
    overlay      = 0,                  --defaults to 0
    color        = WHITE,              --defaults to LIGHTGRAY
    level        = 1,                  --required field
    weight       = 1000,               --required field
    flags        = {},                 --default depends on item type
    set          = "",                 --defaults to ""
    firstmsg     = "You got an item!", --defaults to ""
    color_id     = "generic",          --defaults to 'id'
    res_bullet   = 0,                  --defaults to 0
    res_melee    = 0,                  --defaults to 0
    res_shrapnel = 0,                  --defaults to 0
    res_acid     = 0,                  --defaults to 0
    res_fire     = 0,                  --defaults to 0
    res_plasma   = 0,                  --defaults to 0
    type         = ITEMTYPE_NONE       --required field
    ascii        = "?"                 --default depends on item type
}
  • name is what the item appears to be in-game (e.g. using the 'look' command).
  • id is the item identifier, to be used in lua whenever you want to call the item prototype.
  • sprite is what will eventually be the graphical tile of the item.
  • overlay, like sprite, is based on there being graphical tiles in DoomRL. You can ignore this key entirely for the time being.
  • color is one of 16 (4-bit) colors that you can use to distinguish the item on the map. For items that can be picked up, this is also the color of the item's name in your inventory/equipment screens.
  • level is the minimum level that the item can appear on, for random item generation purposes.
  • weight this affects the frequency that the item will appear, for random item generation purposes. Both level and weight are only important on levels that randomly generate items using Level.flood_items().
  • flags defines what item flags you give your item. It is recommended that you figure out your item's type before adding flags, as some are only important for certain types.
  • set defines what item set the item is in. For instance, the gothic armor/boots and phaseshift armor/boots are part of item sets. Item sets themselves are separate objects that must be defined before any item objects that use them.
  • firstmsg is what will appear in the message area the very first time any item from this object prototype is picked up.
  • color_id sets the id of the item for color-binding purposes. DoomRL levers, for instance, are all set to the same color_id, so that they cannot be disguished by a clever player manipulating color.lua carefully.
  • res_[damage_type] sets the resistance for a particular damage type onto the item. This is only important for weapons, armor, and boots, as the resistance can only help if the item is equipped. (Note that DAMAGE_IGNOREARMOR has no resistance.)
  • type is, quite possibly, the most important key for an item: it sets the item's type, which then determines what additional keys are required/allowed, what engine hooks it can use, and what properties it has. Each type will be explained over the course of the tutorial.
  • ascii is the character that is used for the item on the map. Although this isn't explicitly a part of the base prototype, it exists across all item types, the only difference being its default character (which will be included with each type in this tutorial).

Item types are always written as ITEMTYPE_[type], where [type] is the actual type of the item. A shorthand o this form (e.g., "_RANGED") will be used throughout the tutorial.

Generally speaking, there are two practical groups by which item types can be categorized: inventory items and map items. Inventory items are anything that can be picked up and carried with you, while map items cannot. This categorization is used mostly for the convenience of this tutorial, as it is easier to find the item type you are looking for based on whether or not the item goes into your inventory.

Map Items

All items, both map and inventory, come with two properties and two engine hooks:

  • Properties:
    • itype is the item type in property form. As _NONE, an item cannot be picked up or used.
    • proto is the prototype of the instantiated item.
  • Engine hooks:
    • OnCreate() triggers whenever the item is created. Its primary function is to add properties to an item they may otherwise change from instance to instance.
    • OnEnter() triggers whenever a being enters the same tile as the item. Although technically it can be used for any item, it is most known for its coupling with the teleporter item type.

Map types have fewer properties and hooks associated with them, but they are by no means difficult to work with.

Deadweight (_NONE)

Deadweight items are, quite literally, placeholders. They serve little purpose other than display or preventing another item from being dropped onto its tile. It is technically possible to change a deadweight item that exists on the map into a different type, at which point other properties could be applied to it and would act similarly to another item type: however, with the engine hooks supplied in those other item types, there is almost never a need to do this.

The following is an example of a deadweight item:

Items{
    name   = "moss"
    sprite = 0,
    color  = GREEN
    ascii  = "~"
    level  = 1,
    weight = 0,
--  flags  = {IF_NODESTROY, IF_NUKERESIST},
    type   = ITEMTYPE_NONE,
 
    function OnEnter(_,being)
        being.scount = being.scount - 100
    end,
}

Since we don't want this as a part of the items randomly generated, the weight is set to zero. The two flags, IF_NODESTROY (prevents item destruction by splash-damage explosions) and IF_NUKERESIST (prevents item destruction by nuclear explosion) are about the most use you could get out of a deadweight item, essentially allowing the it to stay put regardless of what the player may throw at it. (In the case of moss, however, these flags are unwanted, so it is commented out.) Finally, we use an OnEnter() hook so that anything that enters a tile with moss effectively takes a bit longer to move in it. The property "scount" is measured such that 1 turn = 100 scounts, so this would add an additional 1 turn to any move into moss.

Teleporter (_TELE)

Teleporter items are almost identical to deadweight items, except that they must define an OnEnter() hook, since it is so pivotal in their function. Teleporters use '*' as their default ASCII character.

In the case of DoomRL's base game, teleporter items are given an extra property using the OnCreate() hook that defines a particular coordinate on the map. This coordinate is then used during OnEnter() to teleport a being that enters the teleporter. Since we want the location to be different for every teleporter, this is why we don't have a "location" prototype key and, instead, have to alter its properties. A basic example is given below:

Items{
    name   = "teleporter",
    id     = "port",
    sprite = 0,
    color  = LIGHTBLUE,
    level  = 1,
    weight = 0,
    type   = ITEMTYPE_TELE,
 
    function OnCreate(self)
        local location = coord.random(area.get(area.FULL))
        self:add_property("exit_pt",location)
    end,
 
    function OnEnter(_,being)
        being.displace(exit_pt)
    end
}
  • First, we use OnCreate() to determine a random location. This is done by using the coord.random() method, which returns a random coordinate between the two given coordinates. area.get() returns the upper-left and bottom-right coordinates of a given area: by selecting area.FULL (which is a pre-defined area that covers the entire map), we return the boundary coordinates of the map, which are then called into coord.random() to give us a random coordinate anywhere on the map.
  • After we grab this location, we use the thing:add_property() method, which takes in the key of the property to be added and the value it should be given. For our cases, we make a key called "exit_pt" (exit point) and set its value to the random coordinate we found.
  • Finally, we use the method thing.displace() to move the being, and place it within OnEnter() so that it will occur whenever the being enters the same tile as the teleporter item.

This example doesn't handle problems such as locations that exist in a cell that blocks movement, however, so it should be improved upon if you want a more useful random teleporter.

Naturally, teleporter items don't need to be used as teleporters only: they are only named as such because of their unique use in the main game of DoomRL.

Powerup (_POWER)

Powerup items (or powerups) use a required OnPickup() hook in order to produce the result as seen in-game. They are meant to be consumed on use, and OnPickup() automatically takes care of this consideration. Powerups use '^' as their default ASCII character.

Here is a quick example of a powerup that makes use of the envirosuit pack's effect:

Items{
    name = "envirosuit"
    sprite = 0,
    color = LIGHTGRAY,
    level = 9,
    weight = 200,
    type = ITEMTYPE_POWER,
 
    function OnPickup()
        ui.msg("You feel protected!")
        player:set_affect(STATUSGREEN,100)
    end,
}

This is about as basic as you can get, using an almost-minimal number of prototype keys. the OnPickup() function displays a message identical to the envirosuit pack's, and then the player is given the enviro status with the player:set_affect(status,duration) method (and receives a message using the ui.msg() method). Note that duration is based on how many actions the player takes, not how many scounts/turns/seconds (that is, affects work using an OnAction() hook).

Lever (_LEVER)

Levers are map items that come with the OnUse() and OnUseCheck() hooks, which serve as their primary function. In the main game, they are randomly strewn throughout the game in lever rooms, and there are a few specially-crafted ones in special levels. Levers use '&' as their default ASCII character.

Levers come with the following additional prototype keys:

Items{
    ....
    type       = ITEMTYPE_LEVER,                           --required type for a lever
    good       = "neutral",                                --default is ""
    desc       = "thermostat",                             --default is ""
    soundID    = "lever",                                  --default is "lever"
    fullchance = 10,                                       --default is 0
    warning    = "This place looks fully air-conditioned." --default is ""
}
  • good is what what the lever shows in parentheses for a player with BF_LEVERSENSE1 (equivalent to having one rank in Intuition). By convention, this is "beneficial", "neutral", or "dangerous".
  • desc is like good, but only displays for a player with BF_LEVERSENSE2 (equivalent to having two tanks in Intuition).
  • soundID is the sound binding for the lever, which plays the sound automatically during OnUse().
  • fullchance is the chance that, when randomly generated, the lever's effect will trigger across the entire map. This is mostly useless to modders, as the generation that creates levers does not allow for custom levers to be added to it.
  • warning is the game message that appears at the start of a level with this lever if fullchance is true.

Some levers, by convention, are also given an extra property that references a particular area of the map, so that their effect can know where to get the job done. For levers such as those that remove walls, add a fluid, or hurt monsters, this is the property they use in order for their effect to work properly, and it is these levers that make use of the fullchance and warning fields. The generator itself has an algorithm to find rooms and return its area, but we can also define much simpler areas, as shown below:

Items{
    name       = "lever",
    id         = "lever_gift_drop",
    color_id   = "lever",
    level      = 13,
    weight     = 50,
    type       = ITEMTYPE_LEVER,
    good       = "neutral",
    desc       = "drops random items",
    soundID    = "lever",
 
    function OnCreate(self)
        local location = area.around(self:get_position(),1) 
        self:add_property(drop_pt,location)
        self:add_property(times_used,"0")
        self:add_property(total_use,math.random(3))
    end,
 
    function OnUseCheck(self)
        if self.times_used == self.total_use then
            ui.msg("Nothing happens.")
            return(false)
        end
        self.times_used = self.times_used + 1
        return(true)
    end,
 
    function OnUse()
        ui.msg("An item materializes!")
        item = table.random_pick{"lmed","epack","pammo","pshell","procket","pcell"}
        Level.area_drop(self.drop_pt,item)
    end,
}

Levers aren't complicated but they tend to have a number of steps, so we'll go through each piece separately:

  • OnCreate() adds three properties to the lever: the target area (drop_pt), the number of times it can be used (total_use), and the number of times it has already been used (times_used). The target area is based on its surroundings using area.around(), which takes in a starting-point coordinate (found by thing:get_position()) and a number that corresponds to how many tiles around the coordinate you want to include. Our location is a 3x3 area centered about the lever, which we add to drop_pt.
  • OnUseCheck() is how we determine if the lever can be used again, by comparing times_used to total_use. The hook itself requires a boolean return value: if true, then OnUse() can be called, if false, OnUse() is ignored when you try it use it. times_used starts at 0, but each successful OnUseCheck() increments it by one. Once it is equal to total_use, OnUseCheck() will return a false value, resulting in the lever doing nothing.
  • OnUse() performs our lever's action. First we display the message that the lever has, indeed, done something. Then we randomly choose from a few item identifiers (specifically we choose from a large med-pack, envirosuit pack, and all four of the ammo packs). Finally, the chosen item is dropped into the area we designated through drop_pt.

The result of this lever is to drop an random (and pretty good) item each time it is used, and can be pulled anywhere from one to three times. (I can't say I wouldn't be happy to see such a lever in the real game!)

Inventory Items

Inventory items, in addition to the keys and hooks mentioned with map items, has the desc prototype key, which is a required string that describes the item in the player's inventory on the sidebar, and the desc property, which is the name of the item in the inventory (e.g., modified chaingun (1d7x6) [40/40] (P1F1)).

The more significant items are inventory items, most importantly equipment. There is, however, a lot to keep track of when creating these items.

Consumable (_PACK)

Consumable items are added into the inventory and directly used from it some number of times. In the base game, there are either items that are used once (at which point they are consumed), or can be used any number of times so long as the conditions are correct. Consumables use '+' as the default ASCII character.

Consumables must include a 'desc' prototype field that describes the item in the inventory, as well as an OnUse() hook by which the item can be used. However, they can also use the OnUseCheck(), OnPickup(), OnFirstPickup(), and OnPickupCheck() hooks if need be. The following example includes most of these hooks:

Items{
    name = "holy cross",
    id = "hcross",
    level = 22,
    weight = 0,
    color = WHITE,
    type = ITEMTYPE_PACK,
 
    function OnPickupCheck()
        wpn_check  = player.eq[weapon] == "spear"
        armr_check = player.eq[torso] == "aarmor"
        if not (wpn_check and armr_check) then
            ui.msg("You must prove yourself worthy of using this!")
            return(false)
        else
            return(true)
        end
    end,
 
    function OnFirstPickup()
        ui.msg("You hear angels singing!"
        player.hp = player.hpmax * 2
        player.tired = false
    end,
 
    function OnUseCheck()
        if player.tired == false
            ui.msg("You are too tired to use this.")
            return(false)
        else
            return(true)
        end
    end,
 
    function OnUse()
        ui.msg("You are flooded with holy energy!"
        player.hp = player.hpmax * 2
        player:set_affect(STATUSINVERT,20)
        player.tired = true
        return(false)
    end,
}
  • The first hook, OnPickupCheck(), checks to see whether or not the item can picked up at all. The conditions use for this are that the player is holding the Longinus Spear (spear) in their weapon slot and the Angelic Armor (aarmor) in their torso slot. Quite a hefty requirement! If these conditions are not met, the game gives you a vague message regarding your failure.
  • The second hook, OnFirstPickup(), activates only the very first time you pick up the item. In fact, this hook is only called the first time you pick up any item with this prototype (this is why chainsaw gives you a berserk effect in the Chained Court but not again if you happen to be lucky enough to find a second one). On this first pickup, we get another message, a supercharge-like health effect, and the player's tactics are reset.
  • The third hook, OnUseCheck(), determines whether or not the item can be used from the inventory by checking to see if the player's tactics are on 'cautious' (this is the false value for tired). If so, the player will use the item: otherwise, it will let the player know why.
  • The final hook, OnUse(), activates the item's effect: another supercharge in health, and invincibility for 20 actions. Upon doing so, the player's tactics are set to 'tired' (this is the true value). Finally, as OnUse() takes a boolean result to see whether or not the consumable is actually consumed, we set it to false: this means that the item will stay in the player's inventory and can be used whenever OnUseCheck() will return a true value. (Basically, it's like the Arena Master's Staff but with a more useful ability.)

Such items in the base game have been coined "Relics", and there's a reason they are so rare. (Though none are quite THIS good.)

Ammunition (_AMMO) and Ammo Pack (_AMMOPACK)

Ammunition is an inventory item that automatically adds itself to weapons as necessary. (See Ranged Weapon for details regarding how this is accomplished.) They are a fairly simple item that uses a few extra prototype keys in order to keep track of the numbers regarding its capacity. Ammunition uses '|' as the default ASCII character.

Ammo packs are similar to ammunition, except that their function is different and twofold: it can be allocated to the prepared slot and used to reload ammo from there, and they can be unloaded in order to gain ammunition items. In the game, they are very similar, but for the purpose of modding, they can be quite different depending on your needs. An ammo pack uses '!' as this default ASCII character.

Ammunition and ammo packs carry the following additional prototype keys:

Items{
    ....
    type = ITEMTYPE_AMMOPACK, --use this or ITEMTYPE_AMMOPACK
    ammo = 50,                --required field
    ammomax = 100,            --required field
    ammoID = "shell",         --required field (_AMMOPACK only)
}
  • ammo is how much ammo the ammo or ammo pack holds when it drops randomly. In the base game, this is adjusted by the game's difficulty.
  • ammomax is how much ammo can fit into a single ammo or ammo pack item. For ammo packs, this should be equal to ammo as they cannot be added to.
  • ammoID is the kind of ammo that the ammo pack uses, for reloading and unloading purposes. Only include this key with ammo packs.

These keys also correspond exactly to the ammo and ammo pack properties for a particular item instance: the only slight exception is that ammoID as a property is called "ammoid".

There isn't a lot you can do with ammunition other than create the necessary rounds for any custom weapons. You will be able to copy and paste a basic ammo prototype, for the most part:

Items{
    name = "some kind of ammo",
    id   = "anammo",
    sprite = 0,
    level = 1,
    weight = 1000,
    desc = "This is just ammo. Hurry up and reload your weapon!"
    type = ITEMTYPE_AMMO,
    ammo = 20,
    ammomax = 100,
}

Ammo packs, on the other hand, can be customized a little bit if you want to be creative. Since they can be equipped, you can technically make use of the resistance prototype keys to grant extra resistance for equipping an ammo pack. In addition, the equipping hooks( OnEquip(), OnEquipCheck(), and OnEquipTick()) can also be used.

Armor (_ARMOR) and Boots (_BOOTS)

Armor and boots items are purely meant to be equipped, and use the various properties that come with equipping protective gear. Armor uses '[' as its default ASCII character, and boots use ';' as its default ASCII character.

Armor and boots share the following additional prototype keys:

Items{
    ....
    type  = ITEMTYPE_ARMOR, --use this for ITEMTYPE_BOOTS
    armor = 2,              --required field
    durability = 100,       --default is 100
    knockmod = 25,          --default is 0
    movemod = -10,          --default is 0
}
  • armor is the protection value of the item. This is a straight-up damage reduction, not to be confused with resistances (which work in terms of percentages).
  • durability is the "health" of the item: as damage sources hurt the being wearing the item, it loses durability. If the armor's current durability (the property) reaches zero, it is destroyed unless specifically flagged not to.
  • knockmod is the knockback modifier of the item. This can be a positive or negative value: positive values increase the modifier and negative values decrease the modifier. Increased knockback makes you fly farther due to damage.
  • movemod is the movespeed modifier of the item. Increased movespeed makes your movement faster.

The most common hooks used with armor and boots are OnEquip(), OnEquipCheck(), and OnEquipTick(), since their primary function is to be equipped to their appropriate slots. They also come with the following properties:

  • armor is the protection value of the item instance, as determined by the armor prototype key.
  • durability is the current value of the item instance's durability.
  • maxdurability is the maximum value of the item instance's durability, as determined by the durability prototype key.
  • movemod is the movespeed modifier of the item instance, as determined by the movemod prototype key.
  • knockback is the knockback modifier of the item instance, as determined by the knockmod prototype key.

Weapon (_MELEE, _RANGED _NRANGED)

Weapon items are what allow beings to attack with a greater ferocity than their ToDam and ToHit modifiers will allow. They replace the fists "weapon" when equipped in the weapon slots, and can also be equipped to the prepped slot. There are three item types that all count as weapons:

  • Melee weapons (_MELEE) only allow the being to attack as they would with their fists (i.e., adjacent to their position). The alt fire can be a ranged attack of sorts (e.g., throw) but cannot use ammo to do so. They are the most limited weapon type in terms of additional functionality. A melee weapon uses "\" as its default ASCII character.
  • Ranged weapons (_RANGED) allow the being to attack anything within its vision and the weapon's own maximum range. This type of weapon requires ammo in order to fire. A ranged weapon uses "}" as its default ASCII character.
  • Natural ranged weapons(_NRANGED) are similar to ranged weapons, except that a few prototype keys (such as ammoID) are avoided. The main purpose of natural ranged weapons is to give non-player beings a means to attack at range (i.e., an imp's fireball attack is a natural ranged weapon). A natural ranged weapon uses "?" as its default ASCII character.

Weapons share the following additional prototype keys:

Items{
    ....
    type          = ITEMTYPE_RANGED, --use this, or ITEMTYPE_MELEE, or ITEMTYPE_NRANGED
    damage        = "4d3",             --required field
    damagetype    = DAMAGE_BULLET,   --required field
    group         = "weapon-pistol", --default is "weapon-other"
    fire          = 12,              --default is 10
    acc           = 6,               --default is 0
    radius        = 0,               --default is 0 (_RANGED and _NRANGED only)
    shots         = 1,               --default is 0
    ammoID        = "ammo",          --required field (_RANGED only)
    ammomax       = 10,              --required field (_RANGED only)
    reload        = 12,              --default is 10 (_RANGED only)
    shotcost      = 1,               --default is 0 (_RANGED only)
    altfire       = ALT_AIMED,       --default is ALT_NONE (_MELEE and _RANGED only)
    altfirename   = "aimed shot",    --default based on altfire (_MELEE and _RANGED only)
    altreload     = RELOAD_FULL,     --default is RELOAD_NONE (_RANGED only)
    altreloadname = "full",          --default based on altreload (_RANGED only)
    soundID       = "pistol",        --default is id (_RANGED and _NRANGED only)
    missile       = "bullet",        --required field (optional for _MELEE)
}
  • damage is the weapon's damage, written in dice notation.
  • damagetype is the weapon's damage type, one of seven possibilities. (SeeDamageType for identifiers.)
  • group is the group that the weapon belongs to, for statistical purposes. Note that setting this has no effect on weapon-specific traits like Son of a Gun or Shottyman: these are determined by the weapon's flags.
  • fire is how long it takes to attack with the weapon, in game turns.
  • acc is the accuracy of the weapon.
  • radius is the explosion blast radius of the weapon's projectile. If radius is set to 0, no explosion occurs.
  • shots is the number of times the weapon attacks or fires projectiles. The default, 0, still causes one attack to occur.
  • ammoID is the identifer of the ammo that is to be used in this ranged weapon. This is what the weapon will look for in the player's inventory (or prepped slot in the case of ammo packs) when reloading.
  • ammomax is the maximum amount of ammo that the weapon can hold, also known as its capacity or clip size.
  • shotcost is the amount of ammo used to fire a single projectile of the weapon. The default, 0, still takes one ammo per projectile.
  • altfire is the identifer of an alternate fire mode for the weapon. This can either be one of several pre-defined alternate fires or the name of a custom function.
  • altfirename is the name of the alternate fire mode, as it appears in the inventory screen. Most of the pre-defined alternate fires have such a name as its default, and in those cases this field can be ignored.
  • altreload is the identifer of an alternate reload mode for the weapon. This can either be one of several pre-defined alternate reloads or the name of a custom function.
  • altreloadname is the name of the alternate fire mode, as it appears in the inventory screen. As with altfirename, this can likely be ignored if using a pre-defined alternate reload.
  • soundID is the sound of the projectile as the weapon fires. By default, it uses the same ID as the weapon's, so there is little reason to include your own.
  • missile is the identifier of the missile that this weapon fires. There are a few pre-defined missiles in the base game that can be used, or you can use your own. In addition, the missile's definition can be inlined into the weapon (see here for the basic structure).

Weapons also come with quite a few properties, although many can be directly related to a prototype key. The following are exactly related:

  • ammo
  • ammomax
  • acc
  • missile
  • shots
  • shotcost
  • damagetype
  • altfire
  • altreload

The following properties are different only by name:

  • ammoid is the property of ammoID
  • blastradius is the property of radius
  • reloadtime is the property of reload
  • usetime is the property of fire

Finally, the damage prototype key is broken into three separate properties:

  • damage_sides is the number after the "d"
  • damage_dice is the number before the "d"
  • damage_add is a constant added to all damage rolls (acts like the ToDam prototype field for beings)

Weapons are a very important facet of the game, and so a separate tutorial will be prepared to handle examples and explanations for many different kinds of weapons.

Personal tools