Modding:Tutorial/Custom Items

From DoomRL Wiki

Revision as of 18:49, 20 September 2011 by Game Hunter (Talk | contribs)

Jump to: navigation, search

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

Deadweight (_NONE)

Deadweight items have no additional prototype keys. In fact, they only have two 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.

In addition, deadweight items can make use of the OnCreate() and OnEnter() engine hooks. These attibutes are included with every other item type as well.

Items of this type 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},
 
    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 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.
  • 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.
  • 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.

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,
}

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,
}


Inventory Items

Consumable (_PACK)

Consumable items are added into the inventory and directly used from there 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:set_affect(STATUSINVERT,20)
    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
    end,
}

Ammunition (_AMMO) and Ammo Pack (_AMMOPACK)

Armor (_ARMOR) and Boots (_BOOTS)

Melee Weapon (_MELEE)

Ranged Weapon (_RANGED) and Natural Ranged Weapon (_NRANGED)

_ARMOR uses '[' _BOOTS uses ';' _AMMO uses '|' _AMMOPACK uses '!' _RANGED uses '}' _NRANGED uses '?' _MELEE uses '\'

Personal tools