diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua index bec05aa3c162225ad3d2efb6738401f5493b2cc9..ea80a8b1e030b4184ca263816d8e8a1cd554df7d 100644 --- a/game/modules/tome/class/Actor.lua +++ b/game/modules/tome/class/Actor.lua @@ -3582,11 +3582,11 @@ function _M:levelupClass(c_data) if self.extra_talent_point_every and c_data.last_level % self.extra_talent_point_every == 0 then self.unused_talents = self.unused_talents + 1 end if self.extra_generic_point_every and c_data.last_level % self.extra_generic_point_every == 0 then self.unused_generics = self.unused_generics + 1 end - -- At levels 10, 20 and 36 and then every 30 levels, we gain a new talent type - if c_data.last_level == 10 or c_data.last_level == 20 or c_data.last_level == 36 or (c_data.last_level > 50 and (c_data.last_level - 6) % 30 == 0) then + -- At levels 10, 20 and 34 and then every 30 levels, we gain a new talent type + if c_data.last_level == 10 or c_data.last_level == 20 or c_data.last_level == 34 or (c_data.last_level > 50 and (c_data.last_level - 4) % 30 == 0) then self.unused_talents_types = self.unused_talents_types + 1 end - -- if c_data.last_level == 30 or c_data.last_level == 42 then self.unused_prodigies = self.unused_prodigies + 1 end + -- if c_data.last_level == 25 or c_data.last_level == 42 then self.unused_prodigies = self.unused_prodigies + 1 end elseif type(self.no_points_on_levelup) == "function" then self:no_points_on_levelup() end @@ -3838,8 +3838,8 @@ function _M:levelup() if self.extra_talent_point_every and self.level % self.extra_talent_point_every == 0 then self.unused_talents = self.unused_talents + 1 end if self.extra_generic_point_every and self.level % self.extra_generic_point_every == 0 then self.unused_generics = self.unused_generics + 1 end - -- At levels 10, 20 and 36 and then every 30 levels, we gain a new talent type - if self.level == 10 or self.level == 20 or self.level == 34 or (self.level > 50 and (self.level - 6) % 30 == 0) then + -- At levels 10, 20 and 34 and then every 30 levels, we gain a new talent type + if self.level == 10 or self.level == 20 or self.level == 34 or (self.level > 50 and (self.level - 4) % 30 == 0) then self.unused_talents_types = self.unused_talents_types + 1 end if self.level == 25 or self.level == 42 then diff --git a/game/modules/tome/class/GameState.lua b/game/modules/tome/class/GameState.lua index 9379833a306948d24799060676154bdd29b6bb7a..0f621827e6c41d90f1fb4c8173136c45812bdbb7 100644 --- a/game/modules/tome/class/GameState.lua +++ b/game/modules/tome/class/GameState.lua @@ -2281,6 +2281,7 @@ end -- @param data.ai = ai_type <"tactical" if rank>3 or base.ai> -- @param data.ai_tactic = tactical weights table for the tactical ai <nil - generated based on talents> -- @param data.no_loot_randart set true to not drop a randart <nil> +-- @param data.loot_fixedart set true to drop a fixedart <nil> -- @param data.on_die set true to run base.rng_boss_on_die and base.rng_boss_on_die_custom on death <nil> -- @param data.name_scheme <randart_name_rules.default> -- @param data.post = function(b, data) to run last to finish generation @@ -2374,6 +2375,7 @@ function _M:createRandomBoss(base, data) -- Boss worthy drops b[#b+1] = resolvers.drops{chance=100, nb=data.loot_quantity or 3, {tome_drops=data.loot_quality or "boss"} } if not data.no_loot_randart then b[#b+1] = resolvers.drop_randart{} end + if data.loot_unique then b[#b+1] = resolvers.drops{chance=100, nb=1, {unique=true, not_properties={"lore"}} } end -- On die if data.on_die then @@ -2658,6 +2660,7 @@ end -- @field data.ai = ai_type <"tactical" if rank>3 or base.ai> -- @field data.ai_tactic = tactical weights table for the tactical ai <nil - generated based on talents> -- @field data.no_loot_randart set true to not drop a randart <nil> +-- @field data.loot_fixedart set true to drop a fixedart <nil> -- @field data.on_die set true to run base.rng_boss_on_die and base.rng_boss_on_die_custom on death <nil> -- @field data.name_scheme <randart_name_rules.default> -- @field data.post = function(b, data) to run last to finish generation @@ -2746,7 +2749,8 @@ function _M:createRandomBossNew(base, data) -- Boss worthy drops b[#b+1] = resolvers.drops{chance=100, nb=data.loot_quantity or 3, {tome_drops=data.loot_quality or "boss"} } if not data.no_loot_randart then b[#b+1] = resolvers.drop_randart{} end - + if data.loot_unique then b[#b+1] = resolvers.drops{chance=100, nb=1, {unique=true, not_properties={"lore"}} } end + -- On die if data.on_die then b.rng_boss_on_die = b.on_die diff --git a/game/modules/tome/class/Object.lua b/game/modules/tome/class/Object.lua index 64aa5f273237e7e2d661704ba3ba14c387523273..ad435e4346d1ccbba63fe1742786e8557d07944b 100644 --- a/game/modules/tome/class/Object.lua +++ b/game/modules/tome/class/Object.lua @@ -447,7 +447,7 @@ function _M:descAttribute(attr) return c.block.." block" end elseif attr == "ARMOR" then - return (self.wielder and self.wielder.combat_def or 0).." def, "..(self.wielder and self.wielder.combat_armor or 0).." armour" + return (self.wielder and self.wielder.combat_def and math.round(self.wielder.combat_def) or 0).." def, "..(self.wielder and self.wielder.combat_armor and math.round(self.wielder.combat_armor) or 0).." armour" elseif attr == "ATTACK" then return (self.wielder and self.wielder.combat_atk or 0).." accuracy, "..(self.wielder and self.wielder.combat_apr or 0).." apr, "..(self.wielder and self.wielder.combat_dam or 0).." power" elseif attr == "MONEY" then diff --git a/game/modules/tome/data/birth/races/human.lua b/game/modules/tome/data/birth/races/human.lua index 2e1b8dfca79bc3c366134dd2f961c54c0a79ee27..5f7190066310242a028c85e6880baa6778ece8e1 100644 --- a/game/modules/tome/data/birth/races/human.lua +++ b/game/modules/tome/data/birth/races/human.lua @@ -162,7 +162,7 @@ newBirthDescriptor name = "Cornac", desc = { "Cornacs are Humans from the northern parts of the Allied Kingdoms.", - "Humans are an inherently very adaptable race and as such they gain a #GOLD#talent category point#WHITE# (others only gain one at levels 10, 20 and 36) and both #GOLD#a class and a generic talent point#WHITE# at birth and every 10 levels.", + "Humans are an inherently very adaptable race and as such they gain a #GOLD#talent category point#WHITE# (others only gain one at levels 10, 20 and 34) and both #GOLD#a class and a generic talent point#WHITE# at birth and every 10 levels.", "#GOLD#Stat modifiers:", "#LIGHT_BLUE# * +0 Strength, +0 Dexterity, +0 Constitution", "#LIGHT_BLUE# * +0 Magic, +0 Willpower, +0 Cunning", diff --git a/game/modules/tome/data/chats/escort-quest.lua b/game/modules/tome/data/chats/escort-quest.lua index 6f323ec791f3f2ab62aa0874ff83d9cec55d8e07..b68b8b0a579af83d6288527a1d9f59f45868ad00 100644 --- a/game/modules/tome/data/chats/escort-quest.lua +++ b/game/modules/tome/data/chats/escort-quest.lua @@ -52,7 +52,7 @@ local reward_types = { types = { ["wild-gift/call"] = 1.0, }, - saves = { mental = 4 }, + saves = { mental = 12 }, talents = { [Talents.T_NATURE_TOUCH] = 1, [Talents.T_EARTH_S_EYES] = 1, @@ -85,7 +85,7 @@ local reward_types = { [Talents.T_PSIBLADES] = 1, [Talents.T_THORN_GRAB] = 1, }, - saves = { spell = 4 }, + saves = { spell = 12 }, stats = { [Stats.STAT_WIL] = 5, [Stats.STAT_DEX] = 5, @@ -128,7 +128,7 @@ local reward_types = { [Talents.T_SKATE] = 1, [Talents.T_TELEKINETIC_LEAP] = 1, }, - saves = { spell = 4, phys = 4 }, + saves = { spell = 12, phys = 12 }, stats = { [Stats.STAT_CUN] = 5, [Stats.STAT_WIL] = 5, @@ -156,7 +156,7 @@ local reward_types = { [Talents.T_RESONANCE_FIELD] = 1, [Talents.T_CONVERSION] = 1, }, - saves = { spell = 4, mental = 4 }, + saves = { spell = 12, mental = 12 }, stats = { [Stats.STAT_CUN] = 5, [Stats.STAT_WIL] = 5, @@ -183,7 +183,7 @@ local reward_types = { [Talents.T_SLEEP] = 1, [Talents.T_DREAM_WALK] = 1, }, - saves = { spell = 4 }, + saves = { spell = 12 }, stats = { [Stats.STAT_WIL] = 5, [Stats.STAT_CUN] = 5, diff --git a/game/modules/tome/data/chats/gates-of-morning-main.lua b/game/modules/tome/data/chats/gates-of-morning-main.lua index f41163f6de44754ad53a07a8260342a85ae52de9..6963921f4ec0800660829727d0d67f72f0e2f250 100644 --- a/game/modules/tome/data/chats/gates-of-morning-main.lua +++ b/game/modules/tome/data/chats/gates-of-morning-main.lua @@ -73,17 +73,107 @@ newChat{ id="prides-dead", Now they are dead? At the hands of just one @playerdescriptor.race@? Truly I am amazed by your power. While you were busy bringing an end to the orcs, we managed to discover some parts of the truth from a captive orc. He talked about the shield protecting the High Peak. It seems to be controlled by "orbs of command" which the masters of the Prides had in their possession. -Look for them if you have not yet found them. He also said the only way to enter the peak and de-activate the shield is through the "slime tunnels", located somewhere in one of the Prides, probably Grushnak. ]], answers = { - {"Thanks, my lady. I will look for the tunnel and venture inside the Peak.", action=function(npc, player) + {"Thanks, my lady. I have not been able to find all of the orbs of command in my travels; could you have some of your men search for me?", + jump="prides-dead-orbs-missing", + cond=function(npc, player) return not (game.party:findInAllPartyInventoriesBy("define_as", "ORB_DRAGON") and game.party:findInAllPartyInventoriesBy("define_as", "ORB_DESTRUCTION") and game.party:findInAllPartyInventoriesBy("define_as", "ORB_UNDEATH") and game.party:findInAllPartyInventoriesBy("define_as", "ORB_ELEMENTS")) end + }, + {"Thanks, my lady. I will look for the tunnel and venture inside the Peak.", + cond=function(npc, player) return (game.party:findInAllPartyInventoriesBy("define_as", "ORB_DRAGON") and game.party:findInAllPartyInventoriesBy("define_as", "ORB_DESTRUCTION") and game.party:findInAllPartyInventoriesBy("define_as", "ORB_UNDEATH") and game.party:findInAllPartyInventoriesBy("define_as", "ORB_ELEMENTS")) end, + action=function(npc, player) + player:setQuestStatus("orc-pride", engine.Quest.DONE) + player:grantQuest("high-peak") + end}, + }, +} + +newChat{ id="prides-dead-orbs-missing", + text = [[I have already sent parties to clear out the remainder of the prides as you progressed, and have instructed to keep a sharp eye out for any orbs of command you may have missed. + Which do you not have? I can check with the parties if they found any. Our sources indicate that you should have four: one of Undeath, one of Destruction, one of Dragons, and one of Elemental might.]], + answers = { + {"The orb of Undeath.", + jump="prides-dead-orbs-missing-undeath", + cond=function(npc, player) return not (game.party:findInAllPartyInventoriesBy("define_as", "ORB_UNDEATH")) end + }, + {"The orb of Destruction.", + jump="prides-dead-orbs-missing-destruction", + cond=function(npc, player) return not (game.party:findInAllPartyInventoriesBy("define_as", "ORB_DESTRUCTION")) end + }, + {"The orb of Dragons.", + jump="prides-dead-orbs-missing-dragon", + cond=function(npc, player) return not (game.party:findInAllPartyInventoriesBy("define_as", "ORB_DRAGON")) end + }, + {"The orb of Elements.", + jump="prides-dead-orbs-missing-elements", + cond=function(npc, player) return not (game.party:findInAllPartyInventoriesBy("define_as", "ORB_ELEMENTS")) end + }, + {"Thanks, my lady, that is all of them. I will look for the tunnel and venture inside the Peak.", + cond=function(npc, player) return (game.party:findInAllPartyInventoriesBy("define_as", "ORB_DRAGON") and game.party:findInAllPartyInventoriesBy("define_as", "ORB_DESTRUCTION") and game.party:findInAllPartyInventoriesBy("define_as", "ORB_UNDEATH") and game.party:findInAllPartyInventoriesBy("define_as", "ORB_ELEMENTS")) end, + action=function(npc, player) player:setQuestStatus("orc-pride", engine.Quest.DONE) player:grantQuest("high-peak") end}, }, } +newChat{ id="prides-dead-orbs-missing-undeath", + text = [[Ah yes, my men have found that in Rak'Shor Pride. Here: ]], + answers = { + {"Thank you, my lady.", + jump="prides-dead-orbs-missing", + action = function(npc, player) + local orb = game.zone:makeEntityByName(game.level, "object", "ORB_UNDEATH", true) + orb:identify(true) + game.zone:addEntity(game.level, orb, "object") + player:addObject(player:getInven("INVEN"), orb) + end}, + } +} + +newChat{ id="prides-dead-orbs-missing-elements", + text = [[Ah yes, my men have found that in Vor Pride. Here: ]], + answers = { + {"Thank you, my lady.", + jump="prides-dead-orbs-missing", + action = function(npc, player) + local orb = game.zone:makeEntityByName(game.level, "object", "ORB_ELEMENTS", true) + orb:identify(true) + game.zone:addEntity(game.level, orb, "object") + player:addObject(player:getInven("INVEN"), orb) + end}, + } +} + +newChat{ id="prides-dead-orbs-missing-destruction", + text = [[Ah yes, my men have found that in Grushnak Pride. Here: ]], + answers = { + {"Thank you, my lady.", + jump="prides-dead-orbs-missing", + action = function(npc, player) + local orb = game.zone:makeEntityByName(game.level, "object", "ORB_DESTRUCTION", true) + orb:identify(true) + game.zone:addEntity(game.level, orb, "object") + player:addObject(player:getInven("INVEN"), orb) + end}, + } +} + +newChat{ id="prides-dead-orbs-missing-dragon", + text = [[Ah yes, my men have found that in Gorbat Pride. Here: ]], + answers = { + {"Thank you, my lady.", + jump="prides-dead-orbs-missing", + action = function(npc, player) + local orb = game.zone:makeEntityByName(game.level, "object", "ORB_DRAGON", true) + orb:identify(true) + game.zone:addEntity(game.level, orb, "object") + player:addObject(player:getInven("INVEN"), orb) + end}, + } +} + newChat{ id="clues", text = [[As much as I would like to help, our forces are already spread too thin; we cannot provide you with direct assistance. But I might be able to help you by explaining how the Pride is organised. diff --git a/game/modules/tome/data/damage_types.lua b/game/modules/tome/data/damage_types.lua index dcb276292ac51d8a88843f34e0453a5c2535f8c3..337704138fb756d97d4b4bacbb41683192ee2220 100644 --- a/game/modules/tome/data/damage_types.lua +++ b/game/modules/tome/data/damage_types.lua @@ -245,6 +245,12 @@ setDefaultProjector(function(src, x, y, type, dam, state) end end + -- Pheremones + if src.is_nature_summon and target:attr("inc_nature_summon") then + inc = inc + target:attr("inc_nature_summon") + print("[PROJECTOR] after nature summon increase dam", dam + (dam * inc) / 100) + end + -- Rigor mortis if src.necrotic_minion and target:attr("inc_necrotic_minions") then inc = inc + target:attr("inc_necrotic_minions") diff --git a/game/modules/tome/data/gfx/particles/block.lua b/game/modules/tome/data/gfx/particles/block.lua new file mode 100644 index 0000000000000000000000000000000000000000..f7aa3ef93bb9c99c002e3f7c3ad79f07e6619539 --- /dev/null +++ b/game/modules/tome/data/gfx/particles/block.lua @@ -0,0 +1,52 @@ +-- ToME - Tales of Maj'Eyal +-- Copyright (C) 2009 - 2019 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 + +base_size = 32 + +local ad = rng.range(0, 360) +local a = math.rad(ad) +local dir = math.rad(ad + 90) +local r = 18 +local speed = 100 +local dirv = math.pi * 2 / speed +local vel = math.pi * 2 * r / speed +local first = true + +return { generator = function() + local dr = rng.range(0, 2) + local da = math.rad(rng.range(0, 360)) + return { + life = core.particles.ETERNAL, + size = 14, sizev = 0, sizea = 0, + + x = r * math.cos(a) + dr * math.cos(da), xv = 0, xa = 0, + y = r * math.sin(a) + dr * math.cos(da), yv = 0, ya = 0, + dir = dir, dirv = dirv, dira = 0, + vel = vel, velv = 0, vela = 0, + + r = 127/255, rv = 0, ra = 0, + g = 127/255, gv = 0, ga = 0, + b = 127/255, gv = 0, ga = 0, + a = 225/255, av = 0, aa = 0, + } +end, }, +function(self) + if first then self.ps:emit(1) first = false end +end, +1, "particles_images/shieldimg" \ No newline at end of file diff --git a/game/modules/tome/data/gfx/particles/stormshield.lua b/game/modules/tome/data/gfx/particles/stormshield.lua new file mode 100644 index 0000000000000000000000000000000000000000..4b56cdb61181b575ee20901ef0577e0603b19973 --- /dev/null +++ b/game/modules/tome/data/gfx/particles/stormshield.lua @@ -0,0 +1,46 @@ +-- ToME - Tales of Maj'Eyal +-- Copyright (C) 2009 - 2019 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 + +can_shift = true +base_size = 32 + +return { generator = function() + local a = rng.float(0,math.pi*2) + local dir = a+math.pi*0.5 + local r = (engine.Map.tile_w + engine.Map.tile_h) / 5 + + return { + life = 14, + size = rng.range(1,2), sizev = 0.3, sizea = -0.01, + + x = r * math.cos(a), xv = 0.01*math.cos(a), xa = -0.25*math.cos(a), + y = r * math.sin(a), yv = 0.167*math.cos(a), ya = 0, + dir = dir, dirv = 0, dira = 0, + vel = 0, velv = 0, vela = 0, + + r = rng.range(160, 180)/255, rv = 0, ra = 0, + g = rng.range(180, 200)/255, gv = 0, ga = 0, + b = rng.range(195, 225)/255, gv = 0, ga = 0, + a = 1, av = 0, aa = 0, + } +end, }, +function(self) + self.ps:emit(4) +end, +240 \ No newline at end of file diff --git a/game/modules/tome/data/talents/corruptions/blood.lua b/game/modules/tome/data/talents/corruptions/blood.lua index 0e7e07a00ae873c270dcaa7e763cda74516fe720..5fdf1207e5e969b7a9364dcc0fdf768ef223bfef 100644 --- a/game/modules/tome/data/talents/corruptions/blood.lua +++ b/game/modules/tome/data/talents/corruptions/blood.lua @@ -17,6 +17,27 @@ -- Nicolas Casalini "DarkGod" -- darkgod@te4.org +--- get a list of diseases on a target +local getTargetDiseases = function(self, target) + if not target then return end + local diseases = self.turn_procs.target_diseases and self.turn_procs.target_diseases[target.uid] + if diseases then return diseases end + + local num, dur = 0, 0 + diseases = {} + for eff_id, p in pairs(target.tmp) do + local e = target.tempeffect_def[eff_id] + if e.subtype.disease then + num, dur = num + 1, dur + p.dur + diseases[#diseases+1] = {id=eff_id, params=p} + end + end + diseases.num, diseases.dur = num, dur + self.turn_procs.target_diseases = self.turn_procs.target_diseases or {} + self.turn_procs.target_diseases[target.uid] = diseases + return diseases +end + newTalent{ name = "Blood Spray", type = {"corruption/blood", 1}, @@ -94,7 +115,15 @@ newTalent{ points = 5, cooldown = 8, vim = 30, - tactical = { ATTACKAREA = {BLIGHT = 0.5} }, -- Needs a better tactical table, setting to low priority for now so it gets used later in the rotation when diseases are up + getTargetDiseases = getTargetDiseases, + tactical = { DISABLE = function(self, t, target) + local diseases = t.getTargetDiseases(self, target) + if diseases and diseases.num > 0 then return 3 end + end, + ATTACKAREA = function(self, t, target) + local diseases = t.getTargetDiseases(self, target) + if diseases and diseases.num > 0 then return {BLIGHT=1} end + end,}, range = 0, radius = function(self, t) return 10 end, getDamage = function(self, t) return self:combatTalentSpellDamage(t, 10, 250) end, diff --git a/game/modules/tome/data/talents/corruptions/scourge.lua b/game/modules/tome/data/talents/corruptions/scourge.lua index ceefc380669fe82a4917ff0611a6a971becb1ee9..4a6945a139023391bbce2c6ea4e493097d6bd86c 100644 --- a/game/modules/tome/data/talents/corruptions/scourge.lua +++ b/game/modules/tome/data/talents/corruptions/scourge.lua @@ -154,6 +154,8 @@ newTalent{ tg.x = target.x tg.y = target.y self:project(tg, target.x, target.y, DamageType.ACID, self:spellCrit(t.getSplash(self, t))) + game.level.map:particleEmitter(target.x, target.y, tg.radius, "ball_acid", {radius=tg.radius}) + game:playSoundNear(self, "talents/slime") end return true diff --git a/game/modules/tome/data/talents/cunning/ambush.lua b/game/modules/tome/data/talents/cunning/ambush.lua index b8e05114ec7dd7169df460f8250b64861d5a83d4..51ea1f99e654a4fc29165b587b4b2c96bde4cf0f 100644 --- a/game/modules/tome/data/talents/cunning/ambush.lua +++ b/game/modules/tome/data/talents/cunning/ambush.lua @@ -95,6 +95,7 @@ newTalent{ game.logSeen(target, "%s resists the disarm!", target.name:capitalize()) end + game:playSoundNear(self, "talents/arcane") return true end, info = function(self, t) diff --git a/game/modules/tome/data/talents/cunning/shadow-magic.lua b/game/modules/tome/data/talents/cunning/shadow-magic.lua index d7d66867a6d9948d08f4e3320c891038bd55f914..c0ff1a9f4379bb6bc202f68fc3cafbe8408d2021 100644 --- a/game/modules/tome/data/talents/cunning/shadow-magic.lua +++ b/game/modules/tome/data/talents/cunning/shadow-magic.lua @@ -32,7 +32,7 @@ newTalent{ if not p then return "" end return tostring(math.floor(damDesc(self, DamageType.DARKNESS, t.getDamage(self, t)))), "buff_font_smaller" end, - getDamage = function(self, t) return self:combatTalentSpellDamage(t, 1, 90) end, -- This doesn't crit or generally scale easily so its safe to be aggressive + getDamage = function(self, t) return self:combatTalentSpellDamage(t, 1, 70) end, -- This doesn't crit or generally scale easily so its safe to be aggressive getManaCost = function(self, t) return 0 end, activate = function(self, t) local ret = {} @@ -41,6 +41,7 @@ newTalent{ local h1x, h1y = self:attachementSpot("hand1", true) if h1x then self:talentParticles(ret, {type="shader_shield", args={img="shadowhands_01", dir=180, a=0.7, size_factor=0.4, x=h1x, y=h1y-0.1}, shader={type="flamehands", time_factor=slow and 700 or 1000}}) end local h2x, h2y = self:attachementSpot("hand2", true) if h2x then self:talentParticles(ret, {type="shader_shield", args={img="shadowhands_01", dir=180, a=0.7, size_factor=0.4, x=h2x, y=h2y-0.1}, shader={type="flamehands", time_factor=not slow and 700 or 1000}}) end end + game:playSoundNear(self, "talents/arcane") return ret end, deactivate = function(self, t, p) @@ -84,6 +85,7 @@ newTalent{ getAtkSpeed = function(self, t) return self:combatTalentScale(t, 2.2, 11, 0.75) end, activate = function(self, t) local speed = t.getAtkSpeed(self, t)/100 + game:playSoundNear(self, "talents/arcane") return { regen = self:addTemporaryValue("mana_regen", t.getManaRegen(self, t)), ps = self:addTemporaryValue("combat_physspeed", speed), @@ -144,6 +146,8 @@ newTalent{ game.logSeen(target, "%s is not dazed!", target.name:capitalize()) end end + + game:playSoundNear(self, "talents/arcane") return true end, info = function(self, t) diff --git a/game/modules/tome/data/talents/gifts/summon-utility.lua b/game/modules/tome/data/talents/gifts/summon-utility.lua index e15dc2bdf0485bddd044a98cc3805c6db9b09c0f..3b7d32b44f3dfc9dc06a22e1137c35935e465e8b 100644 --- a/game/modules/tome/data/talents/gifts/summon-utility.lua +++ b/game/modules/tome/data/talents/gifts/summon-utility.lua @@ -363,13 +363,14 @@ newTalent{ points = 5, equilibrium = 7, cooldown = 10, - no_npc_use = true, + tactical = { DISABLE = 2 }, no_energy = true, requires_target = true, range = 10, direct_hit = true, getRad = function(self, t) return self:combatTalentScale(t, 3, 7) end, - getDur = function(self, t) return self:combatTalentScale(t, 3, 8) end, + getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 5.5)) end + getDamage = function(self, t) return 10 + self:combatTalentMindDamage(t, 10, 25) end, target = function(self, t) return {type="hit", range=self:getTalentRange(t)} end, action = function(self, t) local tg = self:getTalentTarget(t) @@ -377,13 +378,14 @@ newTalent{ if not target or target == self then return nil else - target:setEffect(target.EFF_SUMMON_CONTROL, t.getDur(self, t), {range=t.getRad(self,t), src=self}) + target:setEffect(target.EFF_SUMMON_CONTROL, t.getDur(self, t), {range=t.getRad(self,t), src=self, power=t.getDamage(self,t)}) end game:playSoundNear(self, "talents/spell_generic") return true end, info = function(self, t) - return ([[Mark a creature with pheromones, signalling to all of your summons within %d tiles of it to shift aggression towards the marked creature for %d turns.]]):format(t.getRad(self,t), t.getDur(self,t)) + return ([[Mark a creature with pheromones, signalling to all of your summons within %d tiles to shift aggression towards the marked creature for %d turns. Marked targets will receive %d%% increased damage from your summons and your summons will change target to it. + The increased damage from your summons will increase with your Mindpower]]):format(t.getRad(self,t), t.getDur(self,t), t.getDamage(self,t)) end, } diff --git a/game/modules/tome/data/talents/spells/enhancement.lua b/game/modules/tome/data/talents/spells/enhancement.lua index 08118f8507eeb1757f3b59a0957e08b7e9354eee..a1acbdd1c34c2a787ee17c0a71ba0f48f07e02f4 100644 --- a/game/modules/tome/data/talents/spells/enhancement.lua +++ b/game/modules/tome/data/talents/spells/enhancement.lua @@ -46,7 +46,14 @@ newTalent{ if self:attackTargetWith(target, weapon, DamageType.ARCANE, t.getDamage(self, t)) or hit then hit = true end end - if hit then self:incMana(t.getMana(self, t)) end + if hit then + self:incMana(t.getMana(self, t)) + if core.shader.active(4) then + self:addParticles(Particles.new("shader_shield_temp", 1, {toback=true, size_factor=1.5, y=-0.3, img="healarcane", life=25}, {type="healing", time_factor=2000, beamsCount=20, noup=2.0, beamColor1={0x8e/255, 0x2f/255, 0xbb/255, 1}, beamColor2={0xe7/255, 0x39/255, 0xde/255, 1}, circleDescendSpeed=4})) + self:addParticles(Particles.new("shader_shield_temp", 1, {toback=false, size_factor=1.5, y=-0.3, img="healarcane", life=25}, {type="healing", time_factor=2000, beamsCount=20, noup=1.0, beamColor1={0x8e/255, 0x2f/255, 0xbb/255, 1}, beamColor2={0xe7/255, 0x39/255, 0xde/255, 1}, circleDescendSpeed=4})) + end + game:playSoundNear(self, "talents/arcane") + end return true end, diff --git a/game/modules/tome/data/talents/techniques/combat-techniques.lua b/game/modules/tome/data/talents/techniques/combat-techniques.lua index ebcc33a184b650268aab4460f8e44150aac54861..64730446447d92b606d80dd53aaf346c3506eea3 100644 --- a/game/modules/tome/data/talents/techniques/combat-techniques.lua +++ b/game/modules/tome/data/talents/techniques/combat-techniques.lua @@ -207,11 +207,12 @@ newTalent{ require = techs_strdex_req3, mode = "passive", points = 5, + getSaves = function(self, t) return math.floor(self:combatTalentScale(t, 12, 48)) end, passives = function(self, t, p) - self:talentTemporaryValue(p, "combat_spellresist", self:getTalentLevel(t) * 9) + self:talentTemporaryValue(p, "combat_spellresist", t.getSaves(self,t)) end, info = function(self, t) - return ([[Rigorous training allows you to be more resistant to some spell effects (+%d spell save).]]):format(self:getTalentLevel(t) * 9) + return ([[Rigorous training allows you to be more resistant to some spell effects (+%d spell save).]]):format(t.getSaves(self,t)) end, } diff --git a/game/modules/tome/data/talents/techniques/combat-training.lua b/game/modules/tome/data/talents/techniques/combat-training.lua index 9c8133f1b4f50a2717549a55e84f4582b097f805..b6e8e2ea62bdadaedb3a4380ccaf82451fd90921 100644 --- a/game/modules/tome/data/talents/techniques/combat-training.lua +++ b/game/modules/tome/data/talents/techniques/combat-training.lua @@ -165,7 +165,7 @@ newTalent{ points = 5, require = { level=function(level) return (level - 1) * 4 end }, mode = "passive", - getAttack = function(self, t) return self:combatTalentScale(t, 10, 50, 0.75) end, -- match values at 1 and 5 for old formula + getAttack = function(self, t) return math.floor(self:combatTalentScale(t, 14, 55)) end, info = function(self, t) local attack = t.getAttack(self, t) return ([[Increases the accuracy of unarmed, melee and ranged weapons by %d.]]): diff --git a/game/modules/tome/data/timed_effects/magical.lua b/game/modules/tome/data/timed_effects/magical.lua index 3107fd2a1e9e438ab65375f41ed32f58d6b4d55b..310112adeb37471ba57ea9933e485443219d4acd 100644 --- a/game/modules/tome/data/timed_effects/magical.lua +++ b/game/modules/tome/data/timed_effects/magical.lua @@ -227,9 +227,11 @@ newEffect{ on_lose = function(self, err) return "#Target#'s skin returns to normal.", "-Reflective Skin" end, activate = function(self, eff) eff.tmpid = self:addTemporaryValue("reflect_damage", eff.power) + self:addShaderAura("reflective_skin", "awesomeaura", {time_factor=5500, alpha=0.6, flame_scale=0.6}, "particles_images/arcaneshockwave.png") end, deactivate = function(self, eff) self:removeTemporaryValue("reflect_damage", eff.tmpid) + self:removeShaderAura("reflective_skin") end, } @@ -342,6 +344,11 @@ newEffect{ on_gain = function(self, err) return "#Target# summons a storm to protect them!", "+Stormshield" end, on_lose = function(self, err) return "#Target#'s storm dissipates.", "-Stormshield" end, activate = function(self, eff) + if core.shader.active(4) then + self:effectParticles(eff, {type="shader_ring_rotating", args={rotation=0, radius=1.0, img="lightningshield"}, shader={type="lightningshield"}}) + else + self:effectParticles(eff, {type = "stormshield"}) + end end, deactivate = function(self, eff) end, diff --git a/game/modules/tome/data/timed_effects/mental.lua b/game/modules/tome/data/timed_effects/mental.lua index 569f3ce0bfe35d5a93260635fb2248010316a9b8..a597b0a691142a7adccea25224ce872ac066fe72 100644 --- a/game/modules/tome/data/timed_effects/mental.lua +++ b/game/modules/tome/data/timed_effects/mental.lua @@ -91,33 +91,32 @@ newEffect{ newEffect{ name = "SUMMON_CONTROL", image = "talents/summon_control.png", --Backwards compatibility desc = "Pheromones", - long_desc = function(self, eff) return ("The target has been marked as the focus for all summons within %d radius."):format(eff.range) end, + long_desc = function(self, eff) return ("The target has been marked as the focus for all nature summons within %d radius, receiving %d%% increased damage from nature summons."):format(eff.range, eff.power) end, type = "mental", subtype = { focus=true }, status = "detrimental", - parameters = { }, + parameters = { power = 10 }, on_gain = function(self, err) return "Summons flock towards #Target#.", true end, on_lose = function(self, err) return "#Target# is no longer being targeted by summons.", true end, on_timeout = function(self, eff) - - self:project({type="ball", range=0, friendlyfire=false, radius=eff.range}, self.x, self.y, function(px, py) - local target = game.level.map(px, py, Map.ACTOR) - if not target then return end - if target.summoner == eff.src then - target:setTarget(self) - end - end) - + self:project({type="ball", range=0, friendlyfire=false, radius=eff.range}, self.x, self.y, function(px, py) + local target = game.level.map(px, py, Map.ACTOR) + if not target then return end + if target.summoner == eff.src then + target:setTarget(self) + end + end) end, activate = function(self, eff) - self:project({type="ball", range=0, friendlyfire=false, radius=eff.range}, self.x, self.y, function(px, py) - local target = game.level.map(px, py, Map.ACTOR) - if not target then return end - if target.summoner == eff.src then - target:setTarget(self) - end - end) + self:effectTemporaryValue(eff, "inc_nature_summon", eff.power) + self:project({type="ball", range=0, friendlyfire=false, radius=eff.range}, self.x, self.y, function(px, py) + local target = game.level.map(px, py, Map.ACTOR) + if not target then return end + if target.summoner == eff.src then + target:setTarget(self) + end + end) end, deactivate = function(self, eff) end, diff --git a/game/modules/tome/data/timed_effects/physical.lua b/game/modules/tome/data/timed_effects/physical.lua index 8b82b61d2c45fc10438fe1557cb5d9cc1e1e5b37..fe826510a9960000c22c144616061c4fb073e880 100644 --- a/game/modules/tome/data/timed_effects/physical.lua +++ b/game/modules/tome/data/timed_effects/physical.lua @@ -2379,6 +2379,7 @@ newEffect{ activate = function(self, eff) eff.tmpid = self:addTemporaryValue("block", eff.power) if eff.properties.sp then eff.spell = self:addTemporaryValue("combat_spellresist", eff.power) end + self:effectParticles(eff, {type="block"}) end, deactivate = function(self, eff) self:removeTemporaryValue("block", eff.tmpid) diff --git a/game/modules/tome/data/zones/gorbat-pride/mapscripts/main.lua b/game/modules/tome/data/zones/gorbat-pride/mapscripts/main.lua index 3677a23d596fc110c05cfa7b49b7c2c694ab1f5e..1df0a80bbc05d6038406a83858452c75a02fdc76 100644 --- a/game/modules/tome/data/zones/gorbat-pride/mapscripts/main.lua +++ b/game/modules/tome/data/zones/gorbat-pride/mapscripts/main.lua @@ -27,7 +27,8 @@ if level.level == zone.max_level then self:defineTile(">", 'FLOOR') else self:de self:defineTile("O", "FLOOR", nil, {random_filter={type='humanoid', subtype='orc', special=function(e) return e.pride == mapdata.pride end, random_boss={nb_classes=1, loot_quality="store", loot_quantity=3, ai_move="move_complex", rank=4,}}}) self:defineTile("X", "FLOOR", nil, {entity_mod=function(e) e.make_escort = nil return e end, random_filter={type='humanoid', subtype='orc', special=function(e) return e.pride == mapdata.pride end, random_boss={nb_classes=1, loot_quality="store", loot_quantity=1, no_loot_randart=true, ai_move="move_complex", rank=3}}}, nil, {no_teleport=true}) self:defineTile("d", "FENCE_FLOOR", nil, {random_filter={special_rarity="drake_rarity"}}, nil, {special="roost"}) -self:defineTile("D", "FENCE_FLOOR", nil, {entity_mod=function(e) e.make_escort = nil return e end, random_filter={special_rarity="drake_rarity", special=function(e) return e.rank == 3 end, random_boss={nb_classes=1, loot_quality="store", loot_quantity=1, no_loot_randart=true, ai_move="move_complex", rank=3.5}}}, nil, {special="roost"}) +self:defineTile("D", "FENCE_FLOOR", nil, {entity_mod=function(e) e.make_escort = nil return e end, random_filter={special_rarity="drake_rarity", special=function(e) return e.rank == 3 end, random_boss={nb_classes=1, loot_quality="store", loot_quantity=1, loot_unique=true, no_loot_randart=true, ai_move="move_complex", rank=3.5}}}, nil, {special="roost"}) --drops a fixedart +self:defineTile("R", "FENCE_FLOOR", nil, {entity_mod=function(e) e.make_escort = nil return e end, random_filter={special_rarity="drake_rarity", special=function(e) return e.rank == 3 end, random_boss={nb_classes=1, loot_quality="store", loot_quantity=1, ai_move="move_complex", rank=3.5}}}, nil, {special="roost"}) --drops a randart self:defineTile('&', "GENERIC_LEVER_SAND", nil, nil, nil, {special="roost", lever=1, lever_kind="pride-doors", lever_spot={type="lever", subtype="door", check_connectivity="entrance"}}, {type="lever", subtype="lever", check_connectivity="entrance"}) self:defineTile('*', "ROCK_LEVER_DOOR", nil, nil, nil, {lever_action=2, lever_action_value=0, lever_action_kind="pride-doors"}, {type="lever", subtype="door", check_connectivity="entrance"}) @@ -63,7 +64,7 @@ tm:applyOnGroups(rooms, function(room, idx) local drake = room:pickSpot("any") if drake then local boss = rng.percent(25) and not had_boss - tm:put(drake, boss and 'D' or 'd') + tm:put(drake, boss and (rng.percent(65) and 'D' or 'R') or 'd') room:remove(drake) if boss then had_boss = true end else diff --git a/game/modules/tome/data/zones/grushnak-pride/mapscripts/last.lua b/game/modules/tome/data/zones/grushnak-pride/mapscripts/last.lua index 343f661695e0fea481b6adb85798481cd6c29e0d..933c9a33eac9585ea07f61e2c0021f5591b8c287 100644 --- a/game/modules/tome/data/zones/grushnak-pride/mapscripts/last.lua +++ b/game/modules/tome/data/zones/grushnak-pride/mapscripts/last.lua @@ -28,8 +28,8 @@ self:defineTile('"', "HARDWALL") self:defineTile('t', "TRAINING_DUMMY") self:defineTile('b', "FLOOR", nil, "ORC_ELITE_BERSERKER") self:defineTile('f', "FLOOR", nil, "ORC_ELITE_FIGHTER") -self:defineTile("B", "FLOOR", nil, {random_filter={define_as="ORC_ELITE_BERSERKER", random_boss={name_scheme="Combat Trainer #rng#", force_classes={Berserker=true}, nb_classes=1, loot_quality="store", loot_quantity=1, no_loot_randart=true, rank=3.5}}}) -self:defineTile("F", "FLOOR", nil, {random_filter={define_as="ORC_ELITE_FIGHTER", random_boss={name_scheme="Combat Trainer #rng#", force_classes={Bulwark=true}, nb_classes=1, loot_quality="store", loot_quantity=1, no_loot_randart=true, rank=3.5}}}) +self:defineTile("B", "FLOOR", nil, {random_filter={define_as="ORC_ELITE_BERSERKER", random_boss={name_scheme="Combat Trainer #rng#", force_classes={Berserker=true}, nb_classes=1, loot_quality="store", loot_quantity=1, rank=3.5}}}) +self:defineTile("F", "FLOOR", nil, {random_filter={define_as="ORC_ELITE_FIGHTER", random_boss={name_scheme="Combat Trainer #rng#", force_classes={Bulwark=true}, nb_classes=1, loot_quality="store", loot_quantity=1, loot_unique=true, no_loot_randart=true, rank=3.5}}}) self:defineTile('>', "SLIME_TUNNELS", nil, nil, nil, {special="slimepit"}) self:defineTile(";", "UNDERGROUND_CREEP", nil, nil, nil, {special="slimepit"}) self:defineTile("s", "UNDERGROUND_CREEP", nil, {random_filter={special_rarity="slime_rarity"}}, nil, {special="slimepit"}) diff --git a/game/modules/tome/data/zones/grushnak-pride/mapscripts/main.lua b/game/modules/tome/data/zones/grushnak-pride/mapscripts/main.lua index f76c993cf0bdbc89d5e4dd8fb27d7276840b7524..0880faa85c1aa550efffdcd0f7adc59e11096851 100644 --- a/game/modules/tome/data/zones/grushnak-pride/mapscripts/main.lua +++ b/game/modules/tome/data/zones/grushnak-pride/mapscripts/main.lua @@ -23,8 +23,8 @@ self:defineTile('"', "HARDWALL") self:defineTile('t', "TRAINING_DUMMY") self:defineTile('b', "FLOOR", nil, "ORC_ELITE_BERSERKER") self:defineTile('f', "FLOOR", nil, "ORC_ELITE_FIGHTER") -self:defineTile("B", "FLOOR", nil, {random_filter={define_as="ORC_ELITE_BERSERKER", random_boss={name_scheme="Combat Trainer #rng#", force_classes={Berserker=true}, nb_classes=1, loot_quality="store", loot_quantity=1, no_loot_randart=true, rank=3.5}}}) -self:defineTile("F", "FLOOR", nil, {random_filter={define_as="ORC_ELITE_FIGHTER", random_boss={name_scheme="Combat Trainer #rng#", force_classes={Bulwark=true}, nb_classes=1, loot_quality="store", loot_quantity=1, no_loot_randart=true, rank=3.5}}}) +self:defineTile("B", "FLOOR", nil, {random_filter={define_as="ORC_ELITE_BERSERKER", random_boss={name_scheme="Combat Trainer #rng#", force_classes={Berserker=true}, nb_classes=1, loot_quality="store", loot_quantity=1, rank=3.5}}}) +self:defineTile("F", "FLOOR", nil, {random_filter={define_as="ORC_ELITE_FIGHTER", random_boss={name_scheme="Combat Trainer #rng#", force_classes={Bulwark=true}, nb_classes=1, loot_quality="store", loot_quantity=1, loot_unique=true, no_loot_randart=true, rank=3.5}}}) -- Make the barracks local bsp = BSP.new(5, 5, 6):make(30, 30, '.', '#') diff --git a/game/modules/tome/data/zones/tempest-peak/zone.lua b/game/modules/tome/data/zones/tempest-peak/zone.lua index 4157a2b59008dc30e9f4bbfbe41025423a78fa69..427da8c20e06e9fafa3de8a794e29daba3c20020 100644 --- a/game/modules/tome/data/zones/tempest-peak/zone.lua +++ b/game/modules/tome/data/zones/tempest-peak/zone.lua @@ -36,7 +36,7 @@ return { class = "engine.generator.map.Roomer", nb_rooms = 14, rooms = {"random_room", {"money_vault",5}, {"lesser_vault",8}}, - lesser_vaults_list = {"circle"}, + lesser_vaults_list = {"circle", "perilous-cliffs"}, lite_room_chance = 100, ['.'] = "ROCKY_GROUND", ['#'] = "MOUNTAIN_WALL", diff --git a/game/modules/tome/data/zones/vor-pride/mapscripts/main.lua b/game/modules/tome/data/zones/vor-pride/mapscripts/main.lua index d8496273237970c3e9f373ffc9e6f60dfb1a74e5..41d2312b16bff2bb637abcda29cb475286918710 100644 --- a/game/modules/tome/data/zones/vor-pride/mapscripts/main.lua +++ b/game/modules/tome/data/zones/vor-pride/mapscripts/main.lua @@ -26,7 +26,8 @@ self:defineTile('&', "GOTHIC_GENERIC_LEVER", nil, nil, nil, {lever=1, lever_kind self:defineTile('*', "GOTHIC_GENERIC_LEVER_DOOR", nil, nil, nil, {lever_action=2, lever_action_value=0, lever_action_kind="pride-doors"}, {type="lever", subtype="door", check_connectivity="entrance"}) self:defineTile('B', {"GENERIC_BOOK1","GENERIC_BOOK2","GENERIC_BOOK3"}) -self:defineTile('C', "CANDLE", nil, {random_filter={type='humanoid', subtype='orc', special=function(e) return e.pride == mapdata.pride end, random_boss={nb_classes=0, loot_quality="store", loot_quantity=1, no_loot_randart=true, ai_move="move_complex", rank=3.5, force_classes={Archmage=true}}}}) +self:defineTile('C', "CANDLE", nil, {random_filter={type='humanoid', subtype='orc', special=function(e) return e.pride == mapdata.pride end, random_boss={nb_classes=0, loot_quality="store", loot_quantity=1, ai_move="move_complex", rank=3.5, force_classes={Archmage=true}}}}) +self:defineTile('D', "CANDLE", nil, {random_filter={type='humanoid', subtype='orc', special=function(e) return e.pride == mapdata.pride end, random_boss={nb_classes=0, loot_quality="store", loot_quantity=1, loot_unique=true, no_loot_randart=true, ai_move="move_complex", rank=3.5, force_classes={Archmage=true}}}}) local wfc = WaveFunctionCollapse.new{ mode="overlapping", @@ -60,7 +61,7 @@ tm:applyOnGroups(rooms, function(room, idx) local cpos = room:randomNearPoint(bpos) if cpos then tm:put(bpos, 'B') - tm:put(cpos, 'C') + tm:put(cpos, (rng.percent(65) and 'C' or 'D')) end end end diff --git a/game/modules/tome/dialogs/LevelupDialog.lua b/game/modules/tome/dialogs/LevelupDialog.lua index c7d1a9986bb2e4dcf6b50ee90ea2b1994821b6a6..cb462b8d2e4a1726565d3e51f45e0656fe67e126 100644 --- a/game/modules/tome/dialogs/LevelupDialog.lua +++ b/game/modules/tome/dialogs/LevelupDialog.lua @@ -924,7 +924,7 @@ function _M:getTalentDesc(item) if item.type then text:add({"color",0x00,0xFF,0xFF}, "Talent Category", true) - text:add({"color",0x00,0xFF,0xFF}, "A talent category contains talents you may learn. You gain a talent category point at level 10, 20 and 36. You may also find trainers or artifacts that allow you to learn more.\nA talent category point can be used either to learn a new category or increase the mastery of a known one.", true, true, {"color", "WHITE"}) + text:add({"color",0x00,0xFF,0xFF}, "A talent category contains talents you may learn. You gain a talent category point at level 10, 20 and 34. You may also find trainers or artifacts that allow you to learn more.\nA talent category point can be used either to learn a new category or increase the mastery of a known one.", true, true, {"color", "WHITE"}) if self.actor.talents_types_def[item.type].generic then text:add({"color",0x00,0xFF,0xFF}, "Generic talent tree", true) @@ -947,7 +947,7 @@ function _M:getTalentDesc(item) text:add({"color","YELLOW"}, "This talent can alter the world in a permanent way; as such, you can never unlearn it once known.", {"color","LAST"}, true, true) elseif could_unlearn then local max = tostring(self.actor:lastLearntTalentsMax(t.generic and "generic" or "class")) - text:add({"color","LIGHT_BLUE"}, "This talent was recently learnt; you can still unlearn it if you are in a quiet area like a #{bold}#town#{normal}#.", true, "The last ", max, t.generic and " generic" or " class", " talents you learnt are always unlearnable.", {"color","LAST"}, true, true) + text:add({"color","LIGHT_BLUE"}, "This talent was recently learnt; you can still unlearn it if you are out of combat or in a quiet area like a #{bold}#town#{normal}#.", true, "The last ", max, t.generic and " generic" or " class", " talents you learnt are always unlearnable.", {"color","LAST"}, true, true) end local traw = self.actor:getTalentLevelRaw(t.id)