Items
Items are scattered throughout the dungeon of RuggRogue; they reward the player for exploration. The player can pick up and drop items to add and remove them from their inventory. Weapons and armor are items that can be equipped by the player, conferring bonuses during combat. Other items can be applied (used) for a variety of effects, such as healing the player, hurting monsters and inflicting status effects.
This chapter starts by giving a run-down of the items that exist in RuggRogue, their effects and how often they're found, with source code references sprinkled throughout. Following this is an overview of the interface used to interact with items, and how items move between the map, the player's inventory and equipment. Finally, targeting and effects of items that can be applied are described.
List of Items
All items are spawned by calling functions named like spawn_foo
in the src/spawn.rs
file, where foo
is the name of the item.
The list is as follows:
- Present (
spawn_present
) - The player wins the game when this item is used. - Ration (
spawn_ration
) - Consumable; restores 750 nutrition to the player. - Health Potion (
spawn_healh_potion
) - Consumble; restores 20 hit points if the player is hurt, or increases maximum hit points by 2 otherwise. - Magic Missile Scroll (
spawn_magic_missile_scroll
) - Consumable; inflicts 8 damage to a single target up to 6 tiles away. - Fireball Scroll (
spawn_fireball_scroll
) - Consumble; inflicts 20 damage to targets in a 3-tile area of effect up to 6 tiles away. - Sleep Scroll (
spawn_sleep_scroll
) - Consumable; inflicts the sleep status effect to targets in a 1-tile area of effect up to 6 tiles away. - Weapon (
spawn_weapon
) - Equipped in the "Weapon" slot; provides a bonus to attack. - Armor (
spawn_armor
) - Equipped in the "Armor" slot; provides a bonus to defense.
Note that weapons only vary by appearance and combat bonuses and so are treated as a single item type; likewise for armor.
Item Distribution
Items spawn in one of two broad ways: by room and by level.
When a map is being populated, one in four rooms will spawn items, following the logic in the fill_room_with_spawns
function in the src/spawn.rs
file.
Each such room generally spawns a single item, but may spawn an additional one for every win the player has accumulated.
The distribution of room items is determined by the spawn_random_item_at
function, and looks like this:
- 1 / 11 - a weapon or armor with an extra +1 to +3 power bonus
- 3 / 11 - Health Potion
- 3 / 11 - Magic Missile Scroll
- 2 / 11 - Fireball Scroll
- 2 / 11 - Sleep Scroll
Each level spawns a single Ration with the help of the spawn_guaranteed_ration
function.
The spawn_guaranteed_equipment
function spawns a starting weapon and armor on the first level, and depth-appropriate weapon and armor at irregular depth intervals.
The exact process for all of this is described in detail in the Map Population chapter.
Inventory and Equipment
For the player to pick up and use items, they need an inventory to serve as space to hold them.
The list of items carried by the player is represented by the Inventory
component, defined in the src/components.rs
file:
pub struct Inventory {
pub items: Vec<EntityId>,
}
The game will only allow entities to be picked up if they are marked with the Item
tag component:
pub struct Item;
The player can equip certain items from their inventory.
The player's equipment slots are represented by the Equipment
component:
pub struct Equipment {
pub weapon: Option<EntityId>,
pub armor: Option<EntityId>,
}
Note that equipment slots are separate from the inventory, so for example, equipping a weapon moves it out of the inventory and into the player's "Weapon" slot.
Items that can be equipped are marked with an EquipSlot
component:
pub struct EquipSlot {
Weapon,
Armor,
}
The exact value of the EquipSlot
component determines which equipment slot the item will be moved into when it is equipped.
Item User Interface
Most of the time spent playing the game that isn't moving around the dungeon or fighting monsters is spent dealing with items. As a result, about half of the interface code is dedicated solely to dealing with items.
Although these item-related menus and dialogs allude to actions, note that they never perform these actions themselves. For example, if the player chooses to drop an item from the inventory, the inventory returns a result that captures that intent, and the job of dropping the item is handled elsewhere.
Pick Up Menu
The player presses either the 'g' or the Comma key whenever they want to pick up an item at their position.
This pushes onto the mode stack the PickUpMenuMode
, defined in the src/modes/pick_up_menu.rs
file.
If the player isn't standing over any items, they're given a message saying as much and no menu appears.
If the player is standing over at least one item, a menu appears, allowing them to select an item on the map to be picked up.
This menu has cursor-based controls, along with most of the menus and dialogs in the game.
The entity ID of the selected item is returned as part of the PickUpMenuModeResult
.
Inventory Menu
The player presses the 'i' key to bring up the inventory menu.
This is the biggest and most advanced of the menus, represented as the InventoryMode
in the src/modes/inventory.rs
file.
It shows the player's currently-equipped weapon and armor in a small section at the top, with a larger inventory listing beneath it.
There's also an option to sort the inventory, which sorts inventory items according to hard-coded criteria.
If an inventory item is selected, an inventory action menu is presented for it; a similar equipment action menu is presented if an equipped item is selected.
Any action returned by either of these menus is relayed back as an InventoryModeResult
with the item's entity ID, the action and a target location for items usable at range.
Inventory Action Menu and Equipment Action Menu
Selecting an inventory item presents an inventory action menu, represented by the InventoryActionMode
in the src/modes/inventory_action.rs
file.
It shows a list of possible actions that can be performed with the item, such as "Equip", "Apply" and "Drop".
If one of these actions is chosen, it will be returned in the form of an InventoryActionModeResult
.
Selecting an equipped weapon or armor in the inventory menu brings up the equipment action menu, represented by the EquipmentActionMode
in the src/modes/equipment_action.rs
file.
It presents "Remove" and "Drop" as possible actions, but otherwise works much the same as the InventoryActionMode
.
An EquipmentActionModeResult
holds the selected action in this case.
Ranged Item Targeting Mode
If "Apply" is chosen in the inventory action menu for an item that is usable at range, a targeting mode needs to be brought up to choose a target location.
This is represented by the TargetMode
, defined in the src/modes/target.rs
file.
Unlike everything listed up to this point, the TargetMode
is not a menu!
Instead, it draws the map and interface much like the DungeonMode
in which most of the gameplay occurs.
However, instead of performing actions and taking turns, the TargetMode
highlights valid target tiles for the ranged item it was invoked for.
The movement keys move around a cursor that allows the player to choose a target location out of the valid target tiles.
This selected target location is returned as part of the TargetModeResult
.
Shortcut Menus
The inventory menu allows interacting with items in the player's possession through a single centralized menu, but players who already know what they want to do may find this cumbersome. RuggRogue provides shortcut menus to bypass the inventory in this case, brought up with these shortcut keys:
- 'a' to "Apply"
- 'e' or 'w' to "Equip"
- 'r' to "Remove"
- 'd' to "Drop"
Shortcut keys and actions are associated in the InventoryAction::from_key
function in the src/modes/inventory_action.rs
file, separate from the definition of these shortcut menus.
This shortens interaction from "Inventory -> Item -> Action" to just "Action -> Item".
Pressing one of these keys brings up the InventoryShortcutMode
, defined in the src/modes/inventory_shortcut.rs
.
This presents a prompt such as "Apply which item?" with a list of inventory items narrowed down to only those with which the action can be performed.
The chosen item is encapsulated in the InventoryShortcutModeResult
along with the action that brought the shortcut menu up in the first place.
The "Remove" action brings up a similar EquipmentShortcutMode
that returns an EquipmentShortcutModeResult
.
Note that the "Drop" shortcut menu only lists inventory items even though equipment can be directly dropped while equipped. This is a technical limitation due to how this was originally designed.
The shortcut keys are used beyond these shortcut menus. For example, pressing any of them in the inventory will bring up the inventory action menu with the matching action pre-selected. Further, pressing them in the inventory action menu will move the cursor to the matching action, or confirm the action if it's already selected.
Menu Memory
When a menu that deals with items is closed, the position of the cursor will be remembered upon reopening the menu. This makes it easier to perform the same action on a sequence of items in menus.
This menu cursor memory is stored in the MenuMemory
unique defined in the src/menu_memory.rs
file.
It's simply an array with an entry for a cursor position for each of the different item menus that the player will see:
InventoryMode
InventoryShortcutMode
for "Equip"InventoryShortcutMode
for "Apply"InventoryShortcutMode
for "Drop"EquipmentShortcutMode
for "Remove"EquipmentShortcutMode
for "Drop" (unused as described in the previous section)PickUpMenuMode
For PickUpMenuMode
, the MenuMemory
will recall the map coordinates of the last time the PickUpMenuMode
appeared.
If the player's coordinates differ from last time, the PickUpMenuMode
will reset the cursor memory.
Moving Items Around
While the user interface permits the player to choose actions to perform with items, the task of performing the actions themselves falls upon mode result handling logic near the top of the DungeonMode::update
function in the src/modes/dungeon.rs
file.
The most fundamental of these actions are the ones that simply move items around, such as picking up, dropping and equipping items.
Items exist in one of three places:
- on the map,
- in an inventory, or
- equipped as a weapon or armor.
An item on the map is like any other entity, and thus must have a Coord
component with its map coordinates, as well as be present in the map's spatial cache (the tile_entities
field of the Map
struct).
For the item to be visible but not over the player or any monsters, it must also have a RenderOnFloor
tag component.
An item in an inventory is listed by its entity ID in the items
vector of the Inventory
component.
An item equipped as a weapon has its entity ID set in the weapon
field of the relevant Equipment
component.
An equipped armor item is set to the armor
field instead.
Picking up an item moves it from the map to the player's inventory.
The player::player_pick_up_item
function in the src/player.rs
file encapsulates this action, calling upon the item::remove_item_from_map
and item::add_item_to_inventory
functions defined in the src/item.rs
file to do the heavy lifting.
Dropping an item moves it from the player's inventory to the map.
The player::player_drop
item function in the src/player.rs
file handles this with the help of the item::remove_item_from_inventory
and item::add_item_to_map
functions defined in the src/item.rs
file.
Equipment is dropped by the item::drop_equipment
function with the help of an unequip_item
helper function, both also in the src/item.rs
file.
Equipping an item moves it from the inventory to an equipment slot.
This is the task of the item::equip_item
function in the src/item.rs
file.
The EquipSlot
component of the item is checked here to determine if the item should be equipped as a weapon or as armor.
Unequipping an item moves it from an equipment slot back to the inventory.
This is handled by the item::remove_equipment
function in the src/item.rs
file.
This first unequips the item using the aforementioned unequip_item
helper function, then moves it to the inventory with the item::add_item_to_inventory
function.
Using Items
The "Apply" action can be used on an item that is marked with either the Consumable
or Victory
tag components.
If one of the item-related menus requests that an item be applied, the DungeonMode::update
function will handle it by calling the item::use_item
function defined in the src/item.rs
file.
Victory
The first thing the item::use_item
function checks is if the item in question has a Victory
tag component and the user is the player.
If these conditions are true, the player has won the game!
The first step of handling victory is to immediately perform an auto-save.
The victory sequence involves switching from the DungeonMode
to the GameOverMode
.
If the game window closes for whatever reason, there's no confirmation dialog or any chance to save the game, since those tasks would normally be handled by the DungeonMode
.
Auto-saving in advance mitigates this issue.
The victory item is then deleted and the win counter is incremented.
The item::use_item
function returns a result that signals to the DungeonMode::update
function that it should switch to the GameOverMode
.
The GameOverMode
defined in the src/modes/game_over.rs
file detects that the player is still alive and has thus won the game, showing a congratulatory message in response.
Proceeding from the GameOverMode
switches back to the DungeonMode
to start a New Game Plus run.
Gathering Targets
Back in the item::use_item
function, most items won't win the game outright, so they must have some sort of effect.
The first thing to do in this case is to figure out which entities should be affected by the item.
Items with a Ranged
component will already have target map coordinates chosen previously.
Items that aren't used at range imply self-use; the coordinates of the entity using the item are used in this case.
Affected entities are gathered by calling the ruggrogue::field_of_view
function, centered about the target location.
The radius of this field is either zero for just the target tile, or a non-zero value extracted from the AreaOfEffect
component attached to the item.
Using field of view calculation to determine targets like this prevents items with an area of effect from blasting through walls.
Applying Item Effects
Items can have any number of effects according to the components attached to the item and the targets.
Nutrition is added to the target if the target entity has a Stomach
component to fill.
The fullness
field of that Stomach
component is filled according to the amount stated in the item's Nutrition
component that found only on rations.
Healing is applied if the item has a ProvidesHealing
component and the target has a CombatStats
component.
If the target is at less than full health, the hit points of that target are restored by the amount stated in the ProvidesHealing
component, up to maximum hit points.
If the target is at full health, their hit points and maximum hit points are increased by two.
Damage is applied if the item has an InflictsDamage
component and the target has a CombatStats
component.
This does a few things:
- The target's hit points are reduced according to the amount stated in the
InflictsDamage
component. - The target is given a
HurtBy::Someone(id)
component, whereid
is the entity ID of the item user so they can be credited if the target dies. - If the user has a
Tally
component, add the damage amount to thedamage_dealt
field. - If the target has a
Tally
component, add the damage amount to thedamage_taken
field.
Sleep is applied if the item has an InflictsSleep
component and the target has a CombatStats
component.
It adds the Asleep
component to the target with a sleepiness
amount determined by the InflictsSleep
component.
Once all targets have been processed, if the item is marked with the Consumable
tag component it is removed from the inventory of its user and then destroyed.
The Sleep Status Effect
The sleep status effect renders the target unable to do anything other than pass turns until it wears off. It affects both the player and monsters.
In the case of a sleeping player, there is sleep-related input handling near the top of the player::player_input
function in the src/player.rs
file.
In this state, almost every key press will cause the player to automatically pass their turn.
The wearing off of the sleep status is handled by calling the item::handle_sleep_turn
function back in the src/item.rs
file.
Sleeping monsters are dealt with by the do_turn_for_one_monster
function in the src/monster.rs
file.
A monster that is asleep simply calls the item::handle_sleep_turn
function and bypasses the usual monster AI.
The item::handle_sleep_turn
function is responsible for counting down the sleepiness of sleep-afflicted entities and waking them back up.
The sleepiness counter of the entity is decremented according to the following rules:
- -1 per turn.
- -1 if the entity is the player and a monster is in their field of view, or vice versa.
- -10 if the entity lost hit points since their last turn; the
Asleep
component tracks changes to hit points to detect this.
Once the sleepiness counter reaches zero, the Asleep
component is removed from the entity, waking them up.
The Sleep Scroll inflicts 36 points of sleepiness, by its construction in the spawn_sleep_scroll
function back in the src/spawn.rs
file.
This renders one sleeping monster vulnerable to three hits before waking up if the player wastes no turns to attack them.