Revision 2678 (by dsb, 2016-12-18 15:54:38) Can't use :callTalent() on Telekinetic Grasp's filter() method, genius...
-- $Id: load.lua 2678 2016-12-18 15:54:38Z dsb $
-- ToME - Tales of Maj'Eyal
-- Copyright (C) 2012-2016 Scott Bigham
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
--
-- Scott Bigham ("Zizzo")
-- dsb-tome@killerbunnies.org

local function do_create_alchemist_gems(t, data)
  -- Implement the 'Convert to alchemist gems' menu item below.
  -- Roughly duplicated from /data/talents/spells/stone-alchemy.lua.
  local a, o = data.actor, data.object

  local gem = t.make_gem(a, t, o.define_as)
  if not gem then return nil end
  a:addObject(a.INVEN_INVEN, gem)
  a:removeObject(data.inven, data.item)
  game.logPlayer(a, "You create: %s", gem:getName{do_color=true, do_count=true})
  a:sortInven()
  return true
end

local function do_matter_is_energy(t, data)
  -- Implement the 'Extract energy from gem' menu item below.
  -- Roughly duplicated from /data/talents/psionic/finer-energy-manipulations.lua.
  local a, o = data.actor, data.object
  a:removeObject(data.inven, data.item)
  local amt = t.energy_per_turn(a, t)
  local dur = 3 + 2*(o.material_level or 0)
  a:setEffect(a.EFF_PSI_REGEN, dur, {power=amt})
  a:setEffect(a.EFF_CRYSTAL_BUFF, dur, {name=o.name, gem=o.define_as, effects=o.wielder})
  return true
end

local function do_telekinetic_grasp(t, data)
  -- Implement the 'Grasp telekinetically' menu item below.
  -- Roughly duplicated from /data/talents/psionic/other.lua.
  local a, o = data.actor, data.object

  local pf = a:getInven("PSIONIC_FOCUS")
  if not pf then return nil end
  -- Put back the old one in inventory
  local old = a:removeObject(pf, 1, true)
  if old then
    a:addObject(data.inven, old)
  end
--Note: error with set items -- set_list, on_set_broken, on_set_complete
  -- Fix the slot_forbid bug
  if o.slot_forbid then
    -- Store any original on_takeoff function
    if o.on_takeoff then
      o._old_on_takeoff = o.on_takeoff
    end
    -- Save the original slot_forbid
    o._slot_forbid = o.slot_forbid
    o.slot_forbid = nil
    -- And prepare the resoration of everything
    o.on_takeoff = function(self)
      -- Remove the slot forbid fix
      self.slot_forbid = self._slot_forbid
      self._slot_forbid = nil
      -- Run the original on_takeoff
      if self._old_on_takeoff then
	self.on_takeoff = self._old_on_takeoff
	self._old_on_takeoff = nil
	self:on_takeoff()
	-- Or remove on_takeoff entirely
      else
	self.on_takeoff = nil
      end
    end
  end

  o = a:removeObject(data.inven, data.item)
  -- Force "wield"
  a:addObject(pf, o)
  game.logSeen(a, "%s telekinetically seizes: %s.", a.name:capitalize(), o:getName{do_color=true})

  a:sortInven()
  return true
end

local function do_cursed_sentry(t, data)
  -- Implement the 'Use as sentry' menu item below.
  -- Roughly duplicated from /data/talents/cursed/cursed-aura.lua.
  local a, o = data.actor, data.object
  -- Sneaky Hack(TM):  Apparently, if we pretend to have selected this
  -- object via the Choose Cursed Sentry talent, Cursed Sentry's action()
  -- method will pick it up automatically for us.
  a.cursed_sentry = o
  return t.action(a, t)
end

local function talent_wrapper(t_id, data, f)
  local a = data.actor
  local t = a:getTalentFromId(t_id)
  if a:preUseTalent(t) then
    -- Urk... 'Use as sentry' uses targeting, which insists on being in a
    -- coroutine for no fathomable reason.
    local co = coroutine.create(function()
      local ret = f(t, data)
      if a:postUseTalent(t, ret) then
	a:startTalentCooldown(t)
      end
    end)
    local ok, err = coroutine.resume(co)
    if not ok and err then print(debug.traceback(co)) error(err) end
  end
