diff --git a/game/engine/ActorsSeenDisplay.lua b/game/engine/ActorsSeenDisplay.lua new file mode 100644 index 0000000000000000000000000000000000000000..675f4aa67b64d3ac04968d0fbdfdedff61d5a2a8 --- /dev/null +++ b/game/engine/ActorsSeenDisplay.lua @@ -0,0 +1,73 @@ +-- TE4 - T-Engine 4 +-- 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 + +require "engine.class" + +module(..., package.seeall, class.make) + +function _M:init(actor, x, y, w, h, bgcolor) + self.actor = actor + self.bgcolor = bgcolor + self.font = core.display.newFont("/data/font/VeraMono.ttf", 10) + self.font_h = self.font:lineSkip() + self:resize(x, y, w, h) +end + +--- Resize the display area +function _M:resize(x, y, w, h) + self.display_x, self.display_y = math.floor(x), math.floor(y) + self.w, self.h = math.floor(w), math.floor(h) + self.surface = core.display.newSurface(w, h) + if self.actor then self.actor.changed = true end + + local cw, ch = self.font:size(" ") + self.font_w = cw +end + +-- Displays the hotkeys, keybinds & cooldowns +function _M:display() + local a = self.actor + if not a or not a.changed then return self.surface end + + self.surface:erase(self.bgcolor[1], self.bgcolor[2], self.bgcolor[3]) + + local list = {} + + -- initialize the array + for i, act in ipairs(a.fov.actors_dist) do + local n = act.name:capitalize() + list[n] = list[n] or { name=n, nb=0, dist={} } + list[n].nb = list[n].nb + 1 + list[n].dist[#list[n].dist+1] = math.floor(math.sqrt(a.fov.actors[act].sqdist)) + + local r = a:reactionToward(act) + if r > 0 then list[n].color={0,255,0} + elseif r == 0 then list[n].color={176,196,222} + elseif r < 0 then list[n].color={255,0,0} end + end + local l = {} + for _, a in pairs(list) do l[#l+1] = a end + table.sort(l, function(a, b) return a.name < b.name end) + + for i, a in ipairs(l) do + self.surface:drawColorStringBlended(self.font, ("%s (%d)#WHITE#; distance [%s]"):format(a.name, a.nb, table.concat(a.dist, ",")), 0, (i - 1) * self.font_h, a.color[1], a.color[2], a.color[3]) + end + + return self.surface +end diff --git a/game/engine/Level.lua b/game/engine/Level.lua index 7b4575e0cfc1840dd41ffdbb2e918e959ba9e2bf..dbcd4c136c69c090867a211e5116d445bca042b7 100644 --- a/game/engine/Level.lua +++ b/game/engine/Level.lua @@ -35,7 +35,7 @@ end --- Adds an entity to the level -- Only entities that need to act need to be added. Terrain features do not need this usualy function _M:addEntity(e) - if self.entities[e.uid] then error("Entity "..e.uid.." already present on the level") end + if self.entities[e.uid] then error("Entity "..e.uid.."("..e.name..") already present on the level") end self.entities[e.uid] = e table.insert(self.e_array, e) game:addEntity(e) @@ -43,7 +43,7 @@ end --- Removes an entity from the level function _M:removeEntity(e) - if not self.entities[e.uid] then error("Entity "..e.uid.." not present on the level") end + if not self.entities[e.uid] then error("Entity "..e.uid.."("..e.name..") not present on the level") end self.entities[e.uid] = nil for i = 1, #self.e_array do if self.e_array[i] == e then diff --git a/game/engine/interface/ActorInventory.lua b/game/engine/interface/ActorInventory.lua index c09b948e6aa163f8a4aa67704b340d1104b8ed24..7dc5a1b940f8596315aae5330b2ff3951d0cb68b 100644 --- a/game/engine/interface/ActorInventory.lua +++ b/game/engine/interface/ActorInventory.lua @@ -135,9 +135,11 @@ end function _M:removeObject(inven, item, no_unstack) if type(inven) == "number" then inven = self.inven[inven] end + if not inven[item] then return false, true end + local o, finish = inven[item], true - if o:check("on_preremoveobject", self, inven) then return false end + if o:check("on_preremoveobject", self, inven) then return false, true end if not no_unstack then o, finish = o:unstack() diff --git a/game/engine/interface/ActorLife.lua b/game/engine/interface/ActorLife.lua index c415eab7381696c85275c57ad0b6789e50faa386..9cfdd8d65eb5230ca1c0e99e62c8499f0f76737c 100644 --- a/game/engine/interface/ActorLife.lua +++ b/game/engine/interface/ActorLife.lua @@ -70,7 +70,7 @@ end --- Called when died function _M:die(src) - game.level:removeEntity(self) + if game.level:hasEntity(self) then game.level:removeEntity(self) end self.dead = true self.changed = true diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua index 214c7a56d84092231ee507a672dfd69f19522442..dfcfbbaa8b4e8d796ed3dbcc9791c64dd0306555 100644 --- a/game/modules/tome/class/Actor.lua +++ b/game/modules/tome/class/Actor.lua @@ -507,6 +507,17 @@ function _M:onTakeHit(value, src) end end + -- Mount takes some damage ? + local mount = self:hasMount() + if mount and mount.mount.share_damage then + mount.mount.actor:takeHit(value * mount.mount.share_damage / 100, src) + value = value * (100 - mount.mount.share_damage) / 100 + -- Remove the dead mount + if mount.mount.actor.dead and mount.mount.effect then + self:removeEffect(mount.mount.effect) + end + end + -- Achievements if src and src.resolveSource and src:resolveSource().player and value >= 600 then world:gainAchievement("SIZE_MATTERS", src:resolveSource()) @@ -534,16 +545,18 @@ function _M:die(src) if rng.percent(33) then self:bloodyDeath() end -- Drop stuff - if not self.no_drops then - for inven_id, inven in pairs(self.inven) do - for i, o in ipairs(inven) do - if not o.no_drop then - game.level.map:addObject(self.x, self.y, o) + if not self.keep_inven_on_death then + if not self.no_drops then + for inven_id, inven in pairs(self.inven) do + for i, o in ipairs(inven) do + if not o.no_drop then + game.level.map:addObject(self.x, self.y, o) + end end end end + self.inven = {} end - self.inven = {} -- Give stamina back if src and src.knowTalent and src:knowTalent(src.T_UNENDING_FRENZY) then @@ -1017,12 +1030,13 @@ end --- Suffocate a bit, lose air function _M:suffocate(value, src) - if self:attr("no_breath") then return end + if self:attr("no_breath") then return false, false end self.air = self.air - value if self.air <= 0 then game.logSeen(self, "%s suffocates to death!", self.name:capitalize()) - return self:die(src) + return self:die(src), true end + return false, true end --- Can the actor see the target actor diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua index 3e387810a1a09f006bd3ac9e3dd26a1c7901578c..6f1b2c1ea9187030a61e8f8d8bc955910095c546 100644 --- a/game/modules/tome/class/Game.lua +++ b/game/modules/tome/class/Game.lua @@ -47,6 +47,7 @@ local NPC = require "mod.class.NPC" local PlayerDisplay = require "mod.class.PlayerDisplay" local HotkeysDisplay = require "engine.HotkeysDisplay" +local ActorsSeenDisplay = require "engine.ActorsSeenDisplay" local LogDisplay = require "engine.LogDisplay" local LogFlasher = require "engine.LogFlasher" local DebugConsole = require "engine.DebugConsole" @@ -70,6 +71,8 @@ function _M:init() engine.interface.GameMusic.init(self) engine.interface.GameSound.init(self) + self.persistant_actors = {} + -- Pause at birth self.paused = true @@ -82,6 +85,7 @@ function _M:run() self.logdisplay = LogDisplay.new(0, self.h * 0.8, self.w * 0.5, self.h * 0.2, nil, nil, nil, {255,255,255}, {30,30,30}) self.player_display = PlayerDisplay.new(0, 220, 200, self.h * 0.8 - 220, {30,30,0}) self.hotkeys_display = HotkeysDisplay.new(nil, self.w * 0.5, self.h * 0.8, self.w * 0.5, self.h * 0.2, {30,30,0}) + self.npcs_display = ActorsSeenDisplay.new(nil, self.w * 0.5, self.h * 0.8, self.w * 0.5, self.h * 0.2, {30,30,0}) self.calendar = Calendar.new("/data/calendar_rivendell.lua", "Today is the %s %s of the %s year of the Fourth Age of Middle-earth.\nThe time is %02d:%02d.", 122) self.tooltip = Tooltip.new(nil, nil, {255,255,255}, {30,30,30}) self.flyers = FlyingText.new() @@ -101,6 +105,7 @@ function _M:run() if not self.player then self:newGame() end self.hotkeys_display.actor = self.player + self.npcs_display.actor = self.player self.target = Target.new(Map, self.player) self.target.target.entity = self.player @@ -173,6 +178,7 @@ function _M:onResolutionChange() self.logdisplay:resize(0, self.h * 0.8, self.w * 0.5, self.h * 0.2) self.player_display:resize(0, 220, 200, self.h * 0.8 - 220) self.hotkeys_display:resize(self.w * 0.5, self.h * 0.8, self.w * 0.5, self.h * 0.2) + self.npcs_display:resize(self.w * 0.5, self.h * 0.8, self.w * 0.5, self.h * 0.2) -- Reset mouse bindings to account for new size self:setupMouse(reset) end @@ -227,22 +233,11 @@ function _M:setupDisplayMode() end function _M:setupMiniMap() - print("[MINIMAP MODE]", self.minimap_mode) - self.minimap_mode = self.minimap_mode or (config.settings.tome and config.settings.tome.minimap_mode) or 2 - if self.minimap_mode == 1 then - print("[MINIMAP MODE] disabled") - elseif self.minimap_mode == 2 then - if self.level and self.level.map then self.level.map._map:setupMiniMapGridSize(4) end - print("[MINIMAP MODE] small") - elseif self.minimap_mode == 3 then - if self.level and self.level.map then self.level.map._map:setupMiniMapGridSize(8) end - print("[MINIMAP MODE] full") - end - self:saveSettings("tome.minimap_mode", ("tome.minimap_mode = %d\n"):format(self.minimap_mode)) + if self.level and self.level.map then self.level.map._map:setupMiniMapGridSize(4) end end function _M:save() - return class.save(self, self:defaultSavedFields{difficulty=true}, true) + return class.save(self, self:defaultSavedFields{difficulty=true, persistant_actors=true}, true) end function _M:getSaveDescription() @@ -263,6 +258,7 @@ function _M:getStore(def) end function _M:leaveLevel(level, lev, old_lev) + self.to_re_add_actors = {} if level:hasEntity(self.player) then level.exited = level.exited or {} if lev > old_lev then @@ -271,6 +267,12 @@ function _M:leaveLevel(level, lev, old_lev) level.exited.up = {x=self.player.x, y=self.player.y} end level.last_turn = game.turn + for act, _ in pairs(self.persistant_actors) do + if level:hasEntity(act) then + level:removeEntity(act) + self.to_re_add_actors[act] = true + end + end level:removeEntity(self.player) end end @@ -324,8 +326,18 @@ function _M:changeLevel(lev, zone) self.player:move(self.level.downs[1].x, self.level.downs[1].y, true) end end - self.level:addEntity(self.player) self.player.changed = true + if self.to_re_add_actors then for act, _ in pairs(self.to_re_add_actors) do + local x, y = util.findFreeGrid(self.player.x, self.player.y, 20, true, {[Map.ACTOR]=true}) + if x then act:move(x, y, true) end + end end + + -- Re add entities + self.level:addEntity(self.player) + if self.to_re_add_actors then for act, _ in pairs(self.to_re_add_actors) do + self.level:addEntity(act) + act:setTarget(nil) + end end if self.zone.on_enter then self.zone.on_enter(lev, old_lev, zone) @@ -386,13 +398,6 @@ function _M:onTurn() end function _M:display() - -- We display the player's interface - self.flash:display():toScreen(self.flash.display_x, self.flash.display_y) - self.logdisplay:display():toScreen(self.logdisplay.display_x, self.logdisplay.display_y) - self.player_display:display():toScreen(self.player_display.display_x, self.player_display.display_y) - self.hotkeys_display:display():toScreen(self.hotkeys_display.display_x, self.hotkeys_display.display_y) - if self.player then self.player.changed = false end - -- Now the map, if any if self.level and self.level.map and self.level.map.finished then -- Display the map and compute FOV for the player if needed @@ -436,22 +441,20 @@ function _M:display() end self.old_tmx, self.old_tmy = tmx, tmy + -- Minimap display self.level.map:minimapDisplay(0, 20, util.bound(self.player.x - 25, 0, self.level.map.w - 50), util.bound(self.player.y - 25, 0, self.level.map.h - 50), 50, 50, 1) ---[[ - if self.minimap_mode == 3 then - local mx, my = 0, 0 - local mw, mh = math.floor((self.w - 200) / 8), math.floor(self.h * .80 / 8) - - mx = self.player.x - math.floor(mw / 2) - my = self.player.y - math.floor(mh / 2) - - if self.level.map.w < mw then mx = math.floor((self.level.map.w - mw) / 2) end - if self.level.map.h < mh then my = math.floor((self.level.map.h - mh) / 2) end + end - self.level.map:minimapDisplay(200, 20, mx, my, mw, mh, 0.9) - end -]] + -- We display the player's interface + self.flash:display():toScreen(self.flash.display_x, self.flash.display_y) + self.logdisplay:display():toScreen(self.logdisplay.display_x, self.logdisplay.display_y) + self.player_display:display():toScreen(self.player_display.display_x, self.player_display.display_y) + if self.show_npc_list then + self.npcs_display:display():toScreen(self.npcs_display.display_x, self.npcs_display.display_y) + else + self.hotkeys_display:display():toScreen(self.hotkeys_display.display_x, self.hotkeys_display.display_y) end + if self.player then self.player.changed = false end engine.GameTurnBased.display(self) end @@ -734,11 +737,9 @@ function _M:setupCommands() self:setupDisplayMode() end, - -- Toggle mini map - TOGGLE_MINIMAP = function() - self.minimap_mode = self.minimap_mode or 1 - self.minimap_mode = util.boundWrap(self.minimap_mode + 1, 1, 3) - self:setupMiniMap() + -- Toggle monster list + TOGGLE_NPC_LIST = function() + self.show_npc_list = not self.show_npc_list end, EXIT = function() diff --git a/game/modules/tome/class/Object.lua b/game/modules/tome/class/Object.lua index 6b7535642bb7eb3029ff29a30383420ad390f1df..bff531e7d08e1b247c4f90313c9848d2102e8ba8 100644 --- a/game/modules/tome/class/Object.lua +++ b/game/modules/tome/class/Object.lua @@ -162,7 +162,7 @@ function _M:getTextualDesc() desc[#desc+1] = ("Type: %s / %s"):format(self.type, self.subtype) -- Stop here if unided - if not self:isIdentified() then return table.concat(desc, "\n") end + if not self:isIdentified() then return desc end if self.combat then local dm = {} diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua index c64c54bbab73f96055841ebad826c0694eb4b327..ac1d8e827063dbe92573f9442e184e220a05c082 100644 --- a/game/modules/tome/class/Player.lua +++ b/game/modules/tome/class/Player.lua @@ -250,6 +250,16 @@ function _M:die(src) end end +--- Suffocate a bit, lose air +function _M:suffocate(value, src) + local dead, affected = mod.class.Actor.suffocate(self, value, src) + if affected then + self:runStop("suffocating") + self:restStop("suffocating") + end + return dead, affected +end + function _M:setName(name) self.name = name game.save_name = name diff --git a/game/modules/tome/class/interface/Combat.lua b/game/modules/tome/class/interface/Combat.lua index 1d194a89629209987ba90d11a23d768a8e992041..526533ec47fd6cd79a78831aaeeacff3d28ce920 100644 --- a/game/modules/tome/class/interface/Combat.lua +++ b/game/modules/tome/class/interface/Combat.lua @@ -123,6 +123,12 @@ function _M:attackTarget(target, damtype, mult, noenergy) elseif not hit and not sound_miss then sound_miss = self.combat.sound_miss end end + -- Mount attack ? + local mount = self:hasMount() + if mount and mount.mount.attack_with_rider and math.floor(core.fov.distance(self.x, self.y, target.x, target.y)) <= 1 then + mount.mount.actor:attackTarget(target, nil, nil, nil) + end + -- We use up our own energy if speed and not noenergy then self:useEnergy(game.energy_to_act * speed) @@ -662,3 +668,13 @@ function _M:hasMassiveArmor() end return armor end + +--- Check if the actor has a mount +function _M:hasMount() + if not self:getInven("MOUNT") then return end + local mount = self:getInven("MOUNT")[1] + if not mount or mount.type ~= "mount" then + return nil + end + return mount +end diff --git a/game/modules/tome/data/autolevel_schemes.lua b/game/modules/tome/data/autolevel_schemes.lua index f7b04b38627f26b19d957945686531f0021c8a7c..182fe9c5d3f3c5709b80f7226d806541f8003916 100644 --- a/game/modules/tome/data/autolevel_schemes.lua +++ b/game/modules/tome/data/autolevel_schemes.lua @@ -65,3 +65,7 @@ end} Autolevel:registerScheme{ name = "spider", levelup = function(self) self:learnStats{ self.STAT_CUN, self.STAT_WIL, self.STAT_MAG, self.STAT_DEX, self.STAT_DEX } end} + +Autolevel:registerScheme{ name = "alchemy-golem", levelup = function(self) + self:learnStats{ self.STAT_STR, self.STAT_STR, self.STAT_DEX, self.STAT_CON } +end} diff --git a/game/modules/tome/data/birth/descriptors.lua b/game/modules/tome/data/birth/descriptors.lua index 97f00a577b8c97932b95887e3092aefab6c18448..86b37152b1b93bfe8f9f147378d5540c9b005682 100644 --- a/game/modules/tome/data/birth/descriptors.lua +++ b/game/modules/tome/data/birth/descriptors.lua @@ -38,7 +38,7 @@ newBirthDescriptor{ }, talents = {}, experience = 1.0, - body = { INVEN = 1000, QS_MAINHAND = 1, QS_OFFHAND = 1, MAINHAND = 1, OFFHAND = 1, FINGER = 2, NECK = 1, LITE = 1, BODY = 1, HEAD = 1, HANDS = 1, FEET = 1, TOOL = 1, QUIVER = 1 }, + body = { INVEN = 1000, QS_MAINHAND = 1, QS_OFFHAND = 1, MAINHAND = 1, OFFHAND = 1, FINGER = 2, NECK = 1, LITE = 1, BODY = 1, HEAD = 1, HANDS = 1, FEET = 1, TOOL = 1, QUIVER = 1, MOUNT = 1 }, copy = { -- Mages are unheard of at first, nobody but them regenerates mana diff --git a/game/modules/tome/data/general/objects/mounts.lua b/game/modules/tome/data/general/objects/mounts.lua new file mode 100644 index 0000000000000000000000000000000000000000..bf66d58c8e9e5c92bdeb105a896bb537daf1d689 --- /dev/null +++ b/game/modules/tome/data/general/objects/mounts.lua @@ -0,0 +1,37 @@ +-- 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 + +newEntity{ + define_as = "BASE_MOUNT", + slot = "MOUNT", + type = "mount", + display = "&", color=colors.SLATE, + encumber = 0, + desc = [[A mount]], +} + +newEntity{ base = "BASE_MOUNT", define_as = "ALCHEMIST_GOLEM_MOUNT", + subtype = "golem", + name = "alchemist golem mount", + cost = 0, + mount = { + share_damage = 75, + attack_with_rider = 1, + }, +} diff --git a/game/modules/tome/data/general/objects/objects.lua b/game/modules/tome/data/general/objects/objects.lua index 95580f31e02ac5b359fc4bb94c65906c50759f4e..f90576be2401e4c64607d8421f080cd776632bd1 100644 --- a/game/modules/tome/data/general/objects/objects.lua +++ b/game/modules/tome/data/general/objects/objects.lua @@ -60,6 +60,9 @@ load("/data/general/objects/leather-boots.lua") load("/data/general/objects/heavy-boots.lua") --load("/data/general/objects/gloves.lua") +-- Mounts +load("/data/general/objects/mounts.lua") + -- Artifacts load("/data/general/objects/world-artifacts.lua") load("/data/general/objects/quest-artifacts.lua") diff --git a/game/modules/tome/data/keybinds/tome.lua b/game/modules/tome/data/keybinds/tome.lua new file mode 100644 index 0000000000000000000000000000000000000000..f0e412b2cd20cb683d9bcc1d99f89af33ad2641e --- /dev/null +++ b/game/modules/tome/data/keybinds/tome.lua @@ -0,0 +1,25 @@ +-- 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 + +defineAction{ + default = { "sym:9:false:false:false:false" }, + type = "TOGGLE_NPC_LIST", + group = "actions", + name = "Toggle list of seen creatures", +} diff --git a/game/modules/tome/data/talents/spells/advanced-golemancy.lua b/game/modules/tome/data/talents/spells/advanced-golemancy.lua new file mode 100644 index 0000000000000000000000000000000000000000..4fcac4eee54c687efbbd05110d80acb0647a24b6 --- /dev/null +++ b/game/modules/tome/data/talents/spells/advanced-golemancy.lua @@ -0,0 +1,150 @@ +-- 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 + +newTalent{ + name = "Golem Power", + type = {"spell/advanced-golemancy", 1}, + mode = "passive", + require = spells_req1, + points = 5, + on_learn = function(self, t) + self.alchemy_golem:learnTalent(self.T_WEAPON_COMBAT, true) + self.alchemy_golem:learnTalent(self.T_SWORD_MASTERY, true) + self.alchemy_golem:learnTalent(self.T_MACE_MASTERY, true) + self.alchemy_golem:learnTalent(self.T_AXE_MASTERY, true) + self.alchemy_golem:learnTalent(self.T_HEAVY_ARMOUR_TRAINING, true) + self.alchemy_golem:learnTalent(self.T_MASSIVE_ARMOUR_TRAINING, true) + end, + on_unlearn = function(self, t) + self.alchemy_golem:unlearnTalent(self.T_WEAPON_COMBAT, true) + self.alchemy_golem:unlearnTalent(self.T_SWORD_MASTERY, true) + self.alchemy_golem:unlearnTalent(self.T_MACE_MASTERY, true) + self.alchemy_golem:unlearnTalent(self.T_AXE_MASTERY, true) + self.alchemy_golem:unlearnTalent(self.T_HEAVY_ARMOUR_TRAINING, true) + self.alchemy_golem:unlearnTalent(self.T_MASSIVE_ARMOUR_TRAINING, true) + end, + info = function(self, t) + return ([[Improves your golem proficiency with two handed weapons.]]) + end, +} + +newTalent{ + name = "Golem Resilience", + type = {"spell/advanced-golemancy", 2}, + mode = "passive", + require = spells_req2, + points = 5, + on_learn = function(self, t) + self.alchemy_golem:learnTalent(self.T_HEALTH, true) + self.alchemy_golem:learnTalent(self.T_HEAVY_ARMOUR_TRAINING, true) + self.alchemy_golem:learnTalent(self.T_MASSIVE_ARMOUR_TRAINING, true) + end, + on_unlearn = function(self, t) + self.alchemy_golem:unlearnTalent(self.T_HEALTH, true) + self.alchemy_golem:unlearnTalent(self.T_HEAVY_ARMOUR_TRAINING, true) + self.alchemy_golem:unlearnTalent(self.T_MASSIVE_ARMOUR_TRAINING, true) + end, + info = function(self, t) + return ([[Improves your golem armour training and health.]]) + end, +} + +newTalent{ + name = "Golem: Pound", + type = {"spell/advanced-golemancy", 3}, + require = spells_req3, + points = 5, + cooldown = 15, + range = 10, + mana = 5, + action = function(self, t) + if not game.level:hasEntity(self.alchemy_golem) then + game.logPlayer(self, "Your golem is currently inactive.") + return + end + + local tg = {type="ball", radius=2, range=self:getTalentRange(t)} + game.target.source_actor = self.alchemy_golem + 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 + if math.floor(core.fov.distance(self.alchemy_golem.x, self.alchemy_golem.y, x, y)) > self:getTalentRange(t) then return nil end + + local l = line.new(self.alchemy_golem.x, self.alchemy_golem.y, x, y) + local lx, ly = l() + local tx, ty = self.alchemy_golem.x, self.alchemy_golem.y + lx, ly = l() + while lx and ly do + if game.level.map:checkAllEntities(lx, ly, "block_move", self.alchemy_golem) then break end + tx, ty = lx, ly + lx, ly = l() + end + + self.alchemy_golem:move(tx, ty, true) + + -- Attack & daze + self.alchemy_golem:project({type="ball", radius=2, friendlyfire=false}, tx, ty, function(xx, yy) + local target = game.level.map(xx, yy, Map.ACTOR) + if target and self.alchemy_golem:attackTarget(target, nil, self.alchemy_golem:combatTalentWeaponDamage(t, 0.4, 1.1), true) then + if target:checkHit(self.alchemy_golem:combatAttackStr(), target:combatPhysicalResist(), 0, 95, 10 - self.alchemy_golem:getTalentLevel(t) / 2) and target:canBe("stun") then + target:setEffect(target.EFF_DAZED, 2 + self.alchemy_golem:getTalentLevel(t), {}) + else + game.logSeen(target, "%s resists the dazing blow!", target.name:capitalize()) + end + end + end) + + return true + end, + info = function(self, t) + return ([[Your golem rushes to the target, pounding the area, dazing all for %d turns and doing %d%% damage.]]): + format(2 + self.alchemy_golem:getTalentLevel(t), 100 * self:combatTalentWeaponDamage(t, 0.4, 1.1)) + end, +} + +newTalent{ + name = "Mount Golem", + type = {"spell/advanced-golemancy",4}, + require = spells_req4, + points = 5, + mana = 40, + cooldown = 30, + action = function(self, t) + if not game.level:hasEntity(self.alchemy_golem) then + game.logPlayer(self, "Your golem is currently inactive.") + return + end + if math.floor(core.fov.distance(self.x, self.y, self.alchemy_golem.x, self.alchemy_golem.y)) > 1 then + game.logPlayer(self, "You are too far away from your golem.") + return + end + + -- Create the mount item + local mount = game.zone:makeEntityByName(game.level, "object", "ALCHEMIST_GOLEM_MOUNT") + if not mount then return end + mount.mount.actor = self.alchemy_golem + self:setEffect(self.EFF_GOLEM_MOUNT, 5 + math.ceil(self:getTalentLevel(t) * 4), {mount=mount}) + + return true + end, + info = function(self, t) + return ([[Mount inside your golem, directly controlling it and splitting the damage between both it and you for %d turns.]]): + format(5 + math.ceil(self:getTalentLevel(t) * 4)) + end, +} diff --git a/game/modules/tome/data/talents/spells/alchemy.lua b/game/modules/tome/data/talents/spells/alchemy.lua index 7acbb21431c50dc912b8d9b810dc6d8c22925f3e..3ade657dceaab38a7d2c37a34a8ee25ae3b7bea6 100644 --- a/game/modules/tome/data/talents/spells/alchemy.lua +++ b/game/modules/tome/data/talents/spells/alchemy.lua @@ -62,7 +62,7 @@ newTalent{ require = spells_req1, points = 5, mana = 5, - cooldown = 8, + cooldown = 4, range = function(self, t) return math.ceil(5 + self:getDex(12)) end, diff --git a/game/modules/tome/data/talents/spells/golemancy.lua b/game/modules/tome/data/talents/spells/golemancy.lua index 2eb67336dd00547cae0f7e55261b7a578bee47d5..436314b6520df138a63491635446aa9ea6605527 100644 --- a/game/modules/tome/data/talents/spells/golemancy.lua +++ b/game/modules/tome/data/talents/spells/golemancy.lua @@ -16,12 +16,14 @@ -- -- Nicolas Casalini "DarkGod" -- darkgod@te4.org +local Chat = require "engine.Chat" local function makeGolem() return require("mod.class.NPC").new{ type = "construct", subtype = "golem", display = 'g', color=colors.WHITE, level_range = {1, 50}, + life_rating = 13, combat = { dam=10, atk=10, apr=0, dammod={str=1} }, @@ -30,12 +32,24 @@ local function makeGolem() rank = 3, size_category = 4, - autolevel = "warrior", + 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", 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 }, - no_auto_resists = true, + keep_inven_on_death = true, +-- no_auto_resists = true, open_door = true, blind_immune = 1, fear_immune = 1, @@ -56,12 +70,15 @@ newTalent{ action = function(self, t) if not self.alchemy_golem then self.alchemy_golem = game.zone:finishEntity(game.level, "actor", makeGolem()) + game.persistant_actors[self.alchemy_golem] = 1 if not self.alchemy_golem then return end self.alchemy_golem.faction = self.faction self.alchemy_golem.name = "golem (servant of "..self.name..")" self.alchemy_golem.summoner = self self.alchemy_golem.summoner_gain_exp = true - else + end + + local wait = function() local co = coroutine.running() local ok = false self:restInit(20, "refitting", "refitted", function(cnt, max) @@ -75,8 +92,31 @@ newTalent{ end end - if game.level:hasEntity(self.alchemy_golem) then + 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 + self.alchemy_golem:heal(self:combatTalentSpellDamage(t, 15, 150, (ammo.alchemist_power + self:combatSpellpower()) / 2)) + + -- resurrect the golem else + 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 + wait() + 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 @@ -93,7 +133,13 @@ newTalent{ return true end, info = function(self, t) - return ([[Interract with your golem, reviving it if it is dead, healing it, ...]]) + 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) end, } diff --git a/game/modules/tome/data/talents/spells/spells.lua b/game/modules/tome/data/talents/spells/spells.lua index ff763ccf6cf6c4373ced4c84bdd0b14e50eff246..5d4c8993c985238d474b234528bf5306a08f9833 100644 --- a/game/modules/tome/data/talents/spells/spells.lua +++ b/game/modules/tome/data/talents/spells/spells.lua @@ -80,4 +80,5 @@ load("/data/talents/spells/enhancement.lua") load("/data/talents/spells/alchemy.lua") load("/data/talents/spells/infusion.lua") load("/data/talents/spells/golemancy.lua") +load("/data/talents/spells/advanced-golemancy.lua") load("/data/talents/spells/staff-combat.lua") diff --git a/game/modules/tome/data/talents/spells/staff-combat.lua b/game/modules/tome/data/talents/spells/staff-combat.lua index 01467391154b3410d21629d0571271afeee5b769..f55fa89f3f9932e5e7adddb0c2c2f1085bfea033 100644 --- a/game/modules/tome/data/talents/spells/staff-combat.lua +++ b/game/modules/tome/data/talents/spells/staff-combat.lua @@ -106,6 +106,7 @@ newTalent{ require = spells_req4, points = 5, mana = 30, + cooldown = 6, tactical = { ATTACK = 10, }, diff --git a/game/modules/tome/data/timed_effects.lua b/game/modules/tome/data/timed_effects.lua index 741260449bca9a0b686b75072df07c84846c5e02..290b973bea3f23f6f23ff4d4ff987eecedbab272 100644 --- a/game/modules/tome/data/timed_effects.lua +++ b/game/modules/tome/data/timed_effects.lua @@ -780,3 +780,26 @@ newEffect{ self:removeTemporaryValue("martyrdom", eff.tmpid) end, } + +newEffect{ + name = "GOLEM_MOUNT", + desc = "Golem Mount", + type = "physical", + status = "beneficial", + parameters = { }, + activate = function(self, eff) + self:wearObject(eff.mount, true, true) + game.level:removeEntity(eff.mount.mount.actor) + eff.mount.mount.effect = self.EFF_GOLEM_MOUNT + end, + deactivate = function(self, eff) + if self:removeObject(self.INVEN_MOUNT, 1, true) then + -- Find space + local x, y = util.findFreeGrid(self.x, self.y, 10, true, {[engine.Map.ACTOR]=true}) + if x then + eff.mount.mount.actor:move(x, y, true) + game.level:addEntity(eff.mount.mount.actor) + end + end + end, +} diff --git a/game/modules/tome/load.lua b/game/modules/tome/load.lua index e2f8177e0e04e0a6dc86bb9672a8b6c8a1ddef12..55b2d691356e2062e1d44c805adad80936da4b61 100644 --- a/game/modules/tome/load.lua +++ b/game/modules/tome/load.lua @@ -49,7 +49,7 @@ _3DNoise = n:makeTexture3D(64, 64, 64, 0, 0, 0) WorldAchievements:loadDefinition("/data/achievements/") -- Usefull keybinds -KeyBind:load("move,hotkeys,inventory,actions,debug") +KeyBind:load("move,hotkeys,inventory,actions,tome,debug") -- Additional entities resolvers dofile("/mod/resolvers.lua") @@ -70,6 +70,7 @@ ActorInventory:defineInventory("HANDS", "On hands", true, "Various gloves can be ActorInventory:defineInventory("FEET", "On feet", true, "Sandals or boots can be worn on your feet.") ActorInventory:defineInventory("TOOL", "Tool", true, "This is your readied tool, always available immediately.") ActorInventory:defineInventory("QUIVER", "Quiver", true, "Your readied ammo.") +ActorInventory:defineInventory("MOUNT", "Mount", false, "Your mount.") -- Damage types DamageType:loadDefinition("/data/damage_types.lua") diff --git a/ideas/crafting.ods b/ideas/crafting.ods index 25d04e752174cd90e1c0756ec4c1615e940813bb..5ae6ffd6b1fc12042dcccf9e4b17a68f29da6a18 100644 Binary files a/ideas/crafting.ods and b/ideas/crafting.ods differ