From 16f1ac377665d00a593c649a6b0f8c0355be686a Mon Sep 17 00:00:00 2001 From: DarkGod <darkgod@net-core.org> Date: Wed, 12 Mar 2014 10:12:08 +0100 Subject: [PATCH] Big rework of the Brawler's Grappling tree --- game/modules/tome/class/Player.lua | 4 +- game/modules/tome/class/interface/Combat.lua | 30 +- .../data/general/objects/egos/light-armor.lua | 48 ++- .../data/general/objects/world-artifacts.lua | 4 +- .../tome/data/talents/cursed/cursed-aura.lua | 4 +- .../tome/data/talents/cursed/endless-hunt.lua | 4 +- game/modules/tome/data/talents/misc/npcs.lua | 58 +++ .../talents/techniques/finishing-moves.lua | 3 +- .../data/talents/techniques/grappling.lua | 337 +++++++++--------- .../data/talents/techniques/techniques.lua | 2 +- .../tome/data/timed_effects/physical.lua | 41 ++- 11 files changed, 331 insertions(+), 204 deletions(-) diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua index 6bc3231d79..b299fb5b71 100644 --- a/game/modules/tome/class/Player.lua +++ b/game/modules/tome/class/Player.lua @@ -575,8 +575,8 @@ function _M:playerFOV() -- For each entity, generate lite local uid, e = next(game.level.entities) while uid do - if e ~= self and e.lite and e.lite > 0 and e.computeFOV then - e:computeFOV(e.lite, "block_sight", function(x, y, dx, dy, sqdist) game.level.map:applyExtraLite(x, y, fovdist[sqdist]) end, true, true) + if e ~= self and ((e.lite and e.lite > 0) or (e.radiance_aura and e.radiance_aura > 0)) and e.computeFOV then + e:computeFOV(math.max(e.lite or 0, e.radiance_aura or 0), "block_sight", function(x, y, dx, dy, sqdist) game.level.map:applyExtraLite(x, y, fovdist[sqdist]) end, true, true) end uid, e = next(game.level.entities, uid) end diff --git a/game/modules/tome/class/interface/Combat.lua b/game/modules/tome/class/interface/Combat.lua index 836f0ae232..3785bc27d2 100644 --- a/game/modules/tome/class/interface/Combat.lua +++ b/game/modules/tome/class/interface/Combat.lua @@ -2251,26 +2251,36 @@ end -- Starts the grapple function _M:startGrapple(target) -- pulls boosted grapple effect from the clinch talent if known + local grappledParam = {src = self, apply_power = 1, silence = 0, power = 1, slow = 0, reduction = 0} + local grappleParam = {sharePct = 0, drain = 0, trgt = target } + local duration = 5 if self:knowTalent(self.T_CLINCH) then local t = self:getTalentFromId(self.T_CLINCH) - power = t.getPower(self, t) + if self:knowTalent(self.T_CRUSHING_HOLD) then + local t2 = self:getTalentFromId(self.T_CRUSHING_HOLD) + grappledParam = t2.getBonusEffects(self, t2) -- get the 4 bonus parameters first + end + local power = self:physicalCrit(t.getPower(self, t), nil, target, self:combatAttack(), target:combatDefense()) + grappledParam["power"] = power -- damage/turn set by Clinch duration = t.getDuration(self, t) - hitbonus = self:getTalentLevel(t)/2 - else - power = 5 - duration = 4 - hitbonus = 0 + + grappleParam["drain"] = t.getDrain(self, t) -- stamina/turn set by Clinch + grappleParam["sharePct"] = t.getSharePct(self, t) -- damage shared with grappled set by Clinch + end + -- oh for the love of god why didn't I rewrite this entire structure + grappledParam["src"] = self + grappledParam["apply_power"] = self:combatPhysicalpower() -- Breaks the grapple before reapplying if self:hasEffect(self.EFF_GRAPPLING) then self:removeEffect(self.EFF_GRAPPLING, true) - target:setEffect(target.EFF_GRAPPLED, duration, {src=self, power=power}, true) - self:setEffect(self.EFF_GRAPPLING, duration, {trgt=target}, true) + target:setEffect(target.EFF_GRAPPLED, duration, grappledParam, true) + self:setEffect(self.EFF_GRAPPLING, duration, grappleParam, true) return true elseif target:canBe("pin") then - target:setEffect(target.EFF_GRAPPLED, duration, {src=self, power=power, apply_power=self:combatPhysicalpower()}) + target:setEffect(target.EFF_GRAPPLED, duration, grappledParam) target:crossTierEffect(target.EFF_GRAPPLED, self:combatPhysicalpower()) - self:setEffect(self.EFF_GRAPPLING, duration, {trgt=target}) + self:setEffect(self.EFF_GRAPPLING, duration, grappleParam) return true else game.logSeen(target, "%s resists the grapple!", target.name:capitalize()) diff --git a/game/modules/tome/data/general/objects/egos/light-armor.lua b/game/modules/tome/data/general/objects/egos/light-armor.lua index df797062e0..529a85d683 100644 --- a/game/modules/tome/data/general/objects/egos/light-armor.lua +++ b/game/modules/tome/data/general/objects/egos/light-armor.lua @@ -32,6 +32,7 @@ newEntity{ rarity = 12, cost = 14, wielder = { + healing_factor = resolvers.mbonus_material(20, 10, function(e, v) v=v/100 return 0, v end), -- copied from robe.lua life_regen = resolvers.mbonus_material(120, 30, function(e, v) v=v/10 return 0, v end), }, } @@ -45,12 +46,13 @@ newEntity{ rarity = 22, cost = 35, wielder = { - combat_def = resolvers.mbonus_material(8, 2), - combat_def_ranged = resolvers.mbonus_material(8, 2), - movement_speed = 0.1, + combat_def = resolvers.mbonus_material(14, 2), + combat_def_ranged = resolvers.mbonus_material(14, 2), + movement_speed = 0.2, inc_stats = { [Stats.STAT_DEX] = resolvers.mbonus_material(3, 2), }, }, } + newEntity{ power_source = {technique=true}, name = "marauder's ", prefix=true, instant_resolve=true, @@ -109,15 +111,13 @@ newEntity{ power_source = {technique=true}, name = " of the wind", suffix=true, instant_resolve=true, keywords = {wind=true}, - level_range = {40, 50}, + level_range = {30, 50}, greater_ego = 1, rarity = 30, cost = 80, resolvers.charmt(Talents.T_SECOND_WIND, {3,4,5}, 35), wielder = { - max_life = resolvers.mbonus_material(60, 40, function(e, v) return 0, -v end), - combat_armor = resolvers.mbonus_material(7, 3, function(e, v) return 0, -v end), - + cancel_damage_chance = resolvers.mbonus_material(8, 2), combat_physcrit = resolvers.mbonus_material(7, 3), combat_apr = resolvers.mbonus_material(15, 5), combat_def = resolvers.mbonus_material(10, 5), @@ -168,3 +168,37 @@ newEntity{ }, } +newEntity{ + power_source = {technique=true}, + name = " of alacrity", suffix=true, instant_resolve=true, + keywords = {alacrity=true}, + level_range = {40, 50}, + greater_ego = 1, + rarity = 30, + cost = 80, + wielder = { + combat_physspeed = 0.15, + combat_mindspeed = 0.15, + combat_spellspeed = 0.15, + }, +} + +-- you are so going to veto this ^^; +newEntity{ + power_source = {technique=true, arcane=true, nature=true}, + name = " of the hero ", suffix=true, instant_resolve=true, + keywords = {hero=true}, + level_range = {25, 50}, + greater_ego = 1, + rarity = 30, + cost = 35, + wielder = { + inc_stats = { + [Stats.STAT_STR] = resolvers.mbonus_material(4, 3), + [Stats.STAT_DEX] = resolvers.mbonus_material(4, 3), + [Stats.STAT_WIL] = resolvers.mbonus_material(4, 3), + [Stats.STAT_CUN] = resolvers.mbonus_material(4, 3), + [Stats.STAT_MAG] = resolvers.mbonus_material(4, 3), + }, + }, +} diff --git a/game/modules/tome/data/general/objects/world-artifacts.lua b/game/modules/tome/data/general/objects/world-artifacts.lua index 1b65ba3c75..7ac5c41d54 100644 --- a/game/modules/tome/data/general/objects/world-artifacts.lua +++ b/game/modules/tome/data/general/objects/world-artifacts.lua @@ -41,9 +41,9 @@ newEntity{ base = "BASE_GEM", identified = false, material_level = 4, wielder = { - inc_stats = {[Stats.STAT_DEX] = 10, [Stats.STAT_CUN] = 10 }, + inc_stats = {[Stats.STAT_DEX] = 8, [Stats.STAT_CUN] = 8 }, inc_damage = {[DamageType.LIGHTNING] = 20 }, - cancel_damage_chance = 10, -- add to tooltip + cancel_damage_chance = 8, -- add to tooltip damage_affinity={ [DamageType.LIGHTNING] = 20, }, diff --git a/game/modules/tome/data/talents/cursed/cursed-aura.lua b/game/modules/tome/data/talents/cursed/cursed-aura.lua index be0a924a3e..71f12438b7 100644 --- a/game/modules/tome/data/talents/cursed/cursed-aura.lua +++ b/game/modules/tome/data/talents/cursed/cursed-aura.lua @@ -435,7 +435,7 @@ newTalent{ no_breath = 1, disarm_immune = 1, never_move = 1, - no_drops = true, -- remove to drop the weapon + --no_drops = true, -- remove to drop the weapon resolvers.talents{ [Talents.T_WEAPON_COMBAT]={base=1, every=10}, @@ -504,6 +504,6 @@ newTalent{ local duration = t.getDuration(self, t) local attackSpeed = 100/t.getAttackSpeed(self, t) - return ([[Instill a part of your living curse into a weapon in your inventory, and toss it nearby. This nearly impervious sentry will attack all nearby enemies for %d turns. When the curse ends, the weapon will crumble to dust, worn through by your hatred. Attack Speed: %d%%]]):format(duration, attackSpeed) + return ([[Instill a part of your living curse into a weapon in your inventory, and toss it nearby. This nearly impervious sentry will attack all nearby enemies for %d turns. When the curse ends, the weapon will drop to the ground. Attack Speed: %d%%]]):format(duration, attackSpeed) end, } diff --git a/game/modules/tome/data/talents/cursed/endless-hunt.lua b/game/modules/tome/data/talents/cursed/endless-hunt.lua index 1fd54aea2d..b2ded74824 100644 --- a/game/modules/tome/data/talents/cursed/endless-hunt.lua +++ b/game/modules/tome/data/talents/cursed/endless-hunt.lua @@ -151,7 +151,7 @@ newTalent{ return getHateMultiplier(self, 0.35, 0.67, false, hate) end, getTargetDamageChange = function(self, t) - return -self:combatLimit(self:combatTalentStatDamage(t, "wil", 0.7, 0.9), 1, 0, 0, 0.75, 0.87) -- Limit < 100% + return -self:combatLimit(self:combatTalentStatDamage(t, "wil", 0.7, 0.9), 1, 0, 0, 0.75, 0.87)*100 -- Limit < 100% end, getDuration = function(self, t) return 2 @@ -196,7 +196,7 @@ newTalent{ local targetDamageChange = t.getTargetDamageChange(self, t) local duration = t.getDuration(self, t) return ([[Harass your stalked victim with two quick attacks for %d%% (at 0 Hate) to %d%% (at 100+ Hate) damage each. Each attack that scores a hit disrupts one talent, rune or infusion for %d turns. Your opponent will be unnerved by the attacks, reducing the damage they deal by %d%% for %d turns. - Damage reduction increases with the Willpower stat.]]):format(t.getDamageMultiplier(self, t, 0) * 100, t.getDamageMultiplier(self, t, 100) * 100, cooldownDuration, -targetDamageChange * 100, duration) + Damage reduction increases with the Willpower stat.]]):format(t.getDamageMultiplier(self, t, 0) * 100, t.getDamageMultiplier(self, t, 100) * 100, cooldownDuration, -targetDamageChange, duration) end, } diff --git a/game/modules/tome/data/talents/misc/npcs.lua b/game/modules/tome/data/talents/misc/npcs.lua index 53458c08d7..853e43cbb6 100644 --- a/game/modules/tome/data/talents/misc/npcs.lua +++ b/game/modules/tome/data/talents/misc/npcs.lua @@ -2174,3 +2174,61 @@ newTalent{ format(defense, saves) end, } + +newTalent{ +name = "Maim", +type = {"technique/other", 1}, +points = 5, +random_ego = "attack", +cooldown = 12, +stamina = 10, +tactical = { ATTACK = { PHYSICAL = 2 }, DISABLE = 2 }, +requires_target = true, +getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end, +getDamage = function(self, t) return self:combatTalentPhysicalDamage(t, 10, 100) * getUnarmedTrainingBonus(self) end, +getMaim = function(self, t) return self:combatTalentPhysicalDamage(t, 5, 30) end, +-- Learn the appropriate stance +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 core.fov.distance(self.x, self.y, x, y) > 1 then return nil end + +local grappled = false + +-- breaks active grapples if the target is not grappled +if target:isGrappled(self) then +grappled = true +else +self:breakGrapples() +end + +-- end the talent without effect if the target is to big +if self:grappleSizeCheck(target) then +return true +end + +-- start the grapple; this will automatically hit and reapply the grapple if we're already grappling the target +local hit = self:startGrapple (target) +-- deal damage and maim if appropriate +if hit then +if grappled then +self:project(target, x, y, DamageType.PHYSICAL, self:physicalCrit(t.getDamage(self, t), nil, target, self:combatAttack(), target:combatDefense())) +target:setEffect(target.EFF_MAIMED, t.getDuration(self, t), {power=t.getMaim(self, t)}) +else +self:project(target, x, y, DamageType.PHYSICAL, self:physicalCrit(t.getDamage(self, t), nil, target, self:combatAttack(), target:combatDefense())) +end +end + +return true +end, +info = function(self, t) +local duration = t.getDuration(self, t) +local damage = t.getDamage(self, t) +local maim = t.getMaim(self, t) +return ([[Grapples the target and inflicts %0.2f physical damage. If the target is already grappled, the target will be maimed as well, reducing damage by %d and global speed by 30%% for %d turns. +The grapple effects will be based off your grapple talent, if you have it, and the damage will scale with your Physical Power.]]) +:format(damDesc(self, DamageType.PHYSICAL, (damage)), maim, duration) +end, +} diff --git a/game/modules/tome/data/talents/techniques/finishing-moves.lua b/game/modules/tome/data/talents/techniques/finishing-moves.lua index f1e1d10479..6d4995058b 100644 --- a/game/modules/tome/data/talents/techniques/finishing-moves.lua +++ b/game/modules/tome/data/talents/techniques/finishing-moves.lua @@ -101,7 +101,8 @@ newTalent{ if hit then local tg = {type="ball", range=1, radius=self:getTalentRadius(t), selffire=false, talent=t} - local damage = t.getAreaDamage(self, t) * (0.25 + (self:getCombo(combo) /5)) + local damage = self:physicalCrit(t.getAreaDamage(self, t) * (0.25 + (self:getCombo(combo) /5)), nil, target, self:combatAttack(), target:combatDefense()) + --local damage = self:physicalCrit(t.getAreaDamage(self, t) * (0.25 + (self:getCombo(combo) /5))) self:project(tg, x, y, DamageType.PHYSICAL, damage) game.level.map:particleEmitter(x, y, tg.radius, "ball_earth", {radius=tg.radius}) game:playSoundNear(self, "talents/breath") diff --git a/game/modules/tome/data/talents/techniques/grappling.lua b/game/modules/tome/data/talents/techniques/grappling.lua index dae486f58c..b82f8719e6 100644 --- a/game/modules/tome/data/talents/techniques/grappling.lua +++ b/game/modules/tome/data/talents/techniques/grappling.lua @@ -16,7 +16,7 @@ -- -- Nicolas Casalini "DarkGod" -- darkgod@te4.org - +-- Obsolete but undeleted incase something uses it newTalent{ name = "Grappling Stance", type = {"technique/unarmed-other", 1}, @@ -62,20 +62,11 @@ newTalent{ stamina = 5, tactical = { ATTACK = 2, DISABLE = 2 }, requires_target = true, - getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 5, 8)) end, - getPower = function(self, t) return self:combatTalentPhysicalDamage(t, 5, 25) end, - getDrain = function(self, t) return 6 - math.max(1, self:getTalentLevelRaw(t) or 0) end, - -- Learn the appropriate stance - on_learn = function(self, t) - if not self:knowTalent(self.T_GRAPPLING_STANCE) then - self:learnTalent(self.T_GRAPPLING_STANCE, true, nil, {no_unlearn=true}) - end - end, - on_unlearn = function(self, t) - if not self:knowTalent(t) then - self:unlearnTalent(self.T_GRAPPLING_STANCE) - end - end, + getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end, + getPower = function(self, t) return self:combatTalentPhysicalDamage(t, 20, 60) end, + getDrain = function(self, t) return 6 end, + getSharePct = function(self, t) return math.min(0.35, self:combatTalentScale(t, 0.05, 0.25)) end, + getDamage = function(self, t) return 1 end, action = function(self, t) local tg = {type="hit", range=self:getTalentRange(t)} @@ -85,11 +76,6 @@ newTalent{ local grappled = false - -- force stance change - if target and not self:isTalentActive(self.T_GRAPPLING_STANCE) then - self:forceUseTalent(self.T_GRAPPLING_STANCE, {ignore_energy=true, ignore_cd = true}) - end - -- breaks active grapples if the target is not grappled if target:isGrappled(self) then grappled = true @@ -103,196 +89,219 @@ newTalent{ end -- start the grapple; this will automatically hit and reapply the grapple if we're already grappling the target - local hit = self:startGrapple(target) - - local duration = t.getDuration(self, t) - - -- do crushing hold or strangle if we're already grappling the target - if hit and self:knowTalent(self.T_CRUSHING_HOLD) then - local t = self:getTalentFromId(self.T_CRUSHING_HOLD) - if grappled and not target:attr("no_breath") and not target:attr("undead") and target:canBe("silence") then - target:setEffect(target.EFF_STRANGLE_HOLD, duration, {src=self, power=t.getDamage(self, t) * 1.5}) - else - target:setEffect(target.EFF_CRUSHING_HOLD, duration, {src=self, power=t.getDamage(self, t)}) - end - end - + local hit self:attackTarget(target, nil, t.getDamage(self, t), true) + local hit2 = self:startGrapple(target) + return true end, info = function(self, t) local duration = t.getDuration(self, t) local power = t.getPower(self, t) local drain = t.getDrain(self, t) - return ([[Grapples a target up to one size category larger then yourself for %d turns. A grappled opponent will be unable to move, and its Accuracy and Defense will be reduced by %d. Any movement from the target or you will break the grapple. Maintaining a grapple drains %d stamina per turn. - You may only grapple a single target at a time, and using any targeted unarmed talent on a target that you're not grappling will break the grapple. - The grapple Accuracy and Defense reduction, as well as the grapple success chance, will scale with your Physical Power. - Performing this action will switch your stance to Grappling Stance.]]) - :format(duration, power, drain) + local share = t.getSharePct(self, t)*100 + local damage = t.getDamage(self, t)*100 + return ([[Make a melee attack for %d%% damage and then attempt to grapple a target up to one size category larger then yourself for %d turns. A grappled opponent will be unable to move, take %d damage each turn, and %d%% of the damage you receive from any source will be redirected to them. Any movement from the target or you will break the grapple. Maintaining a grapple drains %d stamina per turn. + You may only grapple a single target at a time, and using any targeted unarmed talent on a target that you're not grappling will break the grapple.]]) + :format(damage, duration, power, share, drain) end, } +-- I tried to keep this relatively consistent with the existing Grappling code structure, but it wound up pretty awkward as a result newTalent{ - name = "Maim", + name = "Crushing Hold", type = {"technique/grappling", 2}, require = techs_req2, + mode = "passive", points = 5, - random_ego = "attack", - cooldown = 12, - stamina = 10, - tactical = { ATTACK = { PHYSICAL = 2 }, DISABLE = 2 }, + tactical = { ATTACK = { PHYSICAL = 2 }, DISABLE = { silence = 2 } }, requires_target = true, - getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end, - getDamage = function(self, t) return self:combatTalentPhysicalDamage(t, 10, 100) * getUnarmedTrainingBonus(self) end, - getMaim = function(self, t) return self:combatTalentPhysicalDamage(t, 5, 30) end, - -- Learn the appropriate stance - 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 core.fov.distance(self.x, self.y, x, y) > 1 then return nil end - - local grappled = false - - -- breaks active grapples if the target is not grappled - if target:isGrappled(self) then - grappled = true + getDamage = function(self, t) return self:combatTalentPhysicalDamage(t, 5, 50) * getUnarmedTrainingBonus(self) end, -- this function shouldn't be used any more but I left it in to be safe, Clinch now handles the damage + getSlow = function(self, t) + if self:getTalentLevel(self.T_CRUSHING_HOLD) >= 5 then + return self:combatTalentPhysicalDamage(t, 0.05, 0.65) else - self:breakGrapples() + return 0 end - - -- end the talent without effect if the target is to big - if self:grappleSizeCheck(target) then - return true - end - - -- start the grapple; this will automatically hit and reapply the grapple if we're already grappling the target - local hit = self:startGrapple (target) - -- deal damage and maim if appropriate - if hit then - if grappled then - self:project(target, x, y, DamageType.PHYSICAL, self:physicalCrit(t.getDamage(self, t), nil, target, self:combatAttack(), target:combatDefense())) - target:setEffect(target.EFF_MAIMED, t.getDuration(self, t), {power=t.getMaim(self, t)}) - else - self:project(target, x, y, DamageType.PHYSICAL, self:physicalCrit(t.getDamage(self, t), nil, target, self:combatAttack(), target:combatDefense())) - end + end, + getDamageReduction = function(self, t) + return self:combatTalentPhysicalDamage(t, 1, 15) + end, + getSilence = function(self, t) -- this is a silence without an immunity check by design, if concerned about NPC use this is the talent to block + if self:getTalentLevel(self.T_CRUSHING_HOLD) >= 3 then + return 1 + else + return 0 end - - return true end, - info = function(self, t) - local duration = t.getDuration(self, t) - local damage = t.getDamage(self, t) - local maim = t.getMaim(self, t) - return ([[Grapples the target and inflicts %0.2f physical damage. If the target is already grappled, the target will be maimed as well, reducing damage by %d and global speed by 30%% for %d turns. - The grapple effects will be based off your grapple talent, if you have it, and the damage will scale with your Physical Power.]]) - :format(damDesc(self, DamageType.PHYSICAL, (damage)), maim, duration) + getBonusEffects = function(self, t) -- used by startGrapple in Combat.lua, essentially merges these properties and the Clinch bonuses + return {silence = t.getSilence(self, t), slow = t.getSlow(self, t), reduce = t.getDamageReduction(self, t)} end, -} - -newTalent{ - name = "Crushing Hold", - type = {"technique/grappling", 3}, - require = techs_req3, - mode = "passive", - points = 5, - tactical = { ATTACK = { PHYSICAL = 2 }, DISABLE = { silence = 2 } }, - requires_target = true, - getDamage = function(self, t) return self:combatTalentPhysicalDamage(t, 5, 50) * getUnarmedTrainingBonus(self) end, info = function(self, t) - local damage = t.getDamage(self, t) - return ([[Your clinch talent now starts a crushing hold that deals %0.2f physical damage each turn. If the target is already grappled, the hold will instead become a stranglehold, silencing the target and inflicting %0.2f physical damage each turn. - Undead, targets immune to silence, and creatures that do not breathe are immune to the strangle effect, and will only be affected by the crushing hold. - The damage will scale with your Physical Power.]]) - :format(damDesc(self, DamageType.PHYSICAL, (damage)), damDesc(self, DamageType.PHYSICAL, (damage * 1.5))) + local reduction = t.getDamageReduction(self, t) + local slow = t.getSlow(self, t) + + return ([[Enhances your grapples with additional effects. All additional effects will apply to every grapple with no additional save or resist check. + #RED#Talent Level 1: Reduces base weapon damage by %d + Talent Level 3: Silences + Talent Level 5: Reduces global action speed by %d%%]]) + :format(reduction, slow*100) end, } newTalent{ name = "Take Down", - type = {"technique/grappling", 4}, - require = techs_req4, + type = {"technique/grappling", 3}, + require = techs_req3, points = 5, random_ego = "attack", - cooldown = 24, - stamina = 12, - tactical = { ATTACK = { PHYSICAL = 1, stun = 1}, DISABLE = { stun = 2 }, CLOSEIN = 2 }, + cooldown = 10, + stamina = 15, + tactical = { ATTACK = { PHYSICAL = 1}, CLOSEIN = 2 }, requires_target = true, range = function(self, t) return math.floor(self:combatTalentScale(t, 2.3, 3.7)) end, getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end, getTakeDown = function(self, t) return self:combatTalentPhysicalDamage(t, 10, 100) * getUnarmedTrainingBonus(self) end, - getSlam = function(self, t) return self:combatTalentPhysicalDamage(t, 15, 150) * getUnarmedTrainingBonus(self) end, - -- Learn the appropriate stance + getSlam = function(self, t) return self:combatTalentPhysicalDamage(t, 10, 400) * getUnarmedTrainingBonus(self) end, + getDamage = function(self, t) + return self:combatTalentWeaponDamage(t, .1, 1) + end, 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 core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then return nil end - local grappled = false + -- if the target is grappled then do an attack+AoE project + if self:hasEffect(self.EFF_GRAPPLING) then + local target = self:hasEffect(self.EFF_GRAPPLING)["trgt"] + local tg = {type="ball", range=1, radius=5, selffire=false} - -- do the rush - local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", self) end - local l = self:lineFOV(x, y, block_actor) - local lx, ly, is_corner_blocked = l:step() - local tx, ty = self.x, self.y - while lx and ly do - if is_corner_blocked or game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end - tx, ty = lx, ly - lx, ly, is_corner_blocked = l:step() - end + local hit = self:attackTarget(target, nil, t.getDamage(self, t), true) + local slam = self:physicalCrit(t.getSlam(self, t), nil, target, self:combatAttack(), target:combatDefense()) + self:project(tg, self.x, self.y, DamageType.PHYSICAL, slam, {type="bones"}) + + self:breakGrapples() + + return true + else + 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 core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then return nil end + - local ox, oy = self.x, self.y - self:move(tx, ty, true) - if config.settings.tome.smooth_move > 0 then - self:resetMoveAnim() - self:setMoveAnim(ox, oy, 8, 5) - end + local grappled = false - -- breaks active grapples if the target is not grappled - if target:isGrappled(self) then - grappled = true - else - self:breakGrapples() - end + -- do the rush + local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", self) end + local l = self:lineFOV(x, y, block_actor) + local lx, ly, is_corner_blocked = l:step() + local tx, ty = self.x, self.y + while lx and ly do + if is_corner_blocked or game.level.map:checkAllEntities(lx, ly, "block_move", self) then break end + tx, ty = lx, ly + lx, ly, is_corner_blocked = l:step() + end - if core.fov.distance(self.x, self.y, x, y) == 1 then - -- end the talent without effect if the target is to big - if self:grappleSizeCheck(target) then - return true + local ox, oy = self.x, self.y + self:move(tx, ty, true) + if config.settings.tome.smooth_move > 0 then + self:resetMoveAnim() + self:setMoveAnim(ox, oy, 8, 5) end - -- start the grapple; this will automatically hit and reapply the grapple if we're already grappling the target - local hit = self:startGrapple (target) - -- takedown or slam as appropriate - if hit then - if grappled then - self:project(target, x, y, DamageType.PHYSICAL, self:physicalCrit(t.getSlam(self, t), nil, target, self:combatAttack(), target:combatDefense())) - if target:canBe("stun") then - target:setEffect(target.EFF_STUNNED, t.getDuration(self, t), {apply_power=self:combatPhysicalpower()}) - else - game.logSeen(target, "%s resists the stun!", target.name:capitalize()) - end - else - self:project(target, x, y, DamageType.PHYSICAL, self:physicalCrit(t.getTakeDown(self, t), nil, target, self:combatAttack(), target:combatDefense())) - if target:canBe("stun") then - target:setEffect(target.EFF_DAZED, t.getDuration(self, t), {apply_power=self:combatPhysicalpower()}) - else - game.logSeen(target, "%s resists the daze!", target.name:capitalize()) - end + -- breaks active grapples if the target is not grappled + if target:isGrappled(self) then + grappled = true + else + self:breakGrapples() + end + + if core.fov.distance(self.x, self.y, x, y) == 1 then + -- end the talent without effect if the target is to big + if self:grappleSizeCheck(target) then + return true end + + -- start the grapple; this will automatically hit and reapply the grapple if we're already grappling the target + local hit = self:attackTarget(target, nil, t.getDamage(self, t), true) + local hit2 = self:startGrapple (target) + end - end - return true + return true + end end, info = function(self, t) - local duration = t.getDuration(self, t) - local takedown = t.getTakeDown(self, t) + local takedown = t.getDamage(self, t)*100 local slam = t.getSlam(self, t) - return ([[Rushes forward and attempts to take the target to the ground, starting a grapple, inflicting %0.2f physical damage, and dazing the target for %d turns. If you're already grappling the target you'll instead slam them into the ground for %0.2f physical damage and potentially stun them for %d turns. + return ([[Rushes forward and attempts to take the target to the ground, making a melee attack for %d%% damage then attempting to grapple them. If you're already grappling the target you'll instead slam them into the ground creating a radius 5 shockwave for %d physical damage and breaking your grapple. The grapple effects and duration will be based off your grapple talent, if you have it, and the damage will scale with your Physical Power.]]) - :format(damDesc(self, DamageType.PHYSICAL, (takedown)), duration, damDesc(self, DamageType.PHYSICAL, (slam)), duration) + :format(damDesc(self, DamageType.PHYSICAL, (takedown)), damDesc(self, DamageType.PHYSICAL, (slam))) end, } +newTalent{ + name = "Hurricane Throw", + type = {"technique/grappling", 4}, + require = techs_str_req4, + points = 5, + random_ego = "attack", + requires_target = true, + cooldown = function(self, t) + return 8 + end, + stamina = 20, + range = function(self, t) + return 8 + end, + radius = function(self, t) + return 1 + end, + getDamage = function(self, t) + return self:combatTalentWeaponDamage(t, 1, 3.5) -- no interaction with Striking Stance so we make the base damage higher to compensate + end, + target = function(self, t) + return {type="ball", range=self:getTalentRange(t), selffire=false, radius=self:getTalentRadius(t)} + end, + action = function(self, t) + + if self:hasEffect(self.EFF_GRAPPLING) then + local grappled = self:hasEffect(self.EFF_GRAPPLING)["trgt"] + + local tg = self:getTalentTarget(t) + local x, y, target = self:getTarget(tg) + if not x or not y then return nil end + local _ _, x, y = self:canProject(tg, x, y) + + -- if the target square is an actor, find a free grid around it instead + if game.level.map(x, y, Map.ACTOR) then + x, y = util.findFreeGrid(x, y, 1, true, {[Map.ACTOR]=true}) + if not x then return end + end + + if game.level.map:checkAllEntities(x, y, "block_move") then return end + + local ox, oy = grappled.x, grappled.y + grappled:move(x, y, true) + if config.settings.tome.smooth_move > 0 then + grappled:resetMoveAnim() + grappled:setMoveAnim(ox, oy, 8, 5) + end + + -- pick all targets around the landing point and do a melee attack + self:project(tg, grappled.x, grappled.y, function(px, py, tg, self) + local target = game.level.map(px, py, Map.ACTOR) + if target and target ~= self then + + local hit = self:attackTarget(target, nil, t.getDamage(self, t), true) + self:breakGrapples() + end + end) + return true + + else + -- only usable if you have something Grappled + return false + end + end, + info = function(self, t) + return ([[In a mighty show of strength you whirl your grappled victim around and throw them into the air causing %d%% damage to them and any nearby enemies they collide with on landing.]]):format(t.getDamage(self, t)*100) + end, +} diff --git a/game/modules/tome/data/talents/techniques/techniques.lua b/game/modules/tome/data/talents/techniques/techniques.lua index 87619c76d7..0094ad6d81 100644 --- a/game/modules/tome/data/talents/techniques/techniques.lua +++ b/game/modules/tome/data/talents/techniques/techniques.lua @@ -47,7 +47,7 @@ newTalentType{ is_unarmed=true, allow_random=true, type="technique/pugilism", na newTalentType{ is_unarmed=true, allow_random=true, type="technique/finishing-moves", name = "finishing moves", description = "Finishing moves that use combo points and may not be practiced in massive armor or while a weapon or shield is equipped." } newTalentType{ is_unarmed=true, allow_random=true, type="technique/grappling", name = "grappling", description = "Grappling techniques that may not be practiced in massive armor or while a weapon or shield is equipped." } newTalentType{ is_unarmed=true, allow_random=true, type="technique/unarmed-discipline", name = "unarmed discipline", description = "Advanced unarmed techniques including kicks and throw that may not be practiced in massive armor or while a weapon or shield is equipped." } -newTalentType{ is_unarmed=true, allow_random=true, type="technique/unarmed-training", name = "unarmed training", description = "Teaches various martial arts techniques that may not be practiced in massive armor or while a weapon or shield is equipped." } +newTalentType{ is_unarmed=true, allow_random=true, generic = true, type="technique/unarmed-training", name = "unarmed training", description = "Teaches various martial arts techniques that may not be practiced in massive armor or while a weapon or shield is equipped." } newTalentType{ allow_random=true, type="technique/conditioning", name = "conditioning", generic = true, description = "Physical conditioning." } newTalentType{ is_unarmed=true, type="technique/unarmed-other", name = "unarmed other", generic = true, description = "Base martial arts attack and stances." } diff --git a/game/modules/tome/data/timed_effects/physical.lua b/game/modules/tome/data/timed_effects/physical.lua index b446f1eab8..020e838ae7 100644 --- a/game/modules/tome/data/timed_effects/physical.lua +++ b/game/modules/tome/data/timed_effects/physical.lua @@ -43,6 +43,8 @@ newEffect{ end, } + + newEffect{ name = "CUT", image = "effects/cut.png", desc = "Bleeding", @@ -1084,54 +1086,67 @@ newEffect{ newEffect{ name = "GRAPPLING", image = "talents/clinch.png", desc = "Grappling", - long_desc = function(self, eff) return ("The target is engaged in a grapple. Any movement will break the effect as will some unarmed talents."):format() end, + long_desc = function(self, eff) return ("Engaged in a grapple draining %d stamina per turn and redirecting %d%% of damage taken to %s. Any movement will break the effect as will some unarmed talents."):format(eff.drain, eff.sharePct*100, eff.trgt.name or "unknown") end, type = "physical", subtype = { grapple=true, }, status = "beneficial", - parameters = {}, + parameters = {trgt, sharePct = 0.1, drain = 0}, on_gain = function(self, err) return "#Target# is engaged in a grapple!", "+Grappling" end, on_lose = function(self, err) return "#Target# has released the hold.", "-Grappling" end, on_timeout = function(self, eff) local p = eff.trgt:hasEffect(eff.trgt.EFF_GRAPPLED) - local drain = 6 - (self:getTalentLevelRaw(self.T_CLINCH) or 0) if not p or p.src ~= self or core.fov.distance(self.x, self.y, eff.trgt.x, eff.trgt.y) > 1 or eff.trgt.dead or not game.level:hasEntity(eff.trgt) then self:removeEffect(self.EFF_GRAPPLING) else - self:incStamina(-drain) + self:incStamina(-eff.drain) end end, activate = function(self, eff) end, deactivate = function(self, eff) end, + callbackOnHit = function(self, eff, cb, src) + if not src then return cb.value end + local share = cb.value * eff.sharePct + + -- deal the redirected damage as physical because I don't know how to preserve the damage type in a callback + DamageType:get(DamageType.PHYSICAL).projector(self or eff.src, eff.trgt.x, eff.trgt.y, DamageType.PHYSICAL, share) + + return cb.value - share + end, } newEffect{ name = "GRAPPLED", image = "talents/grab.png", desc = "Grappled", - long_desc = function(self, eff) return ("The target is grappled, unable to move, and has its defense and attack reduced by %d."):format(eff.power) end, + long_desc = function(self, eff) return ("The target is grappled, unable to move, and limited in its offensive capabilities.\n#RED#Silenced\nPinned\n%s\n%s\n%s"):format("Damage reduced by " .. eff.reduce, "Slowed by " .. eff.slow, "Damage per turn " .. math.ceil(eff.power) ) end, type = "physical", subtype = { grapple=true, pin=true }, status = "detrimental", - parameters = {}, + parameters = {silence = 0, slow = 0, reduce = 1, power = 1}, remove_on_clone = true, on_gain = function(self, err) return "#Target# is grappled!", "+Grappled" end, on_lose = function(self, err) return "#Target# is free from the grapple.", "-Grappled" end, activate = function(self, eff) - eff.tmpid = self:addTemporaryValue("never_move", 1) - eff.def = self:addTemporaryValue("combat_def", -eff.power) - eff.atk = self:addTemporaryValue("combat_atk", -eff.power) + self:effectTemporaryValue(eff, "never_move", 1) + self:effectTemporaryValue(eff, "combat_dam", -eff.reduce) + if (eff.silence > 0) then + self:effectTemporaryValue(eff, "silence", 1) + end + if (eff.slow > 0) then + self:effectTemporaryValue(eff, "global_speed_add", -eff.slow) + end end, on_timeout = function(self, eff) if not self.x or not eff.src or not eff.src.x or core.fov.distance(self.x, self.y, eff.src.x, eff.src.y) > 1 or eff.src.dead or not game.level:hasEntity(eff.src) then self:removeEffect(self.EFF_GRAPPLED) + else + DamageType:get(DamageType.PHYSICAL).projector(eff.src or self, self.x, self.y, DamageType.PHYSICAL, eff.power) end end, deactivate = function(self, eff) - self:removeTemporaryValue("combat_atk", eff.atk) - self:removeTemporaryValue("combat_def", eff.def) - self:removeTemporaryValue("never_move", eff.tmpid) - end, + + end, } newEffect{ -- GitLab