T4 Modules Howto Guide/Inventory Dialogs

From Tales of Maj'Eyal
Revision as of 09:48, 17 August 2015 by Zireael (Talk | contribs) (update)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Inventory and Equipment Dialogs

This section assumes that objects and inventory has already been enabled by following the instructions in the Objects guide.


Picking up and dropping items

To start, lets add the ability to actually pickup and drop items.

First, in your Game.lua file, find the setupCommands() method. Inside this method add the following entry to the self.key:addBinds command:


 PICKUP_FLOOR = function()
    if self.player.no_inventory_access then return end
    self.player:playerPickup()
end,
DROP_FLOOR = function()
    if self.player.no_inventory_access then return end
    self.player:playerDrop()
end, 

This allows the pickup and drop commands to be mapped, which causes the player:playerPickup() or player:playerDrop() methods to execute. Now lets add these methods to the Player.lua file:


 function _M:playerPickup()
    -- If 2 or more objects, display a pickup dialog, otherwise just picks up
    if game.level.map:getObject(self.x, self.y, 2) then
        local d d = self:showPickupFloor("Pickup", nil, function(o, item)
            self:pickupFloor(item, true)
            self.changed = true
            d:used()
        end)
    else
        self:pickupFloor(1, true)
        self:sortInven()
        self:useEnergy()
    self.changed = true
    end
end

function _M:playerDrop()
    local inven = self:getInven(self.INVEN_INVEN)
    local d d = self:showInventory("Drop object", inven, nil, function(o, item)
        self:dropFloor(inven, item, true, true)
        self:sortInven(inven)
        self:useEnergy()
        self.changed = true
        return true
    end)
end 

Now you can play and pick up and drop your items!


Inventory and equipment Screen

Now that you can pick up and drop items, you need to be able to obtain an inventory list and equip items.

First, as above, add the following to the key mappings in Game.lua:


 SHOW_INVENTORY = function()
    if self.player.no_inventory_access then return end
    local d
    d = self.player:showEquipInven("Inventory", nil, function(o, inven, item, button, event)
        if not o then return end
        local ud = require("mod.dialogs.UseItemDialog").new(event == "button", self.player, o, item, inven, function(_, _, _, stop)
            d:generate()
            d:generateList()
            if stop then self:unregisterDialog(d) end
        end)
        self:registerDialog(ud)
    end)
end, 

Once done, you can try run the game and enjoy your new inventory listing. You may however notice that the game will crash if you try to actually use or equip anything in your inventory. This is because we have not yet defined the UseItemDialog.

In your module's Dialogs directory, create a new file file UseItemDialog.lua and paste the following:


 require "engine.class"
require "engine.ui.Dialog"
local List = require "engine.ui.List"
local Savefile = require "engine.Savefile"
local Map = require "engine.Map"

module(..., package.seeall, class.inherit(engine.ui.Dialog))

