diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua index b44efea96690154374b634aefa96e65b15e12b78..3c5932f606b0c1dabc25a72b0256b6d0d7bbd330 100644 --- a/game/modules/tome/class/Actor.lua +++ b/game/modules/tome/class/Actor.lua @@ -132,6 +132,13 @@ function _M:init(t, no_default) t.positive = t.positive or 0 t.negative = t.negative or 0 + t.hate_rating = t.hate_rating or 0.2 + t.hate_regen = t.hate_regen or -0.035 + t.max_hate = t.max_hate or 10 + t.absolute_max_hate = t.absolute_max_hate or 15 + t.hate = t.hate or 10 + t.hate_per_kill = t.hate_per_kill or 0.8 + -- Equilibrium has a default very high max, as bad effects happen even before reaching it t.max_equilibrium = t.max_equilibrium or 100000 t.equilibrium = t.equilibrium or 0 @@ -177,6 +184,10 @@ function _M:act() self:cooldownTalents() -- Regen resources self:regenLife() + if self:knowTalent(self.T_UNNATURAL_BODY) then + local t = self:getTalentFromId(self.T_UNNATURAL_BODY) + t.do_regenLife(self, t) + end self:regenResources() -- Compute timed effects self:timedEffects() @@ -198,6 +209,11 @@ function _M:act() local t = self:getTalentFromId(self.T_BLOOD_FRENZY) t.do_turn(self, t) end + -- this handles cursed gloom turn based effects + if self:isTalentActive(self.T_GLOOM) then + local t = self:getTalentFromId(self.T_GLOOM) + t.do_gloom(self, t) + end if self:attr("stunned") then self.energy.value = 0 end if self:attr("stoned") then self.energy.value = 0 end @@ -500,6 +516,12 @@ function _M:onTakeHit(value, src) self:removeEffect(self.EFF_DAZED) end + -- remove stalking if there is an interaction + if self.stalker and src and self.stalker == src then + self.stalker:removeEffect(self.EFF_STALKER) + self:removeEffect(self.EFF_STALKED) + end + if self:attr("invulnerable") then return 0 end @@ -591,6 +613,11 @@ function _M:onTakeHit(value, src) end end + -- Adds hate + if src and src.max_hate and src.max_hate > 0 then + src.hate = math.min(src.max_hate, src.hate + src.hate_per_kill) + end + -- Achievements if src and src.resolveSource and src:resolveSource().player and value >= 600 then world:gainAchievement("SIZE_MATTERS", src:resolveSource()) @@ -775,6 +802,10 @@ function _M:levelup() self:incMaxStamina(self.stamina_rating) self:incMaxPositive(self.positive_negative_rating) self:incMaxNegative(self.positive_negative_rating) + if self.max_hate < self.absolute_max_hate then + local amount = math.min(self.hate_rating, self.absolute_max_hate - self.max_hate) + self:incMaxHate(amount) + end -- Heal up on new level self:resetToFull() @@ -908,6 +939,7 @@ function _M:learnTalent(t_id, force, nb) if t.type[1]:find("^corruption/") and not self:knowTalent(self.T_VIM_POOL) and t.vim or t.sustain_vim then self:learnTalent(self.T_VIM_POOL, true) end if t.type[1]:find("^divine/") and (t.positive or t.sustain_positive) and not self:knowTalent(self.T_POSITIVE_POOL) then self:learnTalent(self.T_POSITIVE_POOL, true) end if t.type[1]:find("^divine/") and (t.negative or t.sustain_negative) and not self:knowTalent(self.T_NEGATIVE_POOL) then self:learnTalent(self.T_NEGATIVE_POOL, true) end + if t.type[1]:find("^cursed/") and not self:knowTalent(self.T_HATE_POOL) then self:learnTalent(self.T_HATE_POOL, true) end -- If we learn an archery talent, also learn to shoot if t.type[1]:find("^technique/archery") and not self:knowTalent(self.T_SHOOT) then self:learnTalent(self.T_SHOOT, true) end @@ -960,6 +992,10 @@ function _M:preUseTalent(ab, silent, fake) game.logPlayer(self, "You do not have enough negative energy to activate %s.", ab.name) return false end + if ab.sustain_hate and self.max_hate < ab.sustain_hate and not self:isTalentActive(ab.id) then + game.logPlayer(self, "You do not have enough hate to activate %s.", ab.name) + return false + end else if ab.mana and self:getMana() < ab.mana * (100 + self.fatigue) / 100 then game.logPlayer(self, "You do not have enough mana to cast %s.", ab.name) @@ -981,6 +1017,10 @@ function _M:preUseTalent(ab, silent, fake) game.logPlayer(self, "You do not have enough negative energy to use %s.", ab.name) return false end + if ab.hate and self:getHate() < ab.hate * (100 + self.fatigue) / 100 then + game.logPlayer(self, "You do not have enough hate to use %s.", ab.name) + return false + end end -- Equilibrium is special, it has no max, but the higher it is the higher the chance of failure (and loss of the turn) @@ -1065,6 +1105,9 @@ function _M:postUseTalent(ab, ret) if ab.sustain_negative then trigger = true; self.max_negative = self.max_negative - ab.sustain_negative end + if ab.sustain_hate then + trigger = true; self.max_hate = self.max_hate - ab.sustain_hate + end else if ab.sustain_mana then trigger = true; self.max_mana = self.max_mana + ab.sustain_mana @@ -1084,6 +1127,9 @@ function _M:postUseTalent(ab, ret) if ab.sustain_negative then trigger = true; self.max_negative = self.max_negative + ab.sustain_negative end + if ab.sustain_hate then + trigger = true; self.max_hate = self.max_hate + ab.sustain_hate + end end else if ab.mana then @@ -1102,6 +1148,9 @@ function _M:postUseTalent(ab, ret) if ab.negative then trigger = true; self:incNegative(-ab.negative * (100 + self.fatigue) / 100) end + if ab.hate then + trigger = true; self:incHate(-ab.hate * (100 + self.fatigue) / 100) + end -- Equilibrium is not affected by fatigue if ab.equilibrium then self:incEquilibrium(ab.equilibrium) @@ -1167,6 +1216,7 @@ function _M:getTalentFullDescription(t, addlevel) if t.vim or t.sustain_vim then d[#d+1] = "#6fff83#Vim cost: #888888#"..(t.sustain_vim or t.vim) end if t.positive or t.sustain_positive then d[#d+1] = "#6fff83#Positive energy cost: #GOLD#"..(t.sustain_positive or t.positive * (100 + self.fatigue) / 100) end if t.negative or t.sustain_negative then d[#d+1] = "#6fff83#Negative energy cost: #GREY#"..(t.sustain_negative or t.negative * (100 + self.fatigue) / 100) end + if t.hate or t.sustain_hate then d[#d+1] = "#6fff83#Hate cost: #GREY#"..(t.hate or t.sustain_hate) end if self:getTalentRange(t) > 1 then d[#d+1] = "#6fff83#Range: #FFFFFF#"..self:getTalentRange(t) else d[#d+1] = "#6fff83#Range: #FFFFFF#melee/personal" end @@ -1267,6 +1317,13 @@ function _M:canSeeNoCache(actor, def, def_pct) end end + -- check if the actor is stalking you + if self.stalker then + if self.stalker == actor then + return false, 0 + end + end + -- Check for invisibility. This is a "simple" checkHit between invisible and see_invisible attrs if actor:attr("invisible") then -- Special case, 0 see invisible, can NEVER see invisible things diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua index f42577a75be63616a88738c1412cdfd7572577ab..85c076b727c05cde6e4955e45f6948ae2bc5972b 100644 --- a/game/modules/tome/class/Player.lua +++ b/game/modules/tome/class/Player.lua @@ -236,6 +236,18 @@ function _M:playerFOV() end end, true, true, true) end + + + -- Handle Preternatural Senses talent, a simple FOV, using cache. + if self:knowTalent(self.T_PRETERNATURAL_SENSES) then + local t = self:getTalentFromId(self.T_PRETERNATURAL_SENSES) + local range = self:getTalentRange(t) + self:computeFOV(range, "block_sense", function(x, y) + if game.level.map(x, y, game.level.map.ACTOR) then + game.level.map.seens(x, y, 1) + end + end, true, true, true) + end end --- Called before taking a hit, overload mod.class.Actor:onTakeHit() to stop resting and running diff --git a/game/modules/tome/class/PlayerDisplay.lua b/game/modules/tome/class/PlayerDisplay.lua index f8e13bfbc1cd6a503217c808e16e52c72a704acd..f175734f901226810149ddd26ccb5aaff1e0c41e 100644 --- a/game/modules/tome/class/PlayerDisplay.lua +++ b/game/modules/tome/class/PlayerDisplay.lua @@ -108,6 +108,11 @@ function _M:display() self.surface:erase(0x90 / 3, 0x40 / 3, 0x10 / 3, 255, self.bars_x, h, self.bars_w * player:getVim() / player.max_vim, self.font_h) self.surface:drawColorStringBlended(self.font, ("#904010#Vim: #ffffff#%d/%d"):format(player:getVim(), player.max_vim), 0, h, 255, 255, 255) h = h + self.font_h end + if player:knowTalent(player.T_HATE_POOL) then + self.surface:erase(colors.GREY.r / 5, colors.GREY.g / 5, colors.GREY.b / 5, 255, self.bars_x, h, self.bars_w, self.font_h) + self.surface:erase(colors.GREY.r / 2, colors.GREY.g / 2, colors.GREY.b / 2, 255, self.bars_x, h, self.bars_w * player:getHate() / 10, self.font_h) + self.surface:drawColorStringBlended(self.font, ("#F53CBE#Hate: #ffffff#%.1f/%d"):format(player:getHate(), 10), 0, h, 255, 255, 255) h = h + self.font_h + end if savefile_pipe.saving then h = h + self.font_h self.surface:drawColorStringBlended(self.font, "#YELLOW#Saving...", 0, h, 255, 255, 255) h = h + self.font_h end diff --git a/game/modules/tome/class/interface/Combat.lua b/game/modules/tome/class/interface/Combat.lua index f63619b49391049441ec3c765bde7910e7227d23..f652396eb50b0c3086847811a492390262194301 100644 --- a/game/modules/tome/class/interface/Combat.lua +++ b/game/modules/tome/class/interface/Combat.lua @@ -83,6 +83,11 @@ function _M:attackTarget(target, damtype, mult, noenergy) game.logPlayer(self, "%s notices you at the last moment!", target.name:capitalize()) end + if target and target:hasEffect(self.EFF_DOMINATED) and target.dominatedSource and target.dominatedSource == self then + -- target is being dominated by self + mult = (mult or 1) * (target.dominatedDamMult or 1) + end + if not self:attr("disarmed") then -- All weapons in main hands if self:getInven(self.INVEN_MAINHAND) then @@ -144,6 +149,12 @@ function _M:attackTarget(target, damtype, mult, noenergy) if sound then game:playSoundNear(self, sound) elseif sound_miss then game:playSoundNear(self, sound_miss) end + -- cleave second attack + if self:knowTalent(self.T_CLEAVE) then + local t = self:getTalentFromId(self.T_CLEAVE) + t.on_attackTarget(self, t, target, multiplier) + end + -- Cancel stealth! self:breakStealth() return hit @@ -512,6 +523,13 @@ function _M:physicalCrit(dam, weapon, target) return dam * (1.5 + self:getTalentLevel(self.T_SHADOWSTRIKE) / 7), true end + if target.stalker and target.stalker == self and self:knowTalent(self.T_STALK) then + local t = self:getTalentFromId(self.T_STALK) + if rng.percent(math.min(100, 40 + self:getTalentLevel(t) * 12)) then + return dam * 1.5, true + end + end + local chance = self:combatCrit(weapon) local crit = false if self:knowTalent(self.T_BACKSTAB) and target:attr("stunned") then chance = chance + self:getTalentLevel(self.T_BACKSTAB) * 10 end @@ -610,6 +628,20 @@ function _M:hasStaffWeapon() return weapon end +--- Check if the actor has an axe weapon +function _M:hasAxeWeapon() + if self:attr("disarmed") then + return nil, "disarmed" + end + + if not self:getInven("MAINHAND") then return end + local weapon = self:getInven("MAINHAND")[1] + if not weapon or (weapon.subtype ~= "battleaxe" and weapon.subtype ~= "waraxe") then + return nil + end + return weapon +end + --- Check if the actor has a two handed weapon function _M:hasTwoHandedWeapon() if self:attr("disarmed") then diff --git a/game/modules/tome/data/birth/classes/afflicted.lua b/game/modules/tome/data/birth/classes/afflicted.lua new file mode 100644 index 0000000000000000000000000000000000000000..a5f73b16b7f767627e5980b323dfebb0bc676c3b --- /dev/null +++ b/game/modules/tome/data/birth/classes/afflicted.lua @@ -0,0 +1,74 @@ +-- 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 + +newBirthDescriptor{ + type = "class", + name = "Afflicted", + desc = { + "Afflicted classes have been twisted by their association with evil forces.", + "They can use these forces to their advantage, but at a cost...", + }, + descriptor_choices = + { + subclass = + { + __ALL__ = "disallow", + Cursed = function() return profile.mod.allow_build.afflicted_cursed and "allow" or "disallow" end, + }, + }, + copy = { + }, +} + +newBirthDescriptor{ + type = "subclass", + name = "Cursed", + desc = { + "Through ignorance, greed or folly the cursed served some dark design and are now doomed to pay for their sins.", + "Their only master now is the hatred they carry for every living thing.", + "Drawing strength from the death of all they encounter, the cursed become terrifying combatants.", + "Worse, any who approach the cursed can be driven mad by their terrible aura.", + "Their most important stats are: Strength and Willpower", + }, + stats = { wil=4, str=5, }, + talents_types = { + ["cursed/gloom"]={true, 0.0}, + ["cursed/slaughter"]={true, 0.0}, + ["cursed/endless-hunt"]={true, 0.0}, + ["cursed/cursed-form"]={true, 0.0}, + ["technique/combat-training"]={true, 0.3}, + ["cunning/survival"]={false, 0.0} + }, + talents = { + [ActorTalents.T_WEAPON_COMBAT] = 1, + [ActorTalents.T_UNNATURAL_BODY] = 1, + [ActorTalents.T_GLOOM] = 1, + [ActorTalents.T_SLASH] = 1, + [ActorTalents.T_DOMINATE] = 1, + [ActorTalents.T_AXE_MASTERY] = 1 + }, + copy = { + max_life = 110, + life_rating = 12, + resolvers.equip{ id=true, + {type="weapon", subtype="battleaxe", name="iron battleaxe", autoreq=true}, + {type="armor", subtype="light", name="rough leather armour", autoreq=true} + }, + }, +} diff --git a/game/modules/tome/data/birth/descriptors.lua b/game/modules/tome/data/birth/descriptors.lua index 54ee5014c9e440667e74c9957cfeb3ecfe681a64..1e1aaa2186f35c5a39819d6d4c27f0899ecfd609 100644 --- a/game/modules/tome/data/birth/descriptors.lua +++ b/game/modules/tome/data/birth/descriptors.lua @@ -188,3 +188,4 @@ load("/data/birth/classes/mage.lua") load("/data/birth/classes/wilder.lua") load("/data/birth/classes/divine.lua") load("/data/birth/classes/corrupted.lua") +load("/data/birth/classes/afflicted.lua") diff --git a/game/modules/tome/data/birth/worlds.lua b/game/modules/tome/data/birth/worlds.lua index 20f2a6a01bb8744bccd97f4e32c14484bdebd569..8b48ccd91fdb5a2bde424a9a741e6f389dd49f22 100644 --- a/game/modules/tome/data/birth/worlds.lua +++ b/game/modules/tome/data/birth/worlds.lua @@ -54,6 +54,7 @@ newBirthDescriptor{ ) and "allow" or "disallow" end, Corrupter = function() return profile.mod.allow_build.corrupter and "allow" or "disallow" end, + Afflicted = function() return profile.mod.allow_build.afflicted and "allow" or "disallow" end, }, }, } diff --git a/game/modules/tome/data/damage_types.lua b/game/modules/tome/data/damage_types.lua index 953fad62e23ff9b25697dba08e79d155e6a09239..368c2d740336d7ae562796378e8600bc03401d4a 100644 --- a/game/modules/tome/data/damage_types.lua +++ b/game/modules/tome/data/damage_types.lua @@ -62,15 +62,17 @@ setDefaultProjector(function(src, x, y, type, dam) if target == game.player then flash = game.flash.BAD end if src == game.player then flash = game.flash.GOOD end - local srcname = src.x and src.y and game.level.map.seens(src.x, src.y) and src.name:capitalize() or "Something" - game.logSeen(target, flash, "%s hits %s for %s%0.2f %s damage#LAST#.", srcname, target.name, DamageType:get(type).text_color or "#aaaaaa#", dam, DamageType:get(type).name) + if not DamageType:get(type).hideMessage then + local srcname = src.x and src.y and game.level.map.seens(src.x, src.y) and src.name:capitalize() or "Something" + game.logSeen(target, flash, "%s hits %s for %s%0.2f %s damage#LAST#.", srcname, target.name, DamageType:get(type).text_color or "#aaaaaa#", dam, DamageType:get(type).name) + end local sx, sy = game.level.map:getTileToScreen(x, y) if target:takeHit(dam, src) then if rsrc == game.player or rtarget == game.player then game.flyers:add(sx, sy, 30, (rng.range(0,2)-1) * 0.5, -3, "Kill!", {255,0,255}) end - else + elseif not DamageType:get(type).hideFlyer then if rsrc == game.player then game.flyers:add(sx, sy, 30, (rng.range(0,2)-1) * 0.5, -3, tostring(-math.ceil(dam)), {0,255,0}) elseif rtarget == game.player then @@ -714,3 +716,12 @@ newDamageType{ end end, } + +-- life leech (used cursed gloom skill) +newDamageType{ + name = "life leech", + type = "LIFE_LEECH", + text_color = "#F53CBE#", + hideMessage=true, + hideFlyer=true +} diff --git a/game/modules/tome/data/gfx/particles/dominated.lua b/game/modules/tome/data/gfx/particles/dominated.lua new file mode 100644 index 0000000000000000000000000000000000000000..619e8e65a2067b7d4c91514a1a7132f7e98f05e0 --- /dev/null +++ b/game/modules/tome/data/gfx/particles/dominated.lua @@ -0,0 +1,46 @@ +-- 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 + +return { generator = function() + local ad = rng.range(0, 360) + local a = math.rad(ad) + local dir = math.rad(ad) + local r = rng.range(15, 18) + local dirchance = rng.chance(2) + + return { +-- rail = 1, + life = 10, + size = 5, sizev = -0.3, sizea = 0, + + x = r * math.cos(a), xv = 0, xa = 0, + y = r * math.sin(a), yv = 0, ya = 0, + dir = dir, dirv = 0, dira = 0, + vel = -0.4, velv = 0, vela = 0, + + r = 88 / 255, rv = 0, ra = 0, + g = 40 / 255, gv = 0, ga = 0, + b = 48 / 255, bv = 0, ba = 0, + a = 180 / 255, av = 0 / 255, aa = 0, + } +end, }, +function(self) + self.ps:emit(6) +end, +60 diff --git a/game/modules/tome/data/gfx/particles/gloom.lua b/game/modules/tome/data/gfx/particles/gloom.lua new file mode 100644 index 0000000000000000000000000000000000000000..98937d26cee55b5c189bcbbc9c3636f73bef65de --- /dev/null +++ b/game/modules/tome/data/gfx/particles/gloom.lua @@ -0,0 +1,65 @@ +-- 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 + +local toggle = false + +return { generator = function() + local ad = rng.range(0, 360) + local a = math.rad(ad) + local dir = -math.rad(ad) + local r = rng.range(10, 90) + local dirchance = rng.chance(2) + + return { + trail = 1, + life = 30, + size = 12, sizev = -0.3, sizea = 0, + + x = r * math.cos(a), xv = 0, xa = 0, + y = r * math.sin(a), yv = 0, ya = 0, + dir = dir, dirv = 0, dira = 0, + vel = dirchance and 0.32 or -0.2, velv = 0, vela = dirchance and -0.01 or 0.01, + + r = 32, rv = 0, ra = 0, + g = 0, gv = 0, ga = 0, + b = 64, bv = 0, ba = 0, + a = rng.range(20, 50) / 255, av = 0, aa = 0, + +-- trail = 1, +-- life = 30, +-- size = 12, sizev = -0.3, sizea = 0, + +-- x = r * math.cos(a), xv = 0, xa = 0, +-- y = r * math.sin(a), yv = 0, ya = 0, +-- dir = dir, dirv = 0.1, dira = 0, +-- vel = dirchance and 0.32 or -0.2, velv = 0, vela = dirchance and -0.01 or 0.01, + +-- r = 0, rv = 0, ra = 0, +-- g = 0, gv = 0, ga = 0, +-- b = 0, bv = 0, ba = 0, +-- a = 60 / 255, av = -.1, aa = 0, + } +end, }, +function(self) +-- toggle = not toggle +-- if toggle then + self.ps:emit(1) +-- end +end, +20 diff --git a/game/modules/tome/data/gfx/particles/gloom_confused.lua b/game/modules/tome/data/gfx/particles/gloom_confused.lua new file mode 100644 index 0000000000000000000000000000000000000000..cf06180f2a841c90e1717f3b6ba72769be75f911 --- /dev/null +++ b/game/modules/tome/data/gfx/particles/gloom_confused.lua @@ -0,0 +1,46 @@ +-- 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 + +return { generator = function() + local ad = rng.range(0, 360) + local a = math.rad(ad) + local dir = math.rad(ad + 90) + local r = rng.range(14, 18) + local dirchance = rng.chance(2) + + return { +-- rail = 1, + life = 6, + size = 5, sizev = -0.7, sizea = 0, + + x = r * math.cos(a), xv = 0, xa = 0, + y = r * math.sin(a), yv = 0, ya = 0, + dir = dir, dirv = 0.04, dira = 0, + vel = 1.4, velv = 0, + + r = 240 / 255, rv = 0, ra = 0, + g = 240 / 255, gv = 0, ga = 0, + b = 240 / 255, bv = 0, ba = 0, + a = 180 / 255, av = 0 / 255, aa = 0, + } +end, }, +function(self) + self.ps:emit(10) +end, +60 diff --git a/game/modules/tome/data/gfx/particles/gloom_slow.lua b/game/modules/tome/data/gfx/particles/gloom_slow.lua new file mode 100644 index 0000000000000000000000000000000000000000..157a72edeb26f1dea4f976522179bb2e9439bebc --- /dev/null +++ b/game/modules/tome/data/gfx/particles/gloom_slow.lua @@ -0,0 +1,46 @@ +-- 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 + +return { generator = function() + local ad = rng.range(0, 360) + local a = math.rad(ad) + local dir = math.rad(ad) + local r = rng.range(1, 20) + local dirchance = rng.chance(2) + + return { +-- rail = 1, + life = 20, + size = 8, sizev = -0.2, sizea = 0, + + x = r * math.cos(a), xv = 0, xa = 0, + y = r * math.sin(a), yv = 0, ya = 0, + dir = dir, dirv = 0.1, dira = 0, + vel = dirchance and 0.01 or -0.01, velv = 0, vela = 0, + + r = 255 / 255, rv = 0, ra = 0, + g = 255 / 255, gv = 0, ga = 0, + b = 255 / 255, bv = 0, ba = 0, + a = 80 / 255, av = -5 / 255, aa = 0, + } +end, }, +function(self) + self.ps:emit(1) +end, +20 diff --git a/game/modules/tome/data/gfx/particles/gloom_stunned.lua b/game/modules/tome/data/gfx/particles/gloom_stunned.lua new file mode 100644 index 0000000000000000000000000000000000000000..f11e9d07f154e646df9059559d1d5e08c0d10e0b --- /dev/null +++ b/game/modules/tome/data/gfx/particles/gloom_stunned.lua @@ -0,0 +1,47 @@ +-- 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 + +return { generator = function() + local ad = rng.range(0, 360) + local a = math.rad(ad) + local dird = rng.range(0, 360) + local dir = math.rad(dird) + local r = rng.range(1, 20) + local dirchance = rng.chance(2) + + return { +-- rail = 1, + life = 5, + size = 3, sizev = 0, sizea = 0, + + x = r * math.cos(a), xv = 0, xa = 0, + y = r * math.sin(a), yv = 0, ya = 0, + dir = dir, dirv = 0, dira = 0, + vel = dirchance and 0.6 or -0.6, velv = 0, vela = dirchance and -0.02 or 0.02, + + r = 64 / 255, rv = 0, ra = 0, + g = 196 / 255, gv = 0, ga = 0, + b = 32 / 255, bv = 0, ba = 0, + a = 255 / 255, av = -5 / 255, aa = 0, + } +end, }, +function(self) + self.ps:emit(10) +end, +60 diff --git a/game/modules/tome/data/gfx/particles/gloom_weakness.lua b/game/modules/tome/data/gfx/particles/gloom_weakness.lua new file mode 100644 index 0000000000000000000000000000000000000000..e3aaae918e45e269f651a81e047322d2ec7ac5a9 --- /dev/null +++ b/game/modules/tome/data/gfx/particles/gloom_weakness.lua @@ -0,0 +1,45 @@ +-- 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 + +return { generator = function() + local ad = 90 * rng.range(0, 3) + rng.range(0, 89) + local a = math.rad(ad) + local dir = math.rad(ad + 90) + local r = rng.range(2, 15) + + return { +-- rail = 1, + life = 30, + size = 8, sizev = -0.2, sizea = 0, + + x = r * math.cos(a), xv = 0, xa = 0, + y = r * math.sin(a), yv = 0, ya = 0, + dir = dir, dirv = 1.57 / 30, dira = 0, + vel = 0.5, velv = 0, vela = 0, + + r = 0 / 255, rv = 0, ra = 0, + g = 0 / 255, gv = 0, ga = 0, + b = 255 / 255, bv = 0, ba = 0, + a = 40 / 255, av = 0 / 255, aa = 0, + } +end, }, +function(self) + self.ps:emit(4) +end, +60 diff --git a/game/modules/tome/data/gfx/particles/stalked.lua b/game/modules/tome/data/gfx/particles/stalked.lua new file mode 100644 index 0000000000000000000000000000000000000000..06f26babfe7ac0327ede66b1bcefab385937d7a9 --- /dev/null +++ b/game/modules/tome/data/gfx/particles/stalked.lua @@ -0,0 +1,46 @@ +-- 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 + +return { generator = function() + local ad = 90 * rng.range(0, 3) + local a = math.rad(ad) + local dir = math.rad(ad + 90) + local r = rng.range(2, 15) + local dirchance = rng.chance(2) + + return { +-- rail = 1, + life = 30, + size = 4, sizev = -0.1, sizea = 0, + + x = r * math.cos(a), xv = 0, xa = 0, + y = r * math.sin(a), yv = 0, ya = 0, + dir = dir, dirv = 0, dira = 0, + vel = dirchance and 0.8 or -0.8, velv = 0, vela = 0, + + r = 228 / 255, rv = 0, ra = 0, + g = 40 / 255, gv = 0, ga = 0, + b = 5 / 255, bv = 0, ba = 0, + a = 255 / 255, av = 0 / 255, aa = 0, + } +end, }, +function(self) + self.ps:emit(60) +end, +60 diff --git a/game/modules/tome/data/talents.lua b/game/modules/tome/data/talents.lua index 24611fb45c217c83132aae58c30a27dfe0b0f76a..e33fec559758bb86cc81a104a201100e18dee387 100644 --- a/game/modules/tome/data/talents.lua +++ b/game/modules/tome/data/talents.lua @@ -46,3 +46,4 @@ load("/data/talents/gifts/gifts.lua") load("/data/talents/divine/divine.lua") load("/data/talents/corruptions/corruptions.lua") load("/data/talents/undeads/undeads.lua") +load("/data/talents/cursed/cursed.lua") diff --git a/game/modules/tome/data/talents/cursed/cursed-form.lua b/game/modules/tome/data/talents/cursed/cursed-form.lua new file mode 100644 index 0000000000000000000000000000000000000000..d048daf6bd5fc17b711458956fda753c3aea4069 --- /dev/null +++ b/game/modules/tome/data/talents/cursed/cursed-form.lua @@ -0,0 +1,222 @@ +-- 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 + +local function getHateMultiplier(self, min, max) + return (min + ((max - min) * self.hate / 10)) +end + +newTalent{ + name = "Unnatural Body", + type = {"cursed/cursed-form", 1}, + mode = "passive", + require = cursed_str_req1, + points = 5, + on_learn = function(self, t) + -- assume on only learning one point at a time (true when this was written) + local level = self:getTalentLevelRaw(t) + if level == 1 then + -- baseline + self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) - 25 + self.combat_spellresist = self.combat_spellresist - 10 + self.max_life = self.max_life + 15 + elseif level == 2 then + self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) + 5 + self.combat_spellresist = self.combat_spellresist + 2 + self.max_life = self.max_life + 15 + elseif level == 3 then + self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) + 5 + self.combat_spellresist = self.combat_spellresist + 2 + self.max_life = self.max_life + 15 + elseif level == 4 then + self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) + 5 + self.combat_spellresist = self.combat_spellresist + 2 + self.max_life = self.max_life + 15 + elseif level == 5 then + self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) + 5 + self.combat_spellresist = self.combat_spellresist + 2 + self.max_life = self.max_life + 15 + end + return true + end, + on_unlearn = function(self, t) + -- assume on only learning one point at a time (true when this was written) + local level = self:getTalentLevelRaw(t) + if not level or level == 0 then + -- baseline + self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) + 25 + self.combat_spellresist = self.combat_spellresist + 10 + self.max_life = self.max_life - 15 + elseif level == 1 then + self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) - 5 + self.combat_spellresist = self.combat_spellresist - 2 + self.max_life = self.max_life - 15 + elseif level == 2 then + self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) - 5 + self.combat_spellresist = self.combat_spellresist - 2 + self.max_life = self.max_life - 15 + elseif level == 3 then + self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) - 5 + self.combat_spellresist = self.combat_spellresist - 2 + self.max_life = self.max_life - 15 + elseif level == 4 then + self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) - 5 + self.combat_spellresist = self.combat_spellresist - 2 + self.max_life = self.max_life - 15 + end + + return true + end, + do_regenLife = function(self, t) + heal = math.sqrt(self:getTalentLevel(t) * 2) * self.max_life * 0.003 + if heal > 0 then + self:heal(heal) + end + end, + info = function(self, t) + heal = math.sqrt(self:getTalentLevel(t) * 2) * self.max_life * 0.003 + local level = self:getTalentLevelRaw(t) + if level == 1 then + return ([[The curse has twisted your body into an unnatural form. + (-25%% fire resistance, -10 spell save, +15 maximum life, +%0.1f life per turn).]]):format(heal) + elseif level == 2 then + return ([[The curse has twisted your body into an unnatural form. + (-20%% fire resistance, -8 spell save, +15 maximum life, +%0.1f life per turn).]]):format(heal) + elseif level == 3 then + return ([[The curse has twisted your body into an unnatural form. + (-15%% fire resistance, -6 spell save, +15 maximum life, +%0.1f life per turn).]]):format(heal) + elseif level == 4 then + return ([[The curse has twisted your body into an unnatural form. + (-10%% fire resistance, -4 spell save, +15 maximum life, +%0.1f life per turn).]]):format(heal) + else + return ([[The curse has twisted your body into an unnatural form. + (-5%% fire resistance, -2 spell save, +15 maximum life, +%0.1f life per turn).]]):format(heal) + end + end, +} + +--newTalent{ +-- name = "Obsession", +-- type = {"cursed/cursed-form", 2}, +-- require = cursed_str_req2, +-- mode = "passive", +-- points = 5, +-- on_learn = function(self, t) +-- self.hate_per_kill = self.hate_per_kill + 0.1 +-- end, +-- on_unlearn = function(self, t) +-- self.hate_per_kill = self.hate_per_kill - 0.1 +-- end, +-- info = function(self, t) +-- return ([[Your suffering will become theirs. For every life that is taken you gain an extra %0.1f hate.]]):format(self:getTalentLevelRaw(t) * 0.1) +-- end +--} + +--newTalent{ +-- name = "Suffering", +-- type = {"cursed/cursed-form", 2}, +-- require = cursed_str_req2, +-- mode = "passive", +-- points = 5, +-- on_learn = function(self, t) +-- return true +-- end, +-- on_unlearn = function(self, t) +-- return true +-- end, +-- do_onTakeHit = function(self, t, damage) +-- if damage > 0 then +-- local hatePerLife = (1 + self:getTalentLevel(t)) / (self.max_life * 1.5) +-- self.hate = math.max(self.max_hate, self.hate + damage * hatePerLife) +-- end +-- end, +-- info = function(self, t) +-- local hatePerLife = (1 + self:getTalentLevel(t)) / (self.max_life * 1.5) +-- return ([[Your suffering will become theirs. For every %d life that is taken, you gain 1 hate.]]):format(1 / hatePerLife) +-- end +--} + +newTalent{ + name = "Relentless", + type = {"cursed/cursed-form", 2}, + mode = "passive", + require = cursed_str_req2, + points = 5, + on_learn = function(self, t) + self.fear_immune = self.stun_immune or 0 + 0.15 + self.confusion_immune = self.stun_immune or 0 + 0.15 + self.knockback_immune = self.knockback_immune or 0 + 0.15 + self.stun_immune = self.stun_immune or 0 + 0.15 + return true + end, + on_unlearn = function(self, t) + self.fear_immune = self.stun_immune or 0 + 0.15 + self.confusion_immune = self.stun_immune or 0 + 0.15 + self.knockback_immune = self.knockback_immune or 0 + 0.15 + self.stun_immune = self.stun_immune or 0 + 0.15 + return true + end, + info = function(self, t) + return ([[Your thirst for blood drives your movements. (+%d%% confusion, fear, knockback and stun immunity)]]):format(self:getTalentLevelRaw(t) * 15) + end, +} + +newTalent{ + name = "Seethe", + type = {"cursed/cursed-form", 3}, + random_ego = "utility", + require = cursed_str_req3, + points = 5, + cooldown = 400, + action = function(self, t) + local increase = 2 + self:getTalentLevel(t) * 0.9 + hate = math.min(self.max_hate, self.hate + increase) + self:incHate(hate) + + local damage = self.max_life * 0.25 + self:project({type="hit"}, self.x, self.y, DamageType.BLIGHT, damage) + game.level.map:particleEmitter(self.x, self.y, 5, "fireflash", {radius=5, tx=self.x, ty=self.y}) + game:playSoundNear(self, "talents/fireflash") + return true + end, + info = function(self, t) + local increase = 2 + self:getTalentLevel(t) * 0.9 + local damage = self.max_life * 0.25 + return ([[Focus your rage gaining %0.1f hate at the cost of %d life.]]):format(increase, damage) + end, +} + +newTalent{ + name = "Enrage", + type = {"cursed/cursed-form", 4}, + require = cursed_str_req4, + points = 5, + rage = 0.1, + cooldown = 50, + action = function(self, t) + local life = 50 + self:getTalentLevel(t) * 50 + self:setEffect(self.EFF_INCREASED_LIFE, 20, { life = life }) + return true + end, + info = function(self, t) + local life = 50 + self:getTalentLevel(t) * 50 + return ([[In a burst of rage you become an even more fearsome opponent gaining %d extra life for 20 turns.]]):format(life) + end, +} + + diff --git a/game/modules/tome/data/talents/cursed/cursed.lua b/game/modules/tome/data/talents/cursed/cursed.lua new file mode 100644 index 0000000000000000000000000000000000000000..81896e65f2b08fb807477cf0e519f98c1e09c927 --- /dev/null +++ b/game/modules/tome/data/talents/cursed/cursed.lua @@ -0,0 +1,73 @@ +-- 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 + +-- Afflictions +newTalentType{ type="cursed/cursed-form", name = "cursed form", description = "You are wracked with the dark energies of the curse." } +newTalentType{ type="cursed/slaughter", name = "slaughter", description = "Your axe yearns for its next victim." } +newTalentType{ type="cursed/endless-hunt", name = "endless hunt", description = "Each day you lift your weary body and begin the unending hunt." } +newTalentType{ type="cursed/gloom", name = "gloom", description = "All those in your sight must share your despair." } + + +-- Generic requires for corruptions based on talent level +cursed_wil_req1 = { + stat = { wil=function(level) return 12 + (level-1) * 2 end }, + level = function(level) return 0 + (level-1) end, +} +cursed_wil_req2 = { + stat = { wil=function(level) return 20 + (level-1) * 2 end }, + level = function(level) return 4 + (level-1) end, +} +cursed_wil_req3 = { + stat = { wil=function(level) return 28 + (level-1) * 2 end }, + level = function(level) return 8 + (level-1) end, +} +cursed_wil_req4 = { + stat = { wil=function(level) return 36 + (level-1) * 2 end }, + level = function(level) return 12 + (level-1) end, +} +cursed_wil_req5 = { + stat = { wil=function(level) return 44 + (level-1) * 2 end }, + level = function(level) return 16 + (level-1) end, +} + +cursed_str_req1 = { + stat = { str=function(level) return 12 + (level-1) * 2 end }, + level = function(level) return 0 + (level-1) end, +} +cursed_str_req2 = { + stat = { str=function(level) return 20 + (level-1) * 2 end }, + level = function(level) return 4 + (level-1) end, +} +cursed_str_req3 = { + stat = { str=function(level) return 28 + (level-1) * 2 end }, + level = function(level) return 8 + (level-1) end, +} +cursed_str_req4 = { + stat = { str=function(level) return 36 + (level-1) * 2 end }, + level = function(level) return 12 + (level-1) end, +} +cursed_str_req5 = { + stat = { str=function(level) return 44 + (level-1) * 2 end }, + level = function(level) return 16 + (level-1) end, +} + +load("/data/talents/cursed/cursed-form.lua") +load("/data/talents/cursed/slaughter.lua") +load("/data/talents/cursed/endless-hunt.lua") +load("/data/talents/cursed/gloom.lua") diff --git a/game/modules/tome/data/talents/cursed/endless-hunt.lua b/game/modules/tome/data/talents/cursed/endless-hunt.lua new file mode 100644 index 0000000000000000000000000000000000000000..fd5bbdc1997d359451ffa6b5694fc501cecfb1ac --- /dev/null +++ b/game/modules/tome/data/talents/cursed/endless-hunt.lua @@ -0,0 +1,192 @@ +-- 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 + +local function getHateMultiplier(self, min, max) + return (min + ((max - min) * self.hate / 10)) +end + +local function checkWillFailure(self, target, minChance, maxChance, attackStrength) + -- attack power is analogous to mental resist except all willpower and no cunning + local attack = self:getWil() * 0.5 * attackStrength + local defense = target:combatMentalResist() + + -- used this instead of checkHit to get a curve that is a little more ratio dependent than difference dependent. + -- + 10 prevents large changes for low attack/defense values + -- 2 * log adjusts falloff to roughly get 0% break near attack = 0.5 * defense and 100% break near attack = 2 * defense + local chance = minChance + (1 + 2 * math.log((attack + 10) / (defense + 10))) * (maxChance - minChance) * 0.5 + + local result = rng.avg(1, 100) + print("checkWillFailure", self.name, self.level, target.name, target.level, minChance, chance, maxChance) + + if result <= minChance then return true end + if result >= maxChance then return false end + return result <= chance +end + +newTalent{ + name = "Dominate", + type = {"cursed/endless-hunt", 1}, + require = cursed_wil_req1, + points = 5, + random_ego = "attack", + cooldown = 6, + hate = 0.1, + action = function(self, t) + local target = {type="hit", range=self:getTalentRange(t)} + local x, y, target = self:getTarget(target) + if not x or not y or not target then return nil end + if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + + -- attempt domination + if checkWillFailure(self, target, 15, 85, 1) then + local damMult = 1 + self:combatTalentWeaponDamage(t, 0.1, 0.4) + target:setEffect(target.EFF_DOMINATED, 4, { dominatedSource = self, dominatedDamMult = damMult }) + else + game.logSeen(target, "%s resists the domination!", target.name:capitalize()) + end + + -- just a regular attack (but maybe..with domination!) + self:attackTarget(target, nil, 1, true) + + return true + end, + info = function(self, t) + local damMult = self:combatTalentWeaponDamage(t, 0.1, 0.4) + return ([[Combine strength and will to overpower your opponent. A failed save versus will will add %d%% melee damage to your attacks for 4 turns.]]):format(damMult * 100) + end, +} + +newTalent{ + name = "Preternatural Senses", + type = {"cursed/endless-hunt", 2}, + mode = "passive", + require = cursed_wil_req2, + points = 5, + range = function(self, t) + return 2 + getHateMultiplier(self, 0, self:getTalentLevel(t) * 1.5) + end, + info = function(self, t) + local maxRange = 2 + math.floor(self:getTalentLevel(t) * 1.5) + return ([[Your preternatural senses aid you in your hunt for the next victim. You sense foes in a hate-based radius of %d to %d.]]):format(2, maxRange) + end, +} + +newTalent{ + name = "Blindside", + type = {"cursed/endless-hunt", 3}, + require = cursed_wil_req3, + points = 5, + random_ego = "attack", + cooldown = 10, + hate = 0.1, + range = 10, + action = function(self, t) + local tg = {type="hit", range=self:getTalentRange(t)} + local x, y, target = self:getTarget(tg) + if not x or not y or not target then return nil end + if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then return nil end + + local start = rng.range(0, 8) + for i = start, start + 8 do + local x = target.x + (i % 3) - 1 + local y = target.y + math.floor((i % 9) / 3) - 1 + if game.level.map:isBound(x, y) + and self:canMove(x, y) + and not game.level.map.attrs(x, y, "no_teleport") then + self:move(x, y, true) + game:playSoundNear(self, "talents/teleport") + local multiplier = self:combatTalentWeaponDamage(t, 0.9, 1.9) * getHateMultiplier(self, 0.3, 1.0) + self:attackTarget(target, nil, multiplier, true) + return true + end + end + + return true + end, + info = function(self, t) + local multiplier = self:combatTalentWeaponDamage(t, 1.1, 1.9) + return ([[With blinding speed you suddenly appear next to a target up to %d spaces away and attack for %d%% to %d%% rage-based damage.]]):format(self:getTalentRange(t), multiplier * 30, multiplier * 100) + end, +} + +newTalent{ + name = "Stalk", + type = {"cursed/endless-hunt", 4}, + require = cursed_wil_req4, + points = 5, + random_ego = "attack", + cooldown = 25, + hate = 0.1, + range = 10, + action = function(self, t) + local tg = {type="hit", range=self:getTalentRange(t), talent=t} + local x, y, target = self:getTarget(tg) + if not x or not y or not target or target == self then return nil end + + if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then + game.logPlayer(self, "You are too far to begin stalking that prey!") + return nil + end + + if self:hasLOS(x, y) and target:canSee(self) then + game.logPlayer(self, "You cannot begin stalking prey that can see you!") + return nil + end + + local duration = 4 + math.floor(self:getTalentLevel(t) * 2) + self:setEffect(self.EFF_STALKER, duration, {target=target}) + target:setEffect(self.EFF_STALKED, duration, {target=self}) + + return true + end, + info = function(self, t) + local duration = 4 + math.floor(self:getTalentLevel(t) * 2) + local critical = math.min(100, 40 + self:getTalentLevel(t) * 12) + return ([[Stalk a single opponent starting from a position that is out of sight. + You will be invisible to your target for %d turns or until you attack (%d%% chance of a critical strike). + Your gloom will not affect the the stalked prey.]]):format(duration, critical) + end, +} + +--newTalent{ +-- name = "Spite", +-- type = {"cursed/endless-hunt", 4}, +-- require = cursed_wil_req4, +-- points = 5, +-- random_ego = "attack", +-- cooldown = 4, +-- hate = 0.1, +-- range = 3, +-- action = function(self, t) +-- local tg = {type="hit", range=self:getTalentRange(t)} +-- local x, y, target = self:getTarget(tg) +-- if not x or not y or not target then return nil end +-- +-- local damage = self:getWil() * self:getTalentLevel(t) +-- self:project(tg, x, y, DamageType.BLIGHT, damage) +-- +-- game.level.map:particleEmitter(target.x, target.y, 1, "ball_fire", {radius=1}) +-- game:playSoundNear(self, "talents/devouringflame") +-- return true +-- end, +-- info = function(self, t) +-- local damage = self:getWil() * self:getTalentLevel(t) +-- return ([[Focusing your hate you blast your target for %d blight damage. Damage increases with willpower.]]):format(damage) +-- end, +--} diff --git a/game/modules/tome/data/talents/cursed/gloom.lua b/game/modules/tome/data/talents/cursed/gloom.lua new file mode 100644 index 0000000000000000000000000000000000000000..4d51dc2b13b28a99c439897ed0d3455b7c51da8c --- /dev/null +++ b/game/modules/tome/data/talents/cursed/gloom.lua @@ -0,0 +1,236 @@ +-- 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 + +local function getHateMultiplier(self, min, max) + return (min + ((max - min) * self.hate / 10)) +end + +local function getWillDamage(self, t, base, max) + -- Compute at "max" + local mod = max / ((base + 100) * ((math.sqrt(5) - 1) * 0.8 + 1)) + -- Compute real + return (base + self:getWil()) * ((math.sqrt(self:getTalentLevel(t)) - 1) * 0.8 + 1) * mod +end + +local function checkWillFailure(self, target, minChance, maxChance, attackStrength) + -- attack power is analogous to mental resist except all willpower and no cunning + local attack = self:getWil() * 0.5 * attackStrength + local defense = target:combatMentalResist() + + -- used this instead of checkHit to get a curve that is a little more ratio dependent than difference dependent. + -- + 10 prevents large changes for low attack/defense values + -- 2 * log adjusts falloff to roughly get 0% break near attack = 0.5 * defense and 100% break near attack = 2 * defense + local chance = minChance + (1 + 2 * math.log((attack + 10) / (defense + 10))) * (maxChance - minChance) * 0.5 + + local result = rng.avg(1, 100) + --game.logSeen(target, "[%s]: %0.3f / %0.3f; %d vs %d; %d to %d", target.name:capitalize(), result, chance, attack, defense, minChance, maxChance) + + if result <= minChance then return true end + if result >= maxChance then return false end + return result <= chance, chance +end + +local function getWillFailureEffectiveness(self, minChance, maxChance, attackStrength) + return attackStrength * self:getWil() * 0.05 * (minChance + (maxChance - minChance) / 2) +end + +newTalent{ + name = "Gloom", + type = {"cursed/gloom", 1}, + mode = "sustained", + require = cursed_wil_req1, + points = 5, + cooldown = 0, + range = 3, + activate = function(self, t) + self.torment_turns = nil -- restart torment + game:playSoundNear(self, "talents/arcane") + return { + particle = self:addParticles(Particles.new("gloom", 1)), + } + end, + deactivate = function(self, t, p) + self:removeParticles(p.particle) + return true + end, + do_gloom = function(self, tGloom) + -- all gloom effects are handled here + local tWeakness = self:getTalentFromId(self.T_WEAKNESS) + local tTorment = self:getTalentFromId(self.T_TORMENT) + local tAbsorbLife = self:getTalentFromId(self.T_ABSORB_LIFE) + local lifeAbsorbed = 0 + local attackStrength = 0.3 + self:getTalentLevel(tGloom) * 0.12 + local tormentHit = false + + local doTorment = false + local resetTorment = false + if tTorment and self:getTalentLevelRaw(tTorment) > 0 then + if not self.torment_turns then + -- initialize turns + resetTorment = true + elseif self.torment_turns == 0 then + -- attack one of our targets + doTorment = true; + else + -- reduce Torment turns + self.torment_turns = self.torment_turns - 1 + end + end + + local grids = core.fov.circle_grids(self.x, self.y, self:getTalentRange(tGloom), true) + for x, yy in pairs(grids) do + for y, _ in pairs(grids[x]) do + local target = game.level.map(x, y, Map.ACTOR) + if target + and self:reactionToward(target) < 0 + and (not target.stalker or target.stalker ~= self) then + + -- Gloom + if tGloom and self:getTalentLevelRaw(tGloom) > 0 then + local attackStrength = 0.3 + self:getTalentLevel(tGloom) * 0.12 + if checkWillFailure(self, target, 5, 35, attackStrength) then + local effect = rng.range(1, 3) + if effect == 1 then + -- confusion + if target:canBe("confusion") and not target:hasEffect(target.EFF_GLOOM_CONFUSED) then + target:setEffect(target.EFF_GLOOM_CONFUSED, 2, {power=0.75}) + --game:playSoundNear(self, "talents/fire") + hit = true + end + elseif effect == 2 then + -- stun + if target:canBe("stun") and not target:hasEffect(target.EFF_GLOOM_STUNNED) then + target:setEffect(target.EFF_GLOOM_STUNNED, 2, {}) + --game:playSoundNear(self, "talents/fire") + hit = true + end + elseif effect == 3 then + -- slow + if target:canBe("slow") and not target:hasEffect(target.EFF_GLOOM_SLOW) then + target:setEffect(target.EFF_GLOOM_SLOW, 2, {power=0.3}) + --game:playSoundNear(self, "talents/fire") + hit = true + end + end + end + end + + -- Weakness + if tWeakness and self:getTalentLevelRaw(tWeakness) > 0 then + local attackStrength = 0.3 + self:getTalentLevel(tWeakness) * 0.12 + if checkWillFailure(self, target, 10, 60, attackStrength) and not target:hasEffect(target.EFF_GLOOM_WEAKNESS) then + local turns = 3 + math.ceil(self:getTalentLevel(tWeakness)) + + local weapon = target:getInven("MAINHAND") + if weapon then + weapon = weapon[1] and weapon[1].combat + end + weapon = weapon or target.combat + local attack = target:combatAttack(weapon) * (2 + self:getTalentLevel(tWeakness)) * 3.5 / 100 + local damage = target:combatDamage(weapon) * (2 + self:getTalentLevel(tWeakness)) * 3.5 / 100 + target:setEffect(target.EFF_GLOOM_WEAKNESS, turns, {atk=attack, dam=damage}) + hit = true + end + end + + -- Torment + if doTorment then + resetTorment = true + local attackStrength = 0.3 + self:getTalentLevel(tTorment) * 0.12 + if checkWillFailure(self, target, 30, 95, attackStrength) then + local tg = {type="hit", friendlyfire=false, talent=tTorment} + local grids = self:project(tg, target.x, target.y, DamageType.BLIGHT, 30 + self:getWil() * 0.4 * self:getTalentLevel(tTorment), {type="slime"}) + --game:playSoundNear(self, "talents/spell_generic") + doTorment = false + else + game.logSeen(target, "#F53CBE#%s resists your torment.", target.name:capitalize()) + end + end + + -- Absorb Life + if tAbsorbLife and self:getTalentLevelRaw(tAbsorbLife) > 0 then + local fraction = self:getTalentLevel(tAbsorbLife) * 0.002 + local damage = math.min(target.max_life * fraction, self.max_life * fraction * 2) + local actualDamage = DamageType:get(DamageType.ABSORB_LIFE).projector(self, target.x, target.y, DamageType.ABSORB_LIFE, damage) + lifeAbsorbed = lifeAbsorbed + actualDamage + --game.logSeen(target, "#F53CBE#You absorb %.2f life from %s!", actualDamage, target.name:capitalize()) + end + end + end + end + + if resetTorment then + self.torment_turns = math.floor(15 - self:getTalentLevelRaw(tTorment)) + end + + -- absorb life + if lifeAbsorbed > 0 then + self:heal(lifeAbsorbed) + game.logPlayer(self, "#F53CBE#You absorb %0.1f life from your foes.", lifeAbsorbed) + end + end, + info = function(self, t) + local attackStrength = 0.3 + self:getTalentLevel(t) * 0.12 + local effectiveness = getWillFailureEffectiveness(self, 5, 35, attackStrength) + return ([[A terrible gloom surrounds you affecting all those who approach you. + The weak-minded may suffer from slow, stun or confusion. (%d effectiveness) + This ability is innate and carries no cost to activate or deactivate.]]):format(effectiveness) + end, +} + +newTalent{ + name = "Weakness", + type = {"cursed/gloom", 2}, + mode = "passive", + require = cursed_wil_req2, + points = 5, + info = function(self, t) + local turns = 3 + math.ceil(self:getTalentLevel(t)) + local attack = (2 + self:getTalentLevel(t)) * 3.5 + local damage = (2 + self:getTalentLevel(t)) * 3.5 + local attackStrength = 0.3 + self:getTalentLevel(t) * 0.12 + local effectiveness = getWillFailureEffectiveness(self, 10, 60, attackStrength) + return ([[The weak-minded caught in your gloom are crippled by fear for %d turns. (-%d%% attack, -%d%% damage, %d effectiveness)]]):format(turns, attack, damage, effectiveness) + end, +} + +newTalent{ + name = "Torment", + type = {"cursed/gloom", 3}, + mode = "passive", + require = cursed_wil_req3, + points = 5, + info = function(self, t) + local baseDamage = 30 + self:getWil() * 0.4 * self:getTalentLevel(t) + local attackStrength = 0.3 + self:getTalentLevel(t) * 0.12 + local effectiveness = getWillFailureEffectiveness(self, 30, 95, attackStrength) + return ([[Your rage builds within you for 12 turns then unleashes itself for %d to %d hate-based blight damage on the first one to enter your gloom. (%d effectiveness)]]):format(baseDamage * .5, baseDamage * 1.5, effectiveness) + end, +} + +newTalent{ + name = "Life Leech", + type = {"cursed/gloom", 4}, + mode = "passive", + require = cursed_wil_req4, + points = 5, + info = function(self, t) + return ([[Each turn your gloom leeches up to %0.1f%% of the life of foes.]]):format(self:getTalentLevel(t) * 0.2) + end, +} diff --git a/game/modules/tome/data/talents/cursed/slaughter.lua b/game/modules/tome/data/talents/cursed/slaughter.lua new file mode 100644 index 0000000000000000000000000000000000000000..920c50f2ec3aec0f545fdf06c637307308b6ae60 --- /dev/null +++ b/game/modules/tome/data/talents/cursed/slaughter.lua @@ -0,0 +1,235 @@ +-- 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 + +local function getHateMultiplier(self, min, max) + return (min + ((max - min) * self.hate / 10)) +end + +newTalent{ + name = "Slash", + type = {"cursed/slaughter", 1}, + require = cursed_str_req1, + points = 5, + random_ego = "attack", + cooldown = 6, + hate = 0.1, + action = function(self, t) + local weapon = self:hasAxeWeapon() + if not weapon then + game.logPlayer(self, "You cannot use %s without an axe!", t.name) + return nil + end + + local tg = {type="hit", range=self:getTalentRange(t)} + local x, y, target = self:getTarget(tg) + if not x or not y or not target then return nil end + if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + + local multiplier = 1 + (0.15 + .2 * self:getTalentLevel(t)) * getHateMultiplier(self, 0, 1) + local hit = self:attackTarget(target, nil, multiplier, true) + + return true + end, + info = function(self, t) + local multiplier = (0.15 + .2 * self:getTalentLevel(t)) + return ([[Slashes wildly at your target adding up to %d%% hate-based damage.]]):format(multiplier * 100) + end, +} + +newTalent{ + name = "Frenzy", + type = {"cursed/slaughter", 1}, + require = cursed_str_req2, + points = 5, + random_ego = "attack", + cooldown = 15, + hate = 0.1, + action = function(self, t) + local weapon = self:hasAxeWeapon() + if not weapon then + game.logPlayer(self, "You cannot use %s without an axe!", t.name) + return nil + end + + local targets = {} + for i = -1, 1 do + for j = -1, 1 do + local x, y = self.x + i, self.y + j + if (self.x ~= x or self.y ~= y) and game.level.map:isBound(x, y) and game.level.map(x, y, Map.ACTOR) then + local target = game.level.map(x, y, Map.ACTOR) + if target and self:reactionToward(target) < 0 then + targets[#targets+1] = target + end + end + end + end + + if #targets <= 0 then return nil end + + local multiplier = self:combatTalentWeaponDamage(t, 0.2, 0.7) * getHateMultiplier(self, 0.5, 1.0) + for i = 1, 4 do + local target = rng.table(targets) + + self:attackTarget(target, nil, multiplier, true) + end + + return true + end, + info = function(self, t) + local multiplier = self:combatTalentWeaponDamage(t, 0.2, 0.7) + return ([[Assault nearby foes with 4 fast attacks for %d%% to %d%% rage-based damage each.]]):format(multiplier * 50, multiplier * 100) + end, +} + +newTalent{ + name = "Reckless Charge", + type = {"cursed/slaughter", 3}, + require = cursed_str_req3, + points = 5, + random_ego = "attack", + cooldown = 20, + hate = 0.1, + range = 4, + action = function(self, t) + local weapon = self:hasAxeWeapon() + if not weapon then + game.logPlayer(self, "You cannot use %s without an axe!", t.name) + return nil + end + + local targeting = {type="bolt", range=self:getTalentRange(t), nolock=true} + local targetX, targetY, actualTarget = self:getTarget(targeting) + if not targetX or not targetY then return nil end + if math.floor(core.fov.distance(self.x, self.y, targetX, targetY)) > self:getTalentRange(t) then return nil end + + local lineFunction = line.new(self.x, self.y, targetX, targetY) + local nextX, nextY = lineFunction() + local currentX, currentY = self.x, self.y + local blocked = false + while nextX and nextY do + local blockingTarget = game.level.map(nextX, nextY, Map.ACTOR) + if blockingTarget and self:reactionToward(blockingTarget) < 0 then + -- attempt a knockback + local level = self:getTalentLevelRaw(t) + local maxSize = 2 + if level >= 5 then + maxSize = 4 + elseif level >= 3 then + maxSize = 3 + end + + if blockingTarget.size_category <= maxSize then + if blockingTarget:checkHit(self:combatAttackStr(), blockingTarget:combatPhysicalResist(), 0, 95, 15) and blockingTarget:canBe("knockback") then + -- determine where to move the target (any adjacent square that isn't next to the attacker) + local start = rng.range(0, 8) + for i = start, start + 8 do + local x = nextX + (i % 3) - 1 + local y = nextY + math.floor((i % 9) / 3) - 1 + if math.floor(core.fov.distance(currentY, currentX, x, y)) > 1 + and game.level.map:isBound(x, y) + and not game.level.map:checkAllEntities(x, y, "block_move", self) then + blockingTarget:move(x, y, true) + break + end + end + end + end + end + + -- check that we can move + if not game.level.map:isBound(nextX, nextY) or game.level.map:checkAllEntities(nextX, nextY, "block_move", self) then break end + + -- allow the move + currentX, currentY = nextX, nextY + nextX, nextY = lineFunction() + + -- attack adjacent targets + for i = 0, 8 do + local x = currentX + (i % 3) - 1 + local y = currentY + math.floor((i % 9) / 3) - 1 + local target = game.level.map(x, y, Map.ACTOR) + if target and self:reactionToward(target) < 0 then + local multiplier = self:combatTalentWeaponDamage(t, 0.5, 1.5) * getHateMultiplier(self, 0.3, 1.0) + self:attackTarget(target, nil, multiplier, true) + + game.level.map:particleEmitter(x, y, 1, "blood", {}) + game:playSoundNear(self, "actions/melee") + end + end + end + + self:move(currentX, currentY, true) + + return true + end, + info = function(self, t) + local multiplier = self:combatTalentWeaponDamage(t, 0.5, 1.5) + local level = self:getTalentLevelRaw(t) + local size + if level >= 5 then + size = "Big" + elseif level >= 3 then + size = "Medium-sized" + else + size = "Small" + end + return ([[Charge through your opponents attacking anyone near your path for %d%% to %d%% rage-based damage. %s opponents may be knocked from your path.]]):format(multiplier * 30, multiplier * 100, size) + end, +} + +newTalent{ + name = "Cleave", + type = {"cursed/slaughter", 4}, + mode = "passive", + require = cursed_str_req4, + points = 5, + on_attackTarget = function(self, t, target, multiplier) + local weapon = self:hasAxeWeapon() + if not weapon then + return nil + end + + if inCleave then return end + inCleave = true + + local chance = 28 + self:getTalentLevel(t) * 7 + if rng.percent(chance) then + local start = rng.range(0, 8) + for i = start, start + 8 do + local x = self.x + (i % 3) - 1 + local y = self.y + math.floor((i % 9) / 3) - 1 + local secondTarget = game.level.map(x, y, Map.ACTOR) + if secondTarget and secondTarget ~= target and self:reactionToward(secondTarget) < 0 then + local multiplier = multiplier or 1 * self:combatTalentWeaponDamage(t, 0.2, 0.7) * getHateMultiplier(self, 0.5, 1.0) + game.logSeen(self, "%s cleaves through another foe!", self.name:capitalize()) + self:attackTarget(secondTarget, nil, multiplier, true) + inCleave = false + return + end + end + end + inCleave = false + + end, + info = function(self, t) + local chance = 28 + self:getTalentLevel(t) * 7 + local multiplier = self:combatTalentWeaponDamage(t, 0.2, 0.7) + return ([[Every swing of your axe has a %d%% chance of striking a second target for %d%% to %d%% hate-based damage.]]):format(chance, multiplier * 50, multiplier * 100) + end, +} diff --git a/game/modules/tome/data/talents/misc/misc.lua b/game/modules/tome/data/talents/misc/misc.lua index 537c24990b510e589fbfab05ab5a29192a5e3870..90c08c3abcc3102261a5d6c25fc1c2a7fff38691 100644 --- a/game/modules/tome/data/talents/misc/misc.lua +++ b/game/modules/tome/data/talents/misc/misc.lua @@ -68,6 +68,13 @@ newTalent{ mode = "passive", hide = true, } +newTalent{ + name = "Hate Pool", + type = {"base/class", 1}, + info = "Allows you to have a hate pool.", + mode = "passive", + hide = true, +} newTalent{ name = "Improved Health I", diff --git a/game/modules/tome/data/timed_effects.lua b/game/modules/tome/data/timed_effects.lua index df558e98dd66f5b3890953087f1d47be821da621..b55a5b60f493de7dcf95b16a96df9b664208e6a0 100644 --- a/game/modules/tome/data/timed_effects.lua +++ b/game/modules/tome/data/timed_effects.lua @@ -1245,3 +1245,162 @@ newEffect{ self:removeTemporaryValue("move_project", eff.dig) end, } + +newEffect{ + name = "GLOOM_WEAKNESS", + desc = "Gloom Weakness", + type = "physical", + status = "detrimental", + parameters = { atk=10, dam=10 }, + on_gain = function(self, err) return "#F53CBE##Target# is weakened by your gloom." end, + on_lose = function(self, err) return "#F53CBE##Target# is no longer weakened." end, + activate = function(self, eff) + eff.particle = self:addParticles(Particles.new("gloom_weakness", 1)) + eff.atkid = self:addTemporaryValue("combat_atk", -eff.atk) + eff.damid = self:addTemporaryValue("combat_dam", -eff.dam) + end, + deactivate = function(self, eff) + self:removeParticles(eff.particle) + self:removeTemporaryValue("combat_atk", eff.atkid) + self:removeTemporaryValue("combat_dam", eff.damid) + end, +} + +newEffect{ + name = "GLOOM_SLOW", + desc = "Slowed by the gloom", + type = "magical", + status = "detrimental", + parameters = { power=0.1 }, + on_gain = function(self, err) return "#F53CBE##Target# moves reluctantly!", "+Slow" end, + on_lose = function(self, err) return "#Target# overcomes the gloom.", "-Slow" end, + activate = function(self, eff) + eff.particle = self:addParticles(Particles.new("gloom_slow", 1)) + eff.tmpid = self:addTemporaryValue("energy", {mod=-eff.power}) + eff.dur = self:updateEffectDuration(eff.dur, "slow") + end, + deactivate = function(self, eff) + self:removeTemporaryValue("energy", eff.tmpid) + self:removeParticles(eff.particle) + end, +} + +newEffect{ + name = "GLOOM_STUNNED", + desc = "Stunned by the gloom", + type = "physical", + status = "detrimental", + parameters = {}, + on_gain = function(self, err) return "#F53CBE##Target# is paralyzed with fear!", "+Stunned" end, + on_lose = function(self, err) return "#Target# overcomes the gloom", "-Stunned" end, + activate = function(self, eff) + eff.particle = self:addParticles(Particles.new("gloom_stunned", 1)) + eff.tmpid = self:addTemporaryValue("stunned", 1) + eff.dur = self:updateEffectDuration(eff.dur, "stun") + end, + deactivate = function(self, eff) + self:removeParticles(eff.particle) + self:removeTemporaryValue("stunned", eff.tmpid) + end, +} + +newEffect{ + name = "GLOOM_CONFUSED", + desc = "Confused by the gloom", + type = "magical", + status = "detrimental", + parameters = {}, + on_gain = function(self, err) return "#F53CBE##Target# is lost in dispair!", "+Confused" end, + on_lose = function(self, err) return "#Target# overcomes the gloom", "-Confused" end, + activate = function(self, eff) + eff.particle = self:addParticles(Particles.new("gloom_confused", 1)) + eff.tmpid = self:addTemporaryValue("confused", eff.power) + eff.dur = self:updateEffectDuration(eff.dur, "confusion") + end, + deactivate = function(self, eff) + self:removeParticles(eff.particle) + self:removeTemporaryValue("confused", eff.tmpid) + end, +} + +newEffect{ + name = "STALKER", + desc = "Stalking", + type = "physical", + status = "beneficial", + parameters = {}, + activate = function(self, eff) + if not self.stalkee then + self.stalkee = eff.target + game.logSeen(self, "#F53CBE#%s is being stalked by %s!", eff.target.name:capitalize(), self.name) + end + end, + deactivate = function(self, eff) + self.stalkee = nil + game.logSeen(self, "#F53CBE#%s is no longer being stalked by %s.", eff.target.name:capitalize(), self.name) + end, +} + +newEffect{ + name = "STALKED", + desc = "Being Stalked", + type = "physical", + status = "detrimental", + parameters = {}, + activate = function(self, eff) + if not self.stalker then + eff.particle = self:addParticles(Particles.new("stalked", 1)) + self.stalker = eff.target + end + end, + deactivate = function(self, eff) + self.stalker = nil + if eff.particle then self:removeParticles(eff.particle) end + end, +} + +newEffect{ + name = "INCREASED_LIFE", + desc = "Increased Life", + type = "physical", + status = "beneficial", + on_gain = function(self, err) return "#Target# gains extra life.", "+Life" end, + on_lose = function(self, err) return "#Target# loases extra life.", "-Life" end, + parameters = { life = 50 }, + activate = function(self, eff) + self.max_life = self.max_life + eff.life + self.life = self.life + eff.life + self.changed = true + end, + deactivate = function(self, eff) + self.max_life = self.max_life - eff.life + self.life = self.life - eff.life + self.changed = true + if self.life <= 0 then + game.logSeen(self, "%s died when the effects of increased life wore off.", self.name:capitalize()) + self:die(self) + end + end, +} + +newEffect{ + name = "DOMINATED", + desc = "Dominated", + type = "physical", + status = "detrimental", + on_gain = function(self, err) return "#F53CBE##Target# has been dominated!", "+Dominated" end, + on_lose = function(self, err) return "#F53CBE##Target# is no longer dominated.", "-Dominated" end, + parameters = { dominatedDamMult = 1.3 }, + activate = function(self, eff) + if not self.dominatedSource then + self.dominatedSource = eff.dominatedSource + self.dominatedDamMult = 1.3 or eff.dominatedDamMult + eff.particle = self:addParticles(Particles.new("dominated", 1)) + end + end, + deactivate = function(self, eff) + self.dominatedSource = nil + self.dominatedDamMult = nil + self:removeParticles(eff.particle) + end, +} \ No newline at end of file diff --git a/game/modules/tome/load.lua b/game/modules/tome/load.lua index dab5e1c0c91bac7142d3461e57336be7892a91b7..cbe39f2854fd31448a5c7d343f7b21876a988259 100644 --- a/game/modules/tome/load.lua +++ b/game/modules/tome/load.lua @@ -93,6 +93,7 @@ ActorResource:defineResource("Equilibrium", "equilibrium", ActorTalents.T_EQUILI ActorResource:defineResource("Vim", "vim", ActorTalents.T_VIM_POOL, "vim_regen", "Vim represents the amount of life energies/souls you have stolen. Each corruption talents requires some.") ActorResource:defineResource("Positive", "positive", ActorTalents.T_POSITIVE_POOL, "positive_regen", "Positive energy represents your reserve of powitive power. It slowly decreases.") ActorResource:defineResource("Negative", "negative", ActorTalents.T_NEGATIVE_POOL, "negative_regen", "Negative energy represents your reserve of negative power. It slowly decreases.") +ActorResource:defineResource("Hate", "hate", ActorTalents.T_HATE_POOL, "hate_regen", "Hate represents the level of frenzy of a cursed soul.") -- Actor stats ActorStats:defineStat("Strength", "str", 10, 1, 100, "Strength defines your character's ability to apply physical force. It increases your melee damage, damage done with heavy weapons, your chance to resist physical effects, and carrying capacity.")