Skip to content
Snippets Groups Projects
golemancy.lua 10.3 KiB
Newer Older
dg's avatar
dg committed
-- ToME - Tales of Middle-Earth
-- Copyright (C) 2009, 2010 Nicolas Casalini
--
-- 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/>.
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
dg's avatar
dg committed
local Chat = require "engine.Chat"
dg's avatar
dg committed

dg's avatar
dg committed
function getGolem(self)
	if game.level:hasEntity(self.alchemy_golem) then
		return self.alchemy_golem, self.alchemy_golem
	elseif self:hasEffect(self.EFF_GOLEM_MOUNT) then
		return self, self.alchemy_golem
	end
end

dg's avatar
dg committed
local function makeGolem()
	return require("mod.class.NPC").new{
		type = "construct", subtype = "golem",
		display = 'g', color=colors.WHITE,
dg's avatar
dg committed
		level_range = {1, 50}, exp_worth=0,
dg's avatar
dg committed
		life_rating = 13,
dg's avatar
dg committed

		combat = { dam=10, atk=10, apr=0, dammod={str=1} },

		body = { INVEN = 50, MAINHAND=1, OFFHAND=1, BODY=1,},
		infravision = 20,
		rank = 3,
		size_category = 4,

dg's avatar
dg committed
		resolvers.talents{
			[Talents.T_MASSIVE_ARMOUR_TRAINING]=1,
			[Talents.T_HEAVY_ARMOUR_TRAINING]=1,
			[Talents.T_WEAPON_COMBAT]=2,
		},

		resolvers.equip{
			{type="weapon", subtype="battleaxe", autoreq=true},
			{type="armor", subtype="heavy", autoreq=true}
		},

		autolevel = "alchemy-golem",
dg's avatar
dg committed
		ai = "summoned", ai_real = "dumb_talented_simple", ai_state = { talent_in=4, ai_move="move_astar" },
		energy = { mod=1 },
		stats = { str=14, dex=12, mag=10, con=12 },

dg's avatar
dg committed
		keep_inven_on_death = true,
--		no_auto_resists = true,
dg's avatar
dg committed
		open_door = true,
		blind_immune = 1,
		fear_immune = 1,
dg's avatar
dg committed
		poison_immune = 1,
		disease_immune = 1,
dg's avatar
dg committed
		see_invisible = 2,
		no_breath = 1,
	}
end

newTalent{
	name = "Refit Golem",
	type = {"spell/golemancy-base", 1},
dg's avatar
dg committed
	require = spells_req1,
	points = 1,
	cooldown = 20,
	mana = 10,
dg's avatar
dg committed
	action = function(self, t)
		if not self.alchemy_golem then
			self.alchemy_golem = game.zone:finishEntity(game.level, "actor", makeGolem())
dg's avatar
dg committed
			game.persistant_actors[self.alchemy_golem] = 1
dg's avatar
dg committed
			if not self.alchemy_golem then return end
			self.alchemy_golem.faction = self.faction
			self.alchemy_golem.name = "golem (servant of "..self.name..")"
dg's avatar
dg committed
			self.alchemy_golem.summoner = self
			self.alchemy_golem.summoner_gain_exp = true

			-- Find space
			local x, y = util.findFreeGrid(self.x, self.y, 5, true, {[Map.ACTOR]=true})
			if not x then
				game.logPlayer(self, "Not enough space to refit!")
				return
			end
			game.zone:addEntity(game.level, self.alchemy_golem, "actor", x, y)
			return
dg's avatar
dg committed
		end

		local wait = function()
			local co = coroutine.running()
			local ok = false
			self:restInit(20, "refitting", "refitted", function(cnt, max)
				if cnt > max then ok = true end
				coroutine.resume(co)
			end)
			coroutine.yield()
			if not ok then
				game.logPlayer(self, "You have been interrupted!")
dg's avatar
dg committed
				return false
dg's avatar
dg committed
			return true
dg's avatar
dg committed
		end

dg's avatar
dg committed
		local ammo = self:hasAlchemistWeapon()

		-- talk to the golem
		if game.level:hasEntity(self.alchemy_golem) and self.alchemy_golem.life >= self.alchemy_golem.max_life then
			local chat = Chat.new("alchemist-golem", self.alchemy_golem, self)
			chat:invoke()

		-- heal the golem
		elseif game.level:hasEntity(self.alchemy_golem) and self.alchemy_golem.life < self.alchemy_golem.max_life then
			if not ammo or ammo:getNumber() < 2 then
				game.logPlayer(self, "You need to ready 2 alchemist gems in your quiver to heal your golem.")
				return
			end
			for i = 1, 2 do self:removeObject(self:getInven("QUIVER"), 1) end
dg's avatar
dg committed
			self.alchemy_golem:heal(self:combatTalentSpellDamage(t, 15, 350, (ammo.alchemist_power + self:combatSpellpower()) / 2))
dg's avatar
dg committed

		-- resurrect the golem
dg's avatar
dg committed
		else
dg's avatar
dg committed
			if not ammo or ammo:getNumber() < 15 then
				game.logPlayer(self, "You need to ready 15 alchemist gems in your quiver to heal your golem.")
				return
			end
dg's avatar
dg committed
			if not wait() then return end
dg's avatar
dg committed
			for i = 1, 15 do self:removeObject(self:getInven("QUIVER"), 1) end

			self.alchemy_golem.dead = nil
			if self.alchemy_golem.life < 0 then self.alchemy_golem.life = self.alchemy_golem.max_life / 3 end

dg's avatar
dg committed
			-- Find space
			local x, y = util.findFreeGrid(self.x, self.y, 5, true, {[Map.ACTOR]=true})
			if not x then
dg's avatar
dg committed
				game.logPlayer(self, "Not enough space to refit!")
dg's avatar
dg committed
				return
			end
			game.zone:addEntity(game.level, self.alchemy_golem, "actor", x, y)
		end

		game:playSoundNear(self, "talents/arcane")
		return true
	end,
	info = function(self, t)
dg's avatar
dg committed
		local ammo = self:hasAlchemistWeapon()
		local heal = 0
		if ammo then self:combatTalentSpellDamage(t, 15, 150, (ammo.alchemist_power + self:combatSpellpower()) / 2) end
		return ([[Interract with your golem
		- If it is destroyed you will take some time to reconstruct it (takes 15 alchemist gems).
		- If it is alive you will be able to talk to it, change its weapon and armour or heal it (%d; takes 2 alchemist gems)]]):
		format(heal)
dg's avatar
dg committed
	end,
}