end


class:bindHook('UseItemMenu:generate', function(self, data)
  local a, o = data.actor, data.object
  local in_inven = data.inven == a.INVEN_INVEN

  -- Add an 'Extract gem' action for items that the Extract Gems talent
  -- works on.
  if a:knowTalent(a.T_EXTRACT_GEMS) and a:callTalent(a.T_EXTRACT_GEMS, 'filterGem', o) then
    data.menu[#data.menu+1] = { name='Extract gem', mnemonic='x', action='extract_gem' }
  end

  -- Add a 'Convert to alchemist gems' action for gems.
  if a:knowTalent(a.T_CREATE_ALCHEMIST_GEMS) and o.type == 'gem' and not o.unique then
    data.menu[#data.menu+1] = { name='Convert to alchemist gems', mnemonic='a', action='create_alchemist_gems' }
  end

  -- Add an 'Extract energy from gem' action for gems.
  if a:knowTalent(a.T_MATTER_IS_ENERGY) and not a:isTalentCoolingDown(a.T_MATTER_IS_ENERGY) and o.type == 'gem' and o.material_level and not o.unique then
    data.menu[#data.menu+1] = { name='Extract energy from gem', mnemonic='e', action='matter_is_energy' }
  end

  -- Add 'Grasp telekinetically' action for weapons and gems.
  if a:knowTalent(a.T_TELEKINETIC_GRASP) and not a:isTalentCoolingDown(a.T_TELEKINETIC_GRASP) and in_inven then
    local t = a:getTalentFromId(a.T_TELEKINETIC_GRASP)
    if t.filter(o) then
      data.menu[#data.menu+1] = { name='Grasp telekinetically', mnemonic='K', action='telekinetic_grasp' }
    end
  end

  -- Add 'Use as sentry' action for weapons.
  if a:knowTalent(a.T_CURSED_SENTRY) and not a:isTalentCoolingDown(a.T_CURSED_SENTRY) and in_inven and a:callTalent(a.T_CURSED_SENTRY, 'filterObject', o) then
    data.menu[#data.menu+1] = { name='Use as sentry', mnemonic='S', action='cursed_sentry' }
  end

  -- Remove the 'Link item in chat' action if the corresponding chat filter
  -- is disabled.
  if config.settings.chat and config.settings.chat.filter and config.settings.chat.filter.link then
    local remove = nil
    for i, v in ipairs(data.menu) do
      if v.action == 'chat-link' then remove = i end
    end
    if remove then table.remove(data.menu, remove) end
  end
end)
class:bindHook('UseItemMenu:use', function(self, data)
  local a, o = data.actor, data.object

  if data.act == 'extract_gem' then
    talent_wrapper(a.T_EXTRACT_GEMS, data, function(t, data)
      -- Implement the 'Extract gem' menu item above.
      t.extractGem(a, t, o, data.inven, data.item)
      return true
    end)
    data.onuse(data.inven, data.item, o, false)
  elseif data.act == 'create_alchemist_gems' then
    talent_wrapper(a.T_CREATE_ALCHEMIST_GEMS, data, do_create_alchemist_gems)
    data.onuse(data.inven, data.item, o, false)
  elseif data.act == 'matter_is_energy' then
    talent_wrapper(a.T_MATTER_IS_ENERGY, data, do_matter_is_energy)
    data.onuse(data.inven, data.item, o, false)
  elseif data.act == 'telekinetic_grasp' then
    talent_wrapper(a.T_TELEKINETIC_GRASP, data, do_telekinetic_grasp)
    data.onuse(data.inven, data.item, o, false)
  elseif data.act == 'cursed_sentry' then
    -- Have to do this first to close the inventory dialog so we can select
    -- a location.
    data.onuse(data.inven, data.item, o, true)
    talent_wrapper(a.T_CURSED_SENTRY, data, do_cursed_sentry)
  end
end)