function _M:init(center_mouse, actor, object, item, inven, onuse)
    self.actor = actor
    self.object = object
    self.inven = inven
    self.item = item
    self.onuse = onuse

    self:generateList()
    local name = object:getName()
    local w = self.font_bold:size(name)
    engine.ui.Dialog.init(self, name, 1, 1)

    local list = List.new{width=math.max(w, self.max) + 10, nb_items=#self.list, list=self.list, fct=function(item) self:use(item) end}

    self:loadUI{
        {left=0, top=0, ui=list},
    }
    self:setupUI(true, true, function(w, h)
        if center_mouse then
            local mx, my = core.mouse.get()
            self.force_x = mx - w / 2
            self.force_y = my - (self.h - self.ih + list.fh / 3)
        end
    end)

    self.key:addBinds{ EXIT = function() game:unregisterDialog(self) end, }
end

function _M:use(item)
    if not item then return end
    game:unregisterDialog(self)

    local act = item.action

    --if act == "use" then
        --self.actor:playerUseItem(self.object, self.item, self.inven, self.onuse)
        --self.onuse(self.inven, self.item, self.object, true)
    --else
    if act == "drop" then
        self.actor:doDrop(self.inven, self.item, function() self.onuse(self.inven, self.item, self.object, false) end)
    elseif act == "wear" then
        self.actor:doWear(self.inven, self.item, self.object)
        self.onuse(self.inven, self.item, self.object, false)
    elseif act == "takeoff" then
        self.actor:doTakeoff(self.inven, self.item, self.object)
        self.onuse(self.inven, self.item, self.object, false)
    end
end

function _M:generateList()
    local list = {}

    --if self.object:canUseObject() then list[#list+1] = {name="Use", action="use"} end
    if self.inven == self.actor.INVEN_INVEN and self.object:wornInven() and self.actor:getInven(self.object:wornInven()) then list[#list+1] = {name="Wield/Wear", action="wear"} end
    if self.inven ~= self.actor.INVEN_INVEN and self.object:wornInven() then list[#list+1] = {name="Take off", action="takeoff"} end
    if self.inven == self.actor.INVEN_INVEN then list[#list+1] = {name="Drop", action="drop"} end

    self.max = 0
    self.maxh = 0
    for i, v in ipairs(list) do
        local w, h = self.font:size(v.name)
        self.max = math.max(self.max, w)
        self.maxh = self.maxh + self.font_h
    end

    self.list = list
end 

In your Player.lua class file paste the following two functions:


 function _M:doDrop(inven, item, on_done, nb)
    if self.no_inventory_access then return end
    
    if nb == nil or nb >= self:getInven(inven)[item]:getNumber() then
        self:dropFloor(inven, item, true, true)
    else
        for i = 1, nb do self:dropFloor(inven, item, true) end
    end
    self:sortInven(inven)
    self:useEnergy()
    self.changed = true
    if on_done then on_done() end
end

function _M:doWear(inven, item, o)
    self:removeObject(inven, item, true)
    local ro = self:wearObject(o, true, true)
    if ro then
        if type(ro) == "table" then self:addObject(inven, ro) end
    elseif not ro then
        self:addObject(inven, o)
    end
    self:sortInven()
    self:useEnergy()
    self.changed = true
end

function _M:doTakeoff(inven, item, o)
    if self:takeoffObject(inven, item) then
        self:addObject(self.INVEN_INVEN, o)
    end
    self:sortInven()
    self:useEnergy()
    self.changed = true
end 

You should now be able to drop items from within the inventory screen as well as equip and takeoff items.


Consumable and activatable items

Right now you can only "wear", "takeoff", "pickup" and "drop" items. This section deals with allowing objects to be "used".

First, return to the UseItemDialog.lua file in the dialogs directory and uncomment the commented lines in the use and generateList methods.

The onuse flag refers to whether the inventory screen should be closed or not upon using an item.

Next you need to add a new method to the object class to allow the code to check whether an object can be used. If you have not already, this will involve actually creating an objects class. Paste the following into the Object.lua class file:


 require "engine.class"
require "engine.Object"
require "engine.interface.ObjectActivable"

local Stats = require("engine.interface.ActorStats")
local Talents = require("engine.interface.ActorTalents")
local DamageType = require("engine.DamageType")

module(..., package.seeall, class.inherit(
    engine.Object,
    engine.interface.ObjectActivable,
    engine.interface.ActorTalents
))

function _M:init(t, no_default)
    t.encumber = t.encumber or 0

    engine.Object.init(self, t, no_default)
    engine.interface.ObjectActivable.init(self, t)
    engine.interface.ActorTalents.init(self, t)
end

function _M:canAct()
    if self.power_regen or self.use_talent then return true end
    return false
end

function _M:act()
    self:regenPower()
    self:cooldownTalents()
    self:useEnergy()
end

function _M:use(who, typ, inven, item)
    inven = who:getInven(inven)

    if self:wornInven() and not self.wielded and not self.use_no_wear then
        game.logPlayer(who, "You must wear this object to use it!")
        return
    end

    local types = {}
    if self:canUseObject() then types[#types+1] = "use" end

    if not typ and #types == 1 then typ = types[1] end

    if typ == "use" then
        local ret = {self:useObject(who, inven, item)}
        if ret[1] then
            if self.use_sound then game:playSoundNear(who, self.use_sound) end
            who:useEnergy(game.energy_to_act * (inven.use_speed or 1))
        end
        return unpack(ret)
    end
end 

You will also need to modify you Game.lua class: In the loaded function modify the Zone:setup call to also include your new Object class:


 Zone:setup{npc_class="mod.class.NPC", grid_class="mod.class.Grid", object_class="mod.class.Object"} 

Also add the following function to the Player.lua class:


 function _M:playerUseItem(object, item, inven)
    local use_fct = function(o, inven, item)
        if not o then return end
        local co = coroutine.create(function()
            self.changed = true

            local ret = o:use(self, nil, inven, item) or {}
            if not ret.used then return end
            if ret.destroy then
                if o.multicharge and o.multicharge > 1 then
                    o.multicharge = o.multicharge - 1
                else
                    local _, del = self:removeObject(self:getInven(inven), item)
                    if del then
                        game.log("You have no more %s.", o:getName{no_count=true, do_color=true})
                    else
                        game.log("You have %s.", o:getName{do_color=true})
                    end
                    self:sortInven(self:getInven(inven))
                end
            end
        end)
        local ok, ret = coroutine.resume(co)
        if not ok and ret then print(debug.traceback(co)) error(ret) end
        return true
    end

    if object and item then return use_fct(object, inven, item) end

    local titleupdator = self:getEncumberTitleUpdator("Use object")
    self:showEquipInven(titleupdator(),
        function(o)
            return o:canUseObject()
        end,
        use_fct
    )
end 

Congratulations, you now have usable items.

Now all you need to do is define your items usable.

For consumable objects like potions:


 use_simple = { name = "power name",
  use = function(self,who)
    <Code for power use here>
    return {used = true, destroy = true}
  end
}, 

For an object like a wand with limited charges:


 multicharge = <charges>,
use_simple = { name = "power name",
  use = function(self,who)
    <Code for power use here>
    return {used = true, destroy = true}
  end
}, 

For a magical object with regenerating power:


 max_power = <max power>,
power_regen = <power regained per turn>,
use_power = {name = "power name", power = <power use per charge>,
  use = function(self, who)
    <Code for power use here>
    return true
  end
}, 

For a magical object that activates a talent on use:


 max_power = <max power>,
power_regen = <power regained per turn>,
use_talent = {id = Talents.T_SOMETALENT, level = <level of talent>, power = <power use per charge> }, 


Go back to T4 Modules Howto Guide