newTalent{
	name = "Golem: Taunt", short_name = "GOLEM_TAUNT",
dg's avatar
dg committed
	type = {"spell/golemancy", 1},
	require = spells_req1,
	points = 5,
	cooldown = function(self, t)
		return 20 - self:getTalentLevelRaw(t) * 2
dg's avatar
dg committed
	end,
dg's avatar
dg committed
	action = function(self, t)
dg's avatar
dg committed
		local mover, golem = getGolem(self)
		if not golem then
			game.logPlayer(self, "Your golem is currently inactive.")
dg's avatar
dg committed
			return
		end

		local tg = {type="hit", range=self:getTalentRange(t)}
dg's avatar
dg committed
		game.target.source_actor = mover
		local x, y, target = self:getTarget(tg)
		game.target.source_actor = self
dg's avatar
dg committed
		if not x or not y or not target then return nil end
		if math.floor(core.fov.distance(mover.x, mover.y, x, y)) > self:getTalentRange(t) then return nil end
dg's avatar
dg committed

dg's avatar
dg committed
		mover:setTarget(target)
		target:setTarget(mover)
		game.logPlayer(self, "Your golem provokes %s to attack it.", target.name:capitalize())
dg's avatar
dg committed

		return true
	end,
	info = function(self, t)
		return ([[Orders your golem to taunt a target, forcing it to attack the golem.]]):format()
dg's avatar
dg committed
	end,
}

newTalent{
	name = "Golem: Knockback",
	type = {"spell/golemancy", 2},
	require = spells_req2,
	points = 5,
	cooldown = 10,
	range = 10,
	mana = 5,
	action = function(self, t)
dg's avatar
dg committed
		local mover, golem = getGolem(self)
		if not golem then
			game.logPlayer(self, "Your golem is currently inactive.")
			return
		end

		local tg = {type="hit", range=self:getTalentRange(t)}
dg's avatar
dg committed
		game.target.source_actor = mover
		local x, y, target = self:getTarget(tg)
		game.target.source_actor = self
		if not x or not y or not target then return nil end
dg's avatar
dg committed
		if math.floor(core.fov.distance(mover.x, mover.y, x, y)) > self:getTalentRange(t) then return nil end
dg's avatar
dg committed
		mover:setTarget(target)
dg's avatar
dg committed
		local l = line.new(mover.x, mover.y, x, y)
dg's avatar
dg committed
		local tx, ty = mover.x, mover.y
		lx, ly = l()
		while lx and ly do
dg's avatar
dg committed
			if game.level.map:checkAllEntities(lx, ly, "block_move", mover) then break end
dg's avatar
dg committed
		mover:move(tx, ty, true)
dg's avatar
dg committed
		if math.floor(core.fov.distance(mover.x, mover.y, x, y)) > 1 then return true end
		local hit = golem:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.8, 1.6), true)

		-- Try to knockback !
		if hit then
dg's avatar
dg committed
			if target:checkHit(golem:combatAttackStr(), target:combatPhysicalResist(), 0, 95, 5 - self:getTalentLevel(t) / 2) and target:canBe("knockback") then
				target:knockback(mover.x, mover.y, 3)
			else
				game.logSeen(target, "%s resists the knockback!", target.name:capitalize())
			end
		end

		return true
	end,
dg's avatar
dg committed
	info = function(self, t)
		return ([[Your golem rushes to the target, knocking it back and doing %d%% damage.]]):format(100 * self:combatTalentWeaponDamage(t, 0.8, 1.6))
dg's avatar
dg committed
	end,
}

newTalent{
	name = "Golem: Crush",
	type = {"spell/golemancy", 3},
	require = spells_req3,
	points = 5,
	cooldown = 10,
	range = 10,
	mana = 5,
	action = function(self, t)
dg's avatar
dg committed
		local mover, golem = getGolem(self)
		if not golem then
			game.logPlayer(self, "Your golem is currently inactive.")
			return
		end

		local tg = {type="hit", range=self:getTalentRange(t)}
dg's avatar
dg committed
		game.target.source_actor = mover
		local x, y, target = self:getTarget(tg)
		game.target.source_actor = self
		if not x or not y or not target then return nil end
dg's avatar
dg committed
		if math.floor(core.fov.distance(mover.x, mover.y, x, y)) > self:getTalentRange(t) then return nil end
dg's avatar
dg committed
		mover:setTarget(target)
dg's avatar
dg committed
		local l = line.new(mover.x, mover.y, x, y)
dg's avatar
dg committed
		local tx, ty = mover.x, mover.y
		lx, ly = l()
		while lx and ly do
dg's avatar
dg committed
			if game.level.map:checkAllEntities(lx, ly, "block_move", mover) then break end
dg's avatar
dg committed
		mover:move(tx, ty, true)
dg's avatar
dg committed
		if math.floor(core.fov.distance(mover.x, mover.y, x, y)) > 1 then return true end
		local hit = golem:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.8, 1.6), true)

		-- Try to knockback !
		if hit then
dg's avatar
dg committed
			if target:checkHit(golem:combatAttackStr(), target:combatPhysicalResist(), 0, 95, 10 - self:getTalentLevel(t) / 2) and target:canBe("stun") then
				target:setEffect(target.EFF_PINNED, 2 + self:getTalentLevel(t), {})
			else
				game.logSeen(target, "%s resists the crushing!", target.name:capitalize())
			end
		end

		return true
dg's avatar
dg committed
	end,
	info = function(self, t)
		return ([[Your golem rushes to the target, crushing and doing %d%% damage.]]):format(100 * self:combatTalentWeaponDamage(t, 0.8, 1.6))
dg's avatar
dg committed
	end,
}

newTalent{
	name = "Invoke Golem",
	type = {"spell/golemancy",4},
	require = spells_req4,
	points = 5,
dg's avatar
dg committed
	action = function(self, t)
dg's avatar
dg committed
		local mover, golem = getGolem(self)
		if not golem then
dg's avatar
dg committed
			game.logPlayer(self, "Your golem is currently inactive.")
			return
		end

		-- Find space
		local x, y = util.findFreeGrid(self.x, self.y, 5, true, {[Map.ACTOR]=true})
		if not x then
			game.logPlayer(self, "Not enough space to invoke!")
			return
		end

dg's avatar
dg committed
		golem:setEffect(golem.EFF_MIGHTY_BLOWS, 5, {power=self:combatTalentSpellDamage(t, 15, 50)})
		if golem == mover then
			golem:move(x, y, true)
		end
dg's avatar
dg committed
		game:playSoundNear(self, "talents/arcane")
		return true
	end,
	info = function(self, t)
dg's avatar
dg committed
		return ([[You invoke your golem to your side, granting it a temporary melee power increase of %d for 5 turns.]]):format(self:combatTalentSpellDamage(t, 15, 50))
dg's avatar
dg committed
	end,
}