From 16e50d393c48eed025be52fdb65e221597579b10 Mon Sep 17 00:00:00 2001 From: dg <dg@51575b47-30f0-44d4-a5cc-537603b46e54> Date: Fri, 29 Jun 2012 14:37:48 +0000 Subject: [PATCH] shhhh, as shadow we must be git-svn-id: http://svn.net-core.org/repos/t-engine4@5317 51575b47-30f0-44d4-a5cc-537603b46e54 --- game/modules/tome/class/Actor.lua | 106 ++++++++- game/modules/tome/class/PlayerDisplay.lua | 7 + game/modules/tome/class/interface/Combat.lua | 12 +- .../tome/class/interface/TooltipsData.lua | 6 + game/modules/tome/class/uiset/Minimalist.lua | 37 +++ .../tome/data/birth/classes/psionic.lua | 41 +++- .../tome/data/general/events/mice-quest.lua | 94 ++++++++ .../gfx/shockbolt/terrain/pedestal_heart.png | Bin 0 -> 7208 bytes game/modules/tome/data/talents/misc/npcs.lua | 4 +- .../tome/data/talents/psionic/feedback.lua | 221 ++++++++++++++++++ .../tome/data/talents/psionic/psionic.lua | 5 + .../tome/data/talents/psionic/solipsism.lua | 151 ++++++++++++ .../tome/data/timed_effects/mental.lua | 43 ++++ .../modules/tome/data/timed_effects/other.lua | 56 +++++ 14 files changed, 770 insertions(+), 13 deletions(-) create mode 100644 game/modules/tome/data/general/events/mice-quest.lua create mode 100644 game/modules/tome/data/gfx/shockbolt/terrain/pedestal_heart.png create mode 100644 game/modules/tome/data/talents/psionic/feedback.lua create mode 100644 game/modules/tome/data/talents/psionic/solipsism.lua diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua index 6a1cfe7581..2b5f2a6f80 100644 --- a/game/modules/tome/class/Actor.lua +++ b/game/modules/tome/class/Actor.lua @@ -327,6 +327,24 @@ function _M:useEnergy(val) end function _M:actBase() + -- Solipsism speed effects + local current_psi_percentage = self:getPsi() / self:getMaxPsi() + if self:attr("solipsism_threshold") and current_psi_percentage < self:attr("solipsism_threshold") then + if self:hasEffect(self.EFF_CLARITY) then + self:removeEffect(self.EFF_CLARITY) + end + self:setEffect(self.EFF_SOLIPSISM, 1, {power = self:attr("solipsism_threshold") - current_psi_percentage}) + elseif self:attr("clarity_threshold") and current_psi_percentage > self:attr("clarity_threshold") then + if self:hasEffect(self.EFF_SOLIPSISM) then + self:removeEffect(self.EFF_SOLIPSISM) + end + self:setEffect(self.EFF_CLARITY, 1, {power = current_psi_percentage - self:attr("clarity_threshold")}) + elseif self:hasEffect(self.EFF_SOLIPSISM) then + self:removeEffect(self.EFF_SOLIPSISM) + elseif self:hasEffect(self.EFF_CLARITY) then + self:removeEffect(self.EFF_CLARITY) + end + self.energyBase = self.energyBase - game.energy_to_act if self:attr("no_timeflow") then @@ -361,6 +379,11 @@ function _M:actBase() end self:regenResources() + + -- update psionic feedback + if self.psionic_feedback and self.psionic_feedback > 0 then + self.psionic_feedback = math.max(0, self.psionic_feedback - 1) + end -- Compute timed effects self:timedEffects() @@ -1304,7 +1327,23 @@ end --- Regenerate life, call it from your actor class act() method function _M:regenLife() if self.life_regen and not self:attr("no_life_regen") then - self.life = util.bound(self.life + self.life_regen * util.bound((self.healing_factor or 1), 0, 2.5), self.die_at, self.max_life) + local regen = self.life_regen * util.bound((self.healing_factor or 1), 0, 2.5) + + -- Psionic Balance + if self:knowTalent(self.T_BALANCE) then + local t = self:getTalentFromId(self.T_BALANCE) + local ratio = t.getBalanceRatio(self, t) + local psi_increase = regen * ratio + self:incPsi(psi_increase) + -- Quality of life hack, doesn't decrease life regen while resting.. was way to painful + if not self.resting then + regen = regen - psi_increase + end + end + + self.life = util.bound(self.life + regen, self.die_at, self.max_life) + + -- Blood Lock if self:attr("blood_lock") then self.life = util.bound(self.life, self.die_at, self:attr("blood_lock")) end @@ -1355,6 +1394,15 @@ function _M:onHeal(value, src) self:setEffect(self.EFF_REGENERATION, 6, {power=(value * self.fungal_growth / 100) / 6, no_wild_growth=true}) end + -- Psionic Balance + if self:knowTalent(self.T_BALANCE) then + local t = self:getTalentFromId(self.T_BALANCE) + local ratio = t.getBalanceRatio(self, t) + local psi_increase = value * ratio + self:incPsi(psi_increase) + value = value - psi_increase + end + -- Must be last! if self:attr("blood_lock") then if self.life + value > self:attr("blood_lock") then @@ -1532,13 +1580,27 @@ function _M:onTakeHit(value, src) t.explode(self, t, dam) end end + + if self:attr("resonance_shield") then + -- Absorb damage into the shield + if value / 2 <= self.resonance_shield_absorb then + self.resonance_shield_absorb = self.resonance_shield_absorb - (value / 2) + value = value / 2 + else + value = value - self.resonance_shield_absorb + self.resonance_shield_absorb = 0 + end + if self.resonance_shield_absorb <= 0 then + self:removeEffect(self.EFF_RESONANCE_SHIELD) + end + end if self:isTalentActive(self.T_BONE_SHIELD) then local t = self:getTalentFromId(self.T_BONE_SHIELD) t.absorb(self, t, self:isTalentActive(self.T_BONE_SHIELD)) value = 0 end - + if self.knowTalent and (self:knowTalent(self.T_SEETHE) or self:knowTalent(self.T_GRIM_RESOLVE)) then if not self:hasEffect(self.EFF_CURSED_FORM) then self:setEffect(self.EFF_CURSED_FORM, 1, { increase=0 }) @@ -1571,13 +1633,6 @@ function _M:onTakeHit(value, src) if value >= 6000 then world:gainAchievement("DAMAGE_6000", rsrc) end end - -- Stoned ? SHATTER ! - if self:attr("stoned") and value >= self.max_life * 0.3 then - -- Make the damage high enough to kill it - value = self.max_life + 1 - game.logSeen(self, "%s shatters into pieces!", self.name:capitalize()) - end - -- Frozen: absorb some damage into the iceblock if self:attr("encased_in_ice") then local eff = self:hasEffect(self.EFF_FROZEN) @@ -1588,6 +1643,36 @@ function _M:onTakeHit(value, src) eff.begone = game.turn end end + + -- Feedback pool: Stores damage as energy to use later + if self.psionic_feedback then + local current = self.psionic_feedback + local max = self.psionic_feedback_max or 100 + self.psionic_feedback = math.min(self.psionic_feedback_max, self.psionic_feedback + value) + end + + -- Solipsism + if self.knowTalent and self:knowTalent(self.T_SOLIPSISM) then + local t = self:getTalentFromId(self.T_SOLIPSISM) + local damage_to_psi = value * t.damageToPsi(self, t) + if self:getPsi() > damage_to_psi then + self:incPsi(-damage_to_psi) + else + damage_to_psi = self:getPsi() + self:incPsi(-damage_to_psi) + end + if damage_to_psi > 0 then + game.logSeen(self, "%s's mind suffers #BLUE#%d psi#LAST# damage from the attack.", self.name:capitalize(), damage_to_psi) + end + value = value - damage_to_psi + end + + -- Stoned ? SHATTER ! + if self:attr("stoned") and value >= self.max_life * 0.3 then + -- Make the damage high enough to kill it + value = self.max_life + 1 + game.logSeen(self, "%s shatters into pieces!", self.name:capitalize()) + end -- Adds hate if self:knowTalent(self.T_HATE_POOL) then @@ -3286,6 +3371,9 @@ function _M:breakStepUp() if self:hasEffect(self.EFF_REFLEXIVE_DODGING) then self:removeEffect(self.EFF_REFLEXIVE_DODGING) end + if self:hasEffect(self.EFF_DISMISSAL) then + self:removeEffect(self.EFF_DISMISSAL) + end end --- Breaks lightning speed if active diff --git a/game/modules/tome/class/PlayerDisplay.lua b/game/modules/tome/class/PlayerDisplay.lua index 3f135f17d9..85c985fd2d 100644 --- a/game/modules/tome/class/PlayerDisplay.lua +++ b/game/modules/tome/class/PlayerDisplay.lua @@ -372,6 +372,13 @@ function _M:display() {r=colors.BLUE.r / 5, g=colors.BLUE.g / 5, b=colors.BLUE.b / 5} )) h = h + self.font_h end + + if player.psionic_feedback_max then + self:mouseTooltip(self.TOOLTIP_FEEDBACK, self:makeTextureBar("#7fffd4#Feedback:", nil, player.psionic_feedback or 0, player.psionic_feedback_max, -1, x, h, 255, 255, 255, + {r=colors.YELLOW.r / 2, g=colors.YELLOW.g / 2, b=colors.YELLOW.b / 2}, + {r=colors.YELLOW.r / 5, g=colors.YELLOW.g / 5, b=colors.YELLOW.b / 5} + )) h = h + self.font_h + end local quiver = player:getInven("QUIVER") local ammo = quiver and quiver[1] diff --git a/game/modules/tome/class/interface/Combat.lua b/game/modules/tome/class/interface/Combat.lua index e50d082836..906c363d54 100644 --- a/game/modules/tome/class/interface/Combat.lua +++ b/game/modules/tome/class/interface/Combat.lua @@ -1295,7 +1295,17 @@ function _M:combatPhysicalResist(fake) if self:knowTalent(self.T_POWER_IS_MONEY) then add = add + util.bound(self.money / (90 - self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 5), 0, self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 7) end - return self:rescaleCombatStats(self.combat_physresist + (self:getCon() + self:getStr() + (self:getLck() - 50) * 0.5) * 0.35 + add) + + -- To return later + local total = self:rescaleCombatStats(self.combat_physresist + (self:getCon() + self:getStr() + (self:getLck() - 50) * 0.5) * 0.35 + add) + + -- Psionic Balance + if self:knowTalent(self.T_BALANCE) then + local t = self:getTalentFromId(self.T_BALANCE) + local ratio = t.getBalanceRatio(self, t) + total = (1 - ratio)*total + self:combatMentalResist(fake)*ratio + end + return total end --- Computes spell resistance diff --git a/game/modules/tome/class/interface/TooltipsData.lua b/game/modules/tome/class/interface/TooltipsData.lua index de7a62cbe3..5b1c44f799 100644 --- a/game/modules/tome/class/interface/TooltipsData.lua +++ b/game/modules/tome/class/interface/TooltipsData.lua @@ -115,6 +115,12 @@ To get meaningful amounts back in combat, you must absorb it through shields or Your capacity for storing energy is determined by your Willpower. ]] +TOOLTIP_FEEDBACK = [[#GOLD#Feedback#LAST# +Feedback represents energy you've stored up from being attacked. It decays quickly over time. +To get meaningful amounts back in combat, you must take damage. +Your capacity for storing feedback is determined by how many talents you know that harness it. +]] + TOOLTIP_NECROTIC_AURA = [[#GOLD#Necrotic Aura#LAST# Represents the raw materials for creating undead minions. It increases each time you or your minions kill something that is inside the aura radius. diff --git a/game/modules/tome/class/uiset/Minimalist.lua b/game/modules/tome/class/uiset/Minimalist.lua index bd00e18f9f..d088dccab2 100644 --- a/game/modules/tome/class/uiset/Minimalist.lua +++ b/game/modules/tome/class/uiset/Minimalist.lua @@ -72,6 +72,8 @@ hate_c = {0xF5/255, 0x3C/255, 0xBE/255} hate_sha = Shader.new("resources", {color=hate_c, speed=1000, distort={0.4,0.4}}) psi_c = {colors.BLUE.r/255, colors.BLUE.g/255, colors.BLUE.b/255} psi_sha = Shader.new("resources", {color=psi_c, speed=2000, distort={0.4,0.4}}) +feedback_c = {colors.YELLOW.r/255, colors.YELLOW.g/255, colors.YELLOW.b/255} +feedback_sha = Shader.new("resources", {color=feedback_c, speed=2000, distort={0.4,0.4}}) save_c = pos_c save_sha = pos_sha @@ -103,6 +105,8 @@ fshat_vim = {core.display.loadImage("/data/gfx/ui/resources/front_vim.png"):glTe fshat_vim_dark = {core.display.loadImage("/data/gfx/ui/resources/front_vim_dark.png"):glTexture()} fshat_psi = {core.display.loadImage("/data/gfx/ui/resources/front_psi.png"):glTexture()} fshat_psi_dark = {core.display.loadImage("/data/gfx/ui/resources/front_psi_dark.png"):glTexture()} +fshat_feedback = {core.display.loadImage("/data/gfx/ui/resources/front_psi.png"):glTexture()} +fshat_feedback_dark = {core.display.loadImage("/data/gfx/ui/resources/front_psi_dark.png"):glTexture()} fshat_air = {core.display.loadImage("/data/gfx/ui/resources/front_air.png"):glTexture()} fshat_air_dark = {core.display.loadImage("/data/gfx/ui/resources/front_air_dark.png"):glTexture()} @@ -511,6 +515,7 @@ function _M:showResourceTooltip(x, y, w, h, id, desc, is_first) if player:knowTalent(player.T_VIM_POOL) then list[#list+1] = {name="Vim", id="vim"} end if player:knowTalent(player.T_HATE_POOL) then list[#list+1] = {name="Hate", id="hate"} end if player:knowTalent(player.T_PSI_POOL) then list[#list+1] = {name="Psi", id="psi"} end + if player.psionic_feedback_max then list[#list+1] = {name="Feedback", id="feedback"} end Dialog:listPopup("Display/Hide resources", "Toggle:", list, 300, 300, function(sel) if not sel or not sel.id then return end game.player["_hide_resource_"..sel.id] = not game.player["_hide_resource_"..sel.id] @@ -969,6 +974,38 @@ function _M:displayResources(scale, bx, by, a) self:showResourceTooltip(bx+x*scale, by+y*scale, fshat[6], fshat[7], "res:psi", self.TOOLTIP_PSI) x, y = self:resourceOrientStep(orient, bx, by, scale, x, y, fshat[6], fshat[7]) elseif game.mouse:getZone("res:psi") then game.mouse:unregisterZone("res:psi") end + + ----------------------------------------------------------------------------------- + -- Feedback + if player.psionic_feedback_max and not player._hide_resource_feedback then + sshat[1]:toScreenFull(x-6, y+8, sshat[6], sshat[7], sshat[2], sshat[3], 1, 1, 1, a) + bshat[1]:toScreenFull(x, y, bshat[6], bshat[7], bshat[2], bshat[3], 1, 1, 1, a) + if feedback_sha.shad then feedback_sha:setUniform("a", a) feedback_sha.shad:use(true) end + local p = player.psionic_feedback / player.psionic_feedback_max + shat[1]:toScreenPrecise(x+49, y+10, shat[6] * p, shat[7], 0, p * 1/shat[4], 0, 1/shat[5], feedback_c[1], feedback_c[2], feedback_c[3], a) + if feedback_sha.shad then feedback_sha.shad:use(false) end + + if not self.res.feedback or self.res.feedback.vc ~= player.psionic_feedback or self.res.feedback.vm ~= player.psionic_feedback_max or self.res.feedback.vr ~= -1 then + self.res.feedback = { + hidable = "Feedback", + vc = player.psionic_feedback, vm = player.psionic_feedback_max, vr = -1, + cur = {core.display.drawStringBlendedNewSurface(font_sha, ("%d/%d"):format(player.psionic_feedback, player.psionic_feedback_max), 255, 255, 255):glTexture()}, + regen={core.display.drawStringBlendedNewSurface(sfont_sha, ("%+0.2f"):format(-1), 255, 255, 255):glTexture()}, + } + end + local dt = self.res.feedback.cur + dt[1]:toScreenFull(2+x+64, 2+y+10 + (shat[7]-dt[7])/2, dt[6], dt[7], dt[2], dt[3], 0, 0, 0, 0.7 * a) + dt[1]:toScreenFull(x+64, y+10 + (shat[7]-dt[7])/2, dt[6], dt[7], dt[2], dt[3], 1, 1, 1, a) + dt = self.res.feedback.regen + dt[1]:toScreenFull(2+x+144, 2+y+10 + (shat[7]-dt[7])/2, dt[6], dt[7], dt[2], dt[3], 0, 0, 0, 0.7 * a) + dt[1]:toScreenFull(x+144, y+10 + (shat[7]-dt[7])/2, dt[6], dt[7], dt[2], dt[3], 1, 1, 1, a) + + local front = fshat_feedback_dark + if player.psionic_feedback >= player.psionic_feedback_max then front = fshat_feedback end + front[1]:toScreenFull(x, y, front[6], front[7], front[2], front[3], 1, 1, 1, a) + self:showResourceTooltip(bx+x*scale, by+y*scale, fshat[6], fshat[7], "res:feedback", self.TOOLTIP_FEEDBACK) + x, y = self:resourceOrientStep(orient, bx, by, scale, x, y, fshat[6], fshat[7]) + elseif game.mouse:getZone("res:feedback") then game.mouse:unregisterZone("res:feedback") end ----------------------------------------------------------------------------------- -- Ammo diff --git a/game/modules/tome/data/birth/classes/psionic.lua b/game/modules/tome/data/birth/classes/psionic.lua index fb7b66e327..3e9565c426 100644 --- a/game/modules/tome/data/birth/classes/psionic.lua +++ b/game/modules/tome/data/birth/classes/psionic.lua @@ -32,12 +32,12 @@ newBirthDescriptor{ __ALL__ = "disallow", Mindslayer = "allow", Psion = "allow", + Solipsist = "allow", }, }, copy = { psi_regen = 0.2, }, - body = { PSIONIC_FOCUS = 1, QS_PSIONIC_FOCUS = 1,}, } newBirthDescriptor{ @@ -85,6 +85,7 @@ newBirthDescriptor{ [ActorTalents.T_TELEKINETIC_SMASH] = 1, [ActorTalents.T_SHOOT] = 1, }, + body = { PSIONIC_FOCUS = 1, QS_PSIONIC_FOCUS = 1,}, copy = { max_life = 110, resolvers.equip{ id=true, @@ -147,3 +148,41 @@ newBirthDescriptor{ life_rating = -4, }, } + +newBirthDescriptor{ + type = "subclass", + name = "Solipsist", + locked = function() return profile.mod.allow_build.psionic_solipsist and true or "hide" end, + locked_desc = "TODO", + desc = { + "blahblah", + "Their most important stats are: Willpower and Cunning", + "#GOLD#Stat modifiers:", + "#LIGHT_BLUE# * +0 Strength, +0 Dexterity, +0 Constitution", + "#LIGHT_BLUE# * +0 Magic, +5 Willpower, +4 Cunning", + "#GOLD#Life per level:#LIGHT_BLUE# -4 (*special*)", + }, + power_source = {psionic=true}, + stats = { str=0, wil=5, cun=4, }, + talents_types = { + ["psionic/feedback"]={true, 0.3}, + ["psionic/psychic-assault"]={true, 0.3}, + ["psionic/solipsism"]={true, 0.3}, + }, + talents = { + [ActorTalents.T_FEEDBACK] = 1, + [ActorTalents.T_PSYCHIC_LOBOTOMY] = 1, + [ActorTalents.T_SOLIPSISM] = 1, + }, + copy = { + max_life = 90, + resolvers.equip{ id=true, + {type="armor", subtype="cloth", name="linen robe", autoreq=true, ego_chance=-1000}, + {type="weapon", subtype="mindstar", name="mossy mindstar", autoreq=true, ego_chance=-1000}, + {type="weapon", subtype="mindstar", name="mossy mindstar", autoreq=true, ego_chance=-1000}, + }, + }, + copy_add = { + life_rating = -4, + }, +} diff --git a/game/modules/tome/data/general/events/mice-quest.lua b/game/modules/tome/data/general/events/mice-quest.lua new file mode 100644 index 0000000000..1ead996be4 --- /dev/null +++ b/game/modules/tome/data/general/events/mice-quest.lua @@ -0,0 +1,94 @@ +-- ToME - Tales of Maj'Eyal +-- Copyright (C) 2009, 2010, 2011, 2012 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 + +-- Find a random spot +local x, y = rng.range(1, level.map.w - 2), rng.range(1, level.map.h - 2) +local tries = 0 +while not game.state:canEventGrid(level, x, y) and tries < 100 do + x, y = rng.range(1, level.map.w - 2), rng.range(1, level.map.h - 2) + tries = tries + 1 +end +if tries >= 100 then return false end + +local id = "mouse-quest-"..game.turn + +local changer = function(id) + local npcs = mod.class.NPC:loadList{"/data/general/npcs/thieve.lua"} + local objects = mod.class.Object:loadList("/data/general/objects/objects.lua") + local terrains = mod.class.Grid:loadList("/data/general/grids/cave.lua") + local zone = mod.class.Zone.new(id, { + name = "Unknown cave", + level_range = {1, 1}, + level_scheme = "player", + max_level = 1, + actor_adjust_level = function(zone, level, e) return zone.base_level + e:getRankLevelAdjust() + level.level-1 + rng.range(-1,2) end, + width = 20, height = 20, + ambient_music = "Suspicion.ogg", + reload_lists = false, + persistent = "zone", + min_material_level = 1, + max_material_level = 1, + generator = { + map = { + class = "engine.generator.map.Cavern", + zoom = 4, + min_floor = 120, + floor = "CAVEFLOOR", + wall = "CAVEWALL", + up = "CAVE_LADDER_UP_WILDERNESS", + door = "CAVEFLOOR", + }, + actor = { + class = "mod.class.generator.actor.Random", + nb_npc = {14, 14}, + guardian = {random_elite={life_rating=function(v) return v * 1.5 + 4 end, nb_rares=3}}, + }, + object = { + class = "engine.generator.object.Random", + filters = {{type="gem"}}, + nb_object = {6, 9}, + }, + trap = { + class = "engine.generator.trap.Random", + nb_trap = {6, 9}, + }, + }, +-- levels = { [1] = { generator = { map = { up = "CAVEFLOOR", }, }, }, }, + npc_list = npcs, + grid_list = terrains, + object_list = objects, + trap_list = {}}, + }) + return zone +end + +local g = game.level.map(x, y, engine.Map.TERRAIN):cloneFull() +g.name = "" +g.display='>' g.color_r=0 g.color_g=0 g.color_b=255 g.notice = true +g.change_level=1 g.change_zone=id +g.add_displays = g.add_displays or {} +g.add_displays[#g.add_displays+1] = mod.class.Grid.new{image="terrain/pedestal_heart.png", z=5} +g.nice_tiler = nil +g.block_move = function(self) + game:changeLevel(1, self.real_change(self.change_zone), {temporary_zone_shift=true}) + return true +end +game.zone:addEntity(game.level, g, "terrain", x, y) + +return true diff --git a/game/modules/tome/data/gfx/shockbolt/terrain/pedestal_heart.png b/game/modules/tome/data/gfx/shockbolt/terrain/pedestal_heart.png new file mode 100644 index 0000000000000000000000000000000000000000..a3f0cf662ec0a9db89c1a5355443499aa3f52cbc GIT binary patch literal 7208 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 aC5Ul3T*H`x0(kX}M`2=UVNk2@lK6j83eM61 literal 0 HcmV?d00001 diff --git a/game/modules/tome/data/talents/misc/npcs.lua b/game/modules/tome/data/talents/misc/npcs.lua index ee35842b9c..2d8daebf62 100644 --- a/game/modules/tome/data/talents/misc/npcs.lua +++ b/game/modules/tome/data/talents/misc/npcs.lua @@ -1062,7 +1062,7 @@ newTalent{ local tg = self:getTalentTarget(t) local x, y = self:getTarget(tg) if not x or not y then return nil end - self:project(tg, x, y, DamageType.MIND, self:spellCrit(self:combatTalentMindDamage(t, 10, 340)), {type="mind"}) + self:project(tg, x, y, DamageType.MIND, self:mindCrit(self:combatTalentMindDamage(t, 10, 340)), {type="mind"}) game:playSoundNear(self, "talents/spell_generic") return true end, @@ -1112,7 +1112,7 @@ newTalent{ local tg = self:getTalentTarget(t) local x, y = self:getTarget(tg) if not x or not y then return nil end - self:project(tg, x, y, DamageType.MINDKNOCKBACK, self:spellCrit(self:combatTalentMindDamage(t, 10, 170)), {type="mind"}) + self:project(tg, x, y, DamageType.MINDKNOCKBACK, self:mindCrit(self:combatTalentMindDamage(t, 10, 170)), {type="mind"}) game:playSoundNear(self, "talents/spell_generic") return true end, diff --git a/game/modules/tome/data/talents/psionic/feedback.lua b/game/modules/tome/data/talents/psionic/feedback.lua new file mode 100644 index 0000000000..9c8f0a9624 --- /dev/null +++ b/game/modules/tome/data/talents/psionic/feedback.lua @@ -0,0 +1,221 @@ +-- ToME - Tales of Maj'Eyal +-- Copyright (C) 2009, 2010, 2011, 2012 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 + +-- TODO: Sounds and particles + +newTalent{ + name = "Feedback", + type = {"psionic/feedback", 1}, + points = 5, + require = psi_wil_req1, + cooldown = 10, + psi = 10, + tactical = { PSI = 2 }, + on_pre_use = function(self, t, silent) if self.psionic_feedback <= 0 then if not silent then game.logPlayer(self, "You have no feedback to power this talent.") end return false end return not self:hasEffect(self.EFF_REGENERATION) end, + getConversionRatio = function(self, t) return self:combatTalentMindDamage(t, 50, 150)/100 end, + on_learn = function(self, t) + if self:getTalentLevelRaw(t) == 1 then + if not self.psionic_feedback then + self.psionic_feedback = 0 + end + self.psionic_feedback_max = (self.psionic_feedback_max or 0) + 50 + end + return true + end, + on_unlearn = function(self, t) + if not self:knowTalent(t) then + self.psionic_feedback_max = self.psionic_feedback_max - 50 + if self.psionic_feedback_max <= 0 then + self.psionic_feedback_max = nil + self.psionic_feedback = nil + end + end + return true + end, + action = function(self, t) + local power = self.psionic_feedback * t.getConversionRatio(self, t) + self:setEffect(self.EFF_REGENERATION, 5, {power = self:mindCrit(power/5)}) + self.psionic_feedback = 0 + return true + end, + info = function(self, t) + local conversion = t.getConversionRatio(self, t) + return ([[You now store damage you take as psionic feedback. Activating this talent removes all stored feedback, converting %d%% of the stored energy into life regen over the next five turns. + Learning this talent will increase the amount of feedback you can store by 50 (first talent point only). + The conversion ratio will scale with your mindpower.]]):format(conversion * 100) + end, +} + +newTalent{ + name = "Discharge", + type = {"psionic/feedback", 2}, + points = 5, + require = psi_wil_req2, + cooldown = 10, + psi = 10, + tactical = { DISABLE = 2}, + range = 0, + direct_hit = true, + requires_target = true, + getConversionRatio = function(self, t) return 100 - math.min(50, self:combatTalentMindDamage(t, 0, 50)) end, + getDuration = function(self, t) + local power = (self.psionic_feedback or 0) / t.getConversionRatio(self, t) + local duration = 1 + math.floor(power) + return duration + end, + radius = function(self, t) return math.ceil(self:getTalentLevel(t)) end, + target = function(self, t) + return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), selffire=false} + end, + on_pre_use = function(self, t, silent) if self.psionic_feedback <= 0 then if not silent then game.logPlayer(self, "You have no feedback to power this talent.") end return false end return true end, + on_learn = function(self, t) + if self:getTalentLevelRaw(t) == 1 then + if not self.psionic_feedback then + self.psionic_feedback = 0 + end + self.psionic_feedback_max = (self.psionic_feedback_max or 0) + 50 + end + return true + end, + on_unlearn = function(self, t) + if not self:knowTalent(t) then + self.psionic_feedback_max = self.psionic_feedback_max - 50 + if self.psionic_feedback_max <= 0 then + self.psionic_feedback_max = nil + self.psionic_feedback = nil + end + end + return true + end, + action = function(self, t) + local tg = self:getTalentTarget(t) + + self:project(tg, self.x, self.y, function(px, py) + local target = game.level.map(px, py, engine.Map.ACTOR) + if not target then return end + + if target:canBe("stun") then + target:setEffect(target.EFF_DAZED, math.floor(self:mindCrit(t.getDuration(self, t))), {apply_power=self:combatMindpower()}) + else + game.logSeen(target, "%s resists the daze!", target.name:capitalize()) + end + game.level.map:particleEmitter(px, py, 1, "light") + end) + + self.psionic_feedback = 0 + + return true + end, + info = function(self, t) + local conversion = t.getConversionRatio(self, t) + local radius = self:getTalentRadius(t) + return ([[Activate to discharge all stored feedback, dazing creatures in a radius of %d for 1 turn. The duration of the effect will be increased by 1 for every %d feedback you have stored. + Learning this talent will increase the amount of feedback you can store by 50 (first talent point only). + The conversion ratio will scale with your mindpower.]]):format(radius, conversion) + end, +} + +newTalent{ + name = "Resonance Shield", + type = {"psionic/feedback", 3}, + points = 5, + require = psi_wil_req3, + cooldown = 15, + psi = 10, + tactical = { DEFEND = 2, ATTACK = {MIND = 2}}, + on_pre_use = function(self, t, silent) if self.psionic_feedback <= 0 then if not silent then game.logPlayer(self, "You have no feedback to power this talent.") end return false end return true end, + getConversionRatio = function(self, t) return self:combatTalentMindDamage(t, 50, 150) / 100 end, + getDamage = function(self, t) return self:combatTalentMindDamage(t, 10, 50) end, + on_learn = function(self, t) + if self:getTalentLevelRaw(t) == 1 then + if not self.psionic_feedback then + self.psionic_feedback = 0 + end + self.psionic_feedback_max = (self.psionic_feedback_max or 0) + 50 + end + return true + end, + on_unlearn = function(self, t) + if not self:knowTalent(t) then + self.psionic_feedback_max = self.psionic_feedback_max - 50 + if self.psionic_feedback_max <= 0 then + self.psionic_feedback_max = nil + self.psionic_feedback = nil + end + end + return true + end, + action = function(self, t) + local power = (self.psionic_feedback or 0) * t.getConversionRatio(self, t) + self:setEffect(self.EFF_RESONANCE_SHIELD, 10, {power = self:mindCrit(power), dam = self:mindCrit(t.getDamage(self, t))}) + self.psionic_feedback = 0 + return true + end, + info = function(self, t) + local conversion = t.getConversionRatio(self, t) + local damage = t.getDamage(self, t) + return ([[Activate to remove all stored feedback, converting %d%% of the stored energy into a resonance field that will absorb 50%% of all damage you take and inflict %0.2f mind damage to melee attackers. + Learning this talent will increase the amount of feedback you can store by 50 (first talent point only). + The conversion ratio will scale with your mindpower and the effect lasts up to ten turns.]]):format(conversion * 100, damDesc(self, DamageType.MIND, damage)) + end, +} + +newTalent{ + name = "Feedback Loop", + type = {"psionic/feedback", 4}, + points = 5, + require = psi_wil_req4, + cooldown = 24, + psi = 10, + tactical = { FEEDBACK = 2 }, + getConversionRatio = function(self, t) return math.min(100, self:combatTalentMindDamage(t, 20, 100))/100 end, + getFeedbackIncrease = function(self, t) return (self.psionic_feedback_max or 0) * t.getConversionRatio(self, t) end, + no_energy = true, + on_learn = function(self, t) + if self:getTalentLevelRaw(t) == 1 then + if not self.psionic_feedback then + self.psionic_feedback = 0 + end + self.psionic_feedback_max = (self.psionic_feedback_max or 0) + 50 + end + return true + end, + on_unlearn = function(self, t) + if not self:knowTalent(t) then + self.psionic_feedback_max = self.psionic_feedback_max - 50 + if self.psionic_feedback_max <= 0 then + self.psionic_feedback_max = nil + self.psionic_feedback = nil + end + end + return true + end, + action = function(self, t) + self.psionic_feedback = math.min(self.psionic_feedback_max or 0, (self.psionic_feedback or 0) + t.getFeedbackIncrease(self, t)) + return true + end, + info = function(self, t) + local conversion = t.getConversionRatio(self, t) + local feedback = t.getFeedbackIncrease(self, t) + return ([[Activate to instantly convert psi (the cost of the talent) into %d%% of your maximum feedback (currently %d). + Learning this talent will increase the amount of feedback you can store by 50 (first talent point only). + The feedback gain will scale with your mindpower. + This talent takes no time to use.]]):format(conversion * 100, feedback) + end, +} \ No newline at end of file diff --git a/game/modules/tome/data/talents/psionic/psionic.lua b/game/modules/tome/data/talents/psionic/psionic.lua index 5792ff0ce7..1f03e7bcd1 100644 --- a/game/modules/tome/data/talents/psionic/psionic.lua +++ b/game/modules/tome/data/talents/psionic/psionic.lua @@ -37,7 +37,9 @@ newTalentType{ allow_random=true, type="psionic/brainstorm", name = "brainstorm" -- Secret Project... -- Solipsist Talent Trees newTalentType{ allow_random=true, type="psionic/psychic-assault", name = "psychic assault", description = "Directly attack your opponents minds." } +newTalentType{ allow_random=true, type="psionic/solipsism", name = "solipsism", description = "Nothing exists outside the minds ability to perceive it." } -- Generic Solipsist Trees +newTalentType{ allow_random=true, type="psionic/feedback", generic = true, name = "feedback", description = "Store and discharge psychic feedback." } newTalentType{ allow_random=true, type="psionic/possession", name = "possession", description = "You have learnt to shed away your body, allowing you to possess any other." } @@ -169,7 +171,10 @@ load("/data/talents/psionic/psi-archery.lua") load("/data/talents/psionic/grip.lua") -- Solipsist +load("/data/talents/psionic/feedback.lua") load("/data/talents/psionic/psychic-assault.lua") +load("/data/talents/psionic/solipsism.lua") + load("/data/talents/psionic/possession.lua") diff --git a/game/modules/tome/data/talents/psionic/solipsism.lua b/game/modules/tome/data/talents/psionic/solipsism.lua new file mode 100644 index 0000000000..e1c2f35601 --- /dev/null +++ b/game/modules/tome/data/talents/psionic/solipsism.lua @@ -0,0 +1,151 @@ +-- ToME - Tales of Maj'Eyal +-- Copyright (C) 2009, 2010, 2011, 2012 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 + + +-- TODO: Sounds and particles + +newTalent{ + name = "Solipsism", + type = {"psionic/solipsism", 1}, + points = 5, + require = psi_wil_req1, + mode = "passive", + no_unlearn_last = true, + damageToPsi = function(self, t) return math.min(self:getTalentLevel(t) * 0.15, 1) end, + on_learn = function(self, t) + self:incMaxPsi(10) + if self:getTalentLevelRaw(t) == 1 then + self.life_rating = 0 + self.psi_rating = self.psi_rating + 10 + self.solipsism_threshold = (self.solipsism_threshold or 0) + 0.2 + end + return true + end, + on_unlearn = function(self, t) + self:incMaxPsi(-10) + if not self:knowTalent(t) then + self.solipsism_threshold = self.solipsism_threshold - 0.2 + end + return true + end, + info = function(self, t) + local damage_to_psi = t.damageToPsi(self, t) + return ([[You believe that your mind is the center of everything. Permanently increases the amount of psi you gain per level by 10 and reduces your life rating (affects life at level up) to 0 (one time only adjustment). + You also have learned to overcome physical damage with your mind alone and convert %d%% of all damage into psi damage. + Increases your solipsism threshold by 20%% (first point only), reducing global speed if your Psi falls below the threshold (currently %d%%). + Each talent point invested will also increase your max Psi by 10.]]):format(damage_to_psi * 100, self.solipsism_threshold * 100) + end, +} + +newTalent{ + name = "Balance", + type = {"psionic/solipsism", 2}, + points = 5, + require = psi_wil_req2, + mode = "passive", + getBalanceRatio = function(self, t) return math.min(self:getTalentLevel(t) * 0.15, 1) end, + on_learn = function(self, t) + self:incMaxPsi(10) + if self:getTalentLevelRaw(t) == 1 then + self.solipsism_threshold = (self.solipsism_threshold or 0) + 0.1 + end + return true + end, + on_unlearn = function(self, t) + self:incMaxPsi(-10) + if not self:knowTalent(t) then + self.solipsism_threshold = self.solipsism_threshold - 0.1 + end + return true + end, + info = function(self, t) + local ratio = t.getBalanceRatio(self, t) * 100 + return ([[%d%% of your healing and life regen now recovers Psi instead of life. You now use %d%% of your physical save value and %d%% of your mental save value for physical saving throws. + Increases your solipsism threshold by 10%% (first point only), reducing global speed if your Psi falls below the threshold (currently %d%%). + Each talent point invested will also increase your max Psi by 10.]]):format(ratio, ratio, ratio, self.solipsism_threshold * 100) + end, +} + +newTalent{ + name = "Clarity", + type = {"psionic/solipsism", 3}, + points = 5, + require = psi_wil_req3, + mode = "passive", + getClarityThreshold = function(self, t) return math.max(0.5, 1 - self:getTalentLevelRaw(t) / 10) end, + on_learn = function(self, t) + self:incMaxPsi(10) + self.solipsism_threshold = (self.solipsism_threshold or 0) + 0.1 + if self:getTalentLevelRaw(t) == 1 then + self.clarity_threshold = t.getClarityThreshold(self, t) + end + return true + end, + on_unlearn = function(self, t) + self:incMaxPsi(-10) + if not self:knowTalent(t) then + self.solipsism_threshold = self.solipsism_threshold - 0.1 + self.clarity_threshold = nil + else + self.clarity_threshold = t.getClarityThreshold(self, t) + end + return true + end, + info = function(self, t) + local threshold = t.getClarityThreshold(self, t) + return ([[For every percent that your Psi pool exceeds %d%% you gain 1%% global speed. + Increases your solipsism threshold by 10%% (first point only), reducing global speed if your Psi falls below the threshold (currently %d%%). + Each talent point invested will also increase your max Psi by 10.]]):format(threshold * 100, self.solipsism_threshold * 100) + end, +} + +newTalent{ + name = "Dismissal", + type = {"psionic/solipsism", 4}, + points = 5, + require = psi_wil_req4, + cooldown = 12, + psi = 20, + tactical = { DEFEND = 2}, + getDuration = function(self, t) return 1 + math.ceil(self:getTalentLevel(t)) end, + on_learn = function(self, t) + self:incMaxPsi(10) + if self:getTalentLevelRaw(t) == 1 then + self.solipsism_threshold = (self.solipsism_threshold or 0) + 0.1 + end + return true + end, + on_unlearn = function(self, t) + self:incMaxPsi(-10) + if not self:knowTalent(t) then + self.solipsism_threshold = self.solipsism_threshold - 0.1 + end + return true + end, + action = function(self, t) + self:setEffect(self.EFF_DISMISSAL, t.getDuration(self, t), {}) + return true + end, + info = function(self, t) + local duration = t.getDuration(self, t) + return ([[You dismiss 'reality' as merely a figment of your mind. For the next %d turns you are immune to all damage and ignore new status effects. Performing any action other then movement will reaffirm your belief in 'reality' and end the effect. + Increases your solipsism threshold by 10%% (first point only), reducing global speed if your Psi falls below the threshold (currently %d%%). + Each talent point invested will also increase your max Psi by 10.]]):format(duration, self.solipsism_threshold * 100) + end, +} diff --git a/game/modules/tome/data/timed_effects/mental.lua b/game/modules/tome/data/timed_effects/mental.lua index 2fe15cffed..f17a324a8d 100644 --- a/game/modules/tome/data/timed_effects/mental.lua +++ b/game/modules/tome/data/timed_effects/mental.lua @@ -2317,3 +2317,46 @@ newEffect{ self.mental_negative_status_effect_immune = nil end, } + + +newEffect{ + name = "FEEDBACK", image = "talents/feedback.png", + desc = "Feedback", + long_desc = function(self, eff) return ("The target is converting feedback into Psi regen at the rate of %0.2f per turn."):format(eff.power) end, + type = "mental", + subtype = { psionic=true }, + status = "beneficial", + parameters = { power=1 }, + on_gain = function(self, err) return "#target# is recovering Psi quickly.", "+Feedback" end, + on_lose = function(self, err) return "#target#'s Psi recover has returned to normal.", "-Feedback" end, + activate = function(self, eff) + eff.tid = self:addTemporaryValue("psi_regen", eff.power) + end, + deactivate = function(self, eff) + self:removeTemporaryValue("psi_regen", eff.tid) + end, +} + +newEffect{ + name = "RESONANCE_SHIELD", image = "talents/resonance_shield.png", + desc = "Resonance Shield", + long_desc = function(self, eff) return ("The target is surrounded by a psychic shield, absorbing 50%% of all damage (up to %d/%d). Additionally melee attackers will suffer %0.2f mind damage on contact."):format(self.resonance_shield_absorb, eff.power, eff.dam) end, + type = "mental", + subtype = { psionic=true, shield=true }, + status = "beneficial", + parameters = { power=100, dam = 1 }, + on_gain = function(self, err) return "A psychic shield forms around #target#.", "+Resonance Shield" end, + on_lose = function(self, err) return "The psychic shield around #target# crumbles.", "-Resonance Shield" end, + activate = function(self, eff) + self.resonance_shield_absorb = eff.power + eff.particle = self:addParticles(Particles.new("damage_shield", 1)) + eff.sid = self:addTemporaryValue("resonance_shield", eff.power) + eff.did = self:addTemporaryValue("on_melee_hit", {[DamageType.MIND]=eff.dam}) + end, + deactivate = function(self, eff) + self.resonance_shield_absorb = nil + self:removeParticles(eff.particle) + self:removeTemporaryValue("resonance_shield", eff.sid) + self:removeTemporaryValue("on_melee_hit", eff.did) + end, +} diff --git a/game/modules/tome/data/timed_effects/other.lua b/game/modules/tome/data/timed_effects/other.lua index d5d5167f5d..78c8404bfd 100644 --- a/game/modules/tome/data/timed_effects/other.lua +++ b/game/modules/tome/data/timed_effects/other.lua @@ -1541,3 +1541,59 @@ newEffect{ self:removeTemporaryValue("invulnerable", eff.tmpid) end, } + +newEffect{ + name = "SOLIPSISM", image = "talents/solipsism.png", + desc = "Solipsism", + long_desc = function(self, eff) return ("This creature has fallen into a solipsistic state and is caught up in its own thoughts (-%d%% global speed)."):format(eff.power * 100) end, + type = "other", + subtype = { psionic=true }, + status = "detrimental", + decrease = 0, + no_stop_enter_worlmap = true, no_stop_resting = true, + parameters = { }, + activate = function(self, eff) + eff.tmpid = self:addTemporaryValue("global_speed_add", -eff.power) + end, + deactivate = function(self, eff) + self:removeTemporaryValue("global_speed_add", eff.tmpid) + end, +} + +newEffect{ + name = "CLARITY", image = "talents/clarity.png", + desc = "Clarity", + long_desc = function(self, eff) return ("The creature has found a state of clarity (+%d%% global speed)."):format(eff.power * 100) end, + type = "other", + subtype = { psionic=true }, + status = "beneficial", + decrease = 0, + no_stop_enter_worlmap = true, no_stop_resting = true, + parameters = { }, + activate = function(self, eff) + eff.tmpid = self:addTemporaryValue("global_speed_add", eff.power) + end, + deactivate = function(self, eff) + self:removeTemporaryValue("global_speed_add", eff.tmpid) + end, +} + +newEffect{ + name = "DISMISSAL", image = "talents/dismissal.png", + desc = "Dismissal", + long_desc = function(self, eff) return "The target has dismissed reality. For the duration it is immune to new status effects and all damage." end, + type = "other", + subtype = { psionic=true }, + status = "beneficial", + parameters = {}, + on_gain = function(self, err) return "#Target# dismisses reality!", "+Dismissal" end, + on_lose = function(self, err) return "#Target# reaffirms it's belief in reality.", "-Dismissal" end, + activate = function(self, eff) + eff.iid = self:addTemporaryValue("invulnerable", 1) + eff.imid = self:addTemporaryValue("status_effect_immune", 1) + end, + deactivate = function(self, eff) + self:removeTemporaryValue("invulnerable", eff.iid) + self:removeTemporaryValue("status_effect_immune", eff.imid) + end, +} \ No newline at end of file -- GitLab