PK 74N data/PK 04Nn2S S data/damage_types.lua-- $Id: damage_types.lua 3560 2019-01-18 01:16:15Z dsb $
-- ToME - Tales of Middle-Earth
-- Copyright © 2012-2019 Scott Bigham
--
-- 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 .
--
-- Scott Bigham "Zizzo"
-- dsb-tome@killerbunnies.org
-- Damage types extracted from T2's melee1.c
-- The default projector uses the following flags and methods in the damage
-- type definition, if present:
--
-- - harmless_unless_vulnerable: If true, damage is reduced to zero
-- unless target:vulnDam(type) returns true.
--
-- - damage_is_power: If true, this damage type doesn't do direct hit
-- point damage, but has side effects [typically implemented via
-- side_effects_{pre,post}()], and the provided damage value is an
-- effect-specific representation of the side effect's "power". No
-- normal hit will be done and no hit/damage message printed.
--
-- - learn_on{}: Flags specifying the conditions under which the damage
-- effect is considered "obvious" for purposes of monster learning
-- [cf. ActorLearn:learnFlagsFor()]. If absent, the damage effect is
-- considered "obvious" by default. Supported flags:
--
-- = not_resist: Damage effect is "obvious" if target does not resist
-- this damage type.
--
-- = side_effect: "Obvious"-ness is determined by side_effects_pre()
-- and/or side_effects_post(), which will return their conclusion in
-- the 'learn' field of their tmp{} parameter.
--
-- - target_flags{}: Targeting flags that will be used for this damage
-- type by spell.bolt_spell() [q.v.] and related utility methods.
-- [cf. util.target_merge_damage_type()]
--
-- - resist_reduce(dam, target, type): Returns reduced damage depending
-- on target's resistances. Can assume target:resistDam(type) returns
-- true.
--
-- - vuln_increase(dam, target): Returns increased damage depending on
-- target's vulnerabilities. Can assume target:vulnDam(type) returns
-- true.
--
-- - is_immune(self, who, learn): Called indirectly through
-- NPC:immuneDam() and Player:immuneDam(); if target should be immune,
-- returns true and an optional suffix message. If 'learn' is true,
-- should call ActorLearn:learnFlags() as appropriate.
--
-- - is_resist(self, who, learn): Called indirectly through
-- NPC:resistDam() and Player:resistDam(); if target should resist,
-- returns true and an optional suffix message. If 'learn' is true,
-- should call ActorLearn:learnFlags() as appropriate.
--
-- - is_vulnerable(self, who, learn): Called indirectly through
-- NPC:vulnDam() and Player:vulnDam(); if target should be vulnerable,
-- returns true and an optional suffix message. If 'learn' is true,
-- should call ActorLearn:learnFlags() as appropriate.
--
-- - side_effects_pre(src, target, x, y, type, dam, p, tmp): Applies
-- side effects like destroying inventory before damage is applied to
-- target.
--
-- - side_effects_post(src, target, x, y, type, dam, p, tmp): Applies
-- side effects like stealing gold and teleporting away after damage is
-- applied to target (if target is not dead).
--
-- - grid_side_effect(def, g, x, y, src): Called by Grid:projected() to
-- apply side effects like illuminating a grid or removing traps.
-- Should not be used to change the grid type, use change_grid() or the
-- grid type definition's change_for_damtype() method instead.
--
-- - change_grid(def, g, x, y, who): Called by Grid:projected to
-- potentiall change the grid. Supercedes the grid definition's
-- change_for_damtype() method [if any] if it returns a non-nil grid
-- type.
--
-- - post_project(def, who, t, x, y, damtype, dam, particles, grids,
-- stop_x, stop_y): Called by the module's overridden
-- ActorProject:project() method immediately after it calls the
-- engine's ActorProject:project(), to apply "global" side effects
-- after all grids have been :project()'ed to. spell.clobber_FOVs()
-- [q.v.], for instance, is popular for damage types that create or
-- remove vision-blocking grids.
--
-- In addition, the default projector allows the 'dam' parameter to be a
-- table containing additional controlling flags, which will be passed in
-- the p{} parameter to side_effects_{pre,post}(). dam[1] will be the
-- actual numerical damage; flags used directly by the projector are:
--
-- - atk_msg: Appended to attacker name as display message. Should
-- have one '%s' printf() field, which will receive the target name.
-- Should not include final period; that will be added by the projector.
-- [cf. AttackType:monAttackTarget()]
--
-- - short_0dam: If set and damage is zero, hit message will not include
-- e.g. "for 0 physical damage". [cf. PlayerCombat:oneBlowSpecial()]
--
-- - silent_0dam: If set and damage is zero, no hit message will be
-- shown.
--
-- - cx, cy: The center of effect for ball spells. The projector will
-- compute distance from this center to the effect coordinates as an
-- additional field 'cd' for its own internal use, and will reduce
-- damage relative to this distance.
-- [cf. spell.ball_spell(), spell.cloud_spell()]
--
-- - display_src: An optional alternate source to use for the hit
-- message. Can be an entity with a :getName() method [which should
-- behave like Actor:getName()] or a table with a 'name' field.
--
-- Other fields in the damage type definition used by other code:
--
-- - cloud_color{}: Colors used for cloud effects of this damage type.
-- A random one will be selected each turn. [cf. spell.cloud_spell(),
-- spell.wall_spell()]
--
-- - floor_dam{}: Categories into which this damage type fits, and
-- probabilities that they will damage grids.
-- [cf. spell.grid_damage(), Grid:projected()]
--
-- = floor_dam.floor: A special field of floor_dam{}, used by
-- spell.floor_damage() [q.v.] to determine how this damage type
-- affects floor grids. If the value is a table, keys are grid types
-- and values are probability of change; if the value is a function
-- [taking parameters (grid, x, y, damtype, dam)], it should return
-- the resulting grid type, or nil for no change.
--
-- - obj_destroy_sfx{}: Message suffixes (singular, plural) for floor
-- objects destroyed by this damage type.
--
-- - power: Used to determine hit chance of monster melee attack.
-- [cf. AttackType:monAttackTarget()]
--
-- - weapon: The weapon (or analogue) in use if this damage is being
-- applied by way of a melee attack. This will be passed via extreme
-- hackery to the target's on_take_hit_pre() actor callback [q.v.].
-- A modified version of engine.Target.default.block_path() designed for
-- spell effects that can destroy (non-permanent) walls, and thus should
-- not be blocked thereby.
function DamageType.pass_wall_block_path(typ, lx, ly, for_highlights)
local map = game.level.map
if not map:isBound(lx, ly) then
return true, false, false
elseif not typ.no_restrict then
if typ.range and typ.start_x then
local dist = core.fov.distance(typ.start_x, typ.start_y, lx, ly)
if dist > typ.range then return true, false, false end
elseif typ.range and typ.source_actor and typ.source_actor.x then
local dist = core.fov.distance(typ.source_actor.x, typ.source_actor.y, lx, ly)
if dist > typ.range then return true, false, false end
end
local is_known = map.remembers(lx, ly) or map.seens(lx, ly)
if typ.requires_knowledge and not is_known then
return true, false, false
end
local trn_block, trn_pass
-- Our modification: require both 'block_move' and 'permanent' to
-- block.
trn_block = (map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") and map:checkEntity(lx, ly, engine.Map.TERRAIN, "permanent")) or false
if trn_block then
trn_pass = map:checkEntity(lx, ly, engine.Map.TERRAIN, "pass_projectile") or false
if not trn_pass then -- blocked by terrain
if for_highlights and not is_known then
return false, "unknown", true
else
return true, true, false
end
end
end
-- If the projection is blocked by something other than terrain, the grid should be hit
if typ.stop_block then -- check all entities
-- get #blocking entities and subtract for each entity that can be explicitly passed through
local nb = map:checkAllEntitiesCount(lx, ly, "block_move")
if nb > 0 then -- decrement for passable terrain
if trn_block == nil then trn_block = map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") end
if trn_block and (typ.pass_terrain or (trn_pass == nil and map:checkEntity(lx, ly, engine.Map.TERRAIN, "pass_projectile") or trn_pass)) then
nb = nb - 1
end
end
if nb > 0 and (typ.friendlyblock ~= nil or not typ.actorblock) then -- decrement for passable actors
local a = map(lx, ly, engine.Map.ACTOR)
if a then -- friendly block controls if specified
if typ.friendlyblock ~= nil and typ.source_actor and typ.source_actor.reactionToward and typ.source_actor:reactionToward(a) >= 0 then
if not typ.friendlyblock then nb = nb - 1 end
elseif not typ.actorblock then
nb = nb - 1
end
end
end
if nb > 0 then
if for_highlights then
-- Targeting highlight should be yellow if the grid is not known
if not is_known then
return false, "unknown", true
-- Don't show the path as blocked if it's blocked by an actor we can't see
elseif nb == 1 and typ.source_actor and typ.source_actor.canSee and not typ.source_actor:canSee(map(lx, ly, engine.Map.ACTOR)) then
return false, true, true
end
end
return true, true, true
end
end
if for_highlights and not is_known then
return false, "unknown", true
end
end
-- Projection not blocked, grid is hit
return false, true, true
end
-- A modified version of engine.Target.default.block_radius() designed for
-- spell effects that can destroy (non-permanent) walls, and thus should
-- not be blocked thereby.
function DamageType.pass_wall_block_radius(typ, lx, ly, for_highlights)
local map = game.level.map
if not map:isBound(lx, ly) then return true end
if typ.no_restrict then return end
if typ.requires_knowledge and not (map.remembers(lx, ly) or map.seens(lx, ly)) then return true end
local blocked, trn_block, trn_pass
-- Our modification: require both 'block_move' and 'permanent' to block.
trn_block = (map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") and map:checkEntity(lx, ly, engine.Map.TERRAIN, "permanent")) or false
if trn_block then
trn_pass = map:checkEntity(lx, ly, engine.Map.TERRAIN, "pass_projectile") or false
if not trn_pass then blocked = true end -- blocked by terrain
end
if not blocked and typ.stop_block then -- check all entities
-- get #blocking entities and subtract for each entity that can be explicitly passed through
local nb = map:checkAllEntitiesCount(lx, ly, "block_move")
if nb > 0 then -- decrement for passable terrain
if trn_block == nil then trn_block = map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") end
if trn_block and (typ.pass_terrain or (trn_pass == nil and map:checkEntity(lx, ly, engine.Map.TERRAIN, "pass_projectile") or trn_pass)) then
nb = nb - 1
end
end
if nb > 0 then
local a = map(lx, ly, engine.Map.ACTOR)
if a then -- decrement for passable actors
-- For targeting highlights, don't show as blocked if the player can't see the actor
if for_highlights and typ.source_actor and typ.source_actor.canSee and not typ.source_actor:canSee(a) then
nb = nb - 1
else
if typ.friendlyblock == nil and not typ.actorblock then -- friendly block controls if specified
nb = nb - 1
else
if typ.friendlyblock ~= nil and typ.source_actor and typ.source_actor.reactionToward and typ.source_actor:reactionToward(a) >= 0 then
if not typ.friendlyblock then nb = nb - 1 end
elseif not typ.actorblock then
nb = nb - 1
end
end
end
end
end
if nb > 0 then blocked = true end
end
-- treat unknown grids as non-blocking for player targeting highlights
if blocked and not (for_highlights and not (map.remembers(lx, ly) or map.seens(lx, ly))) then return true end
end
-- Reduce the provided value inversely by the provided distance. Used for
-- damage and other side effect power levels in ball spells.
local function reduce_by_dist(val, p)
if p.cd then return math.floor((val + p.cd)/(p.cd + 1)) end
return val
end
setDefaultProjector(function(src, x, y, type, dam)
local p = {}
if _G.type(dam) == 'table' then dam, p = dam[1], dam end
local ret = 0
local atk_msg = p.atk_msg or ' hits %s'
local name = src.getName and src:getName{def_art=true, check_seen=true, show_setter=true} or src.name or 'something'
if p.display_src then
name = p.display_src.getName and p.display_src:getName{def_art=true, check_seen=true, show_setter=true} or p.display_src.name or name
end
local fmt = name:capitalize()..atk_msg..' for %d %s%s#LAST# damage.'
local fmt_0dam = name:capitalize()..atk_msg..'.'
local dt = DamageType:get(type)
local dt_color = dt.text_color or '#AAAAAA#'
local tmp = {}
local learn = dt.learn_on and false or true
-- Questionable Hack(TM): Max damage done to the player is 1600, before
-- any other adjustment (cf. src/spells1.c line 7281)
if target == game.player then dam = math.min(dam, 1600) end
if p.cx and p.cy then
-- For ball spells, reduce damage by distance from center.
p.cd = math.floor(core.fov.distance(x, y, p.cx, p.cy))
dam = reduce_by_dist(dam, p)
end
local target = game.level.map(x, y, Map.ACTOR)
if target and not target.dead then
local flash = game.flash.NEUTRAL
if target == game.player then flash = game.flash.BAD end
if src == game.player then flash = game.flash.GOOD end
-- Apply resistances, susceptibilities and immunities.
tmp.vuln, tmp.vuln_msg = target:vulnDam(type, {learn=true})
if tmp.vuln then
dam = dt.vuln_increase and dt.vuln_increase(dam, target) or dam
elseif dt.harmless_unless_vulnerable then
dam = 0
end
tmp.resist, tmp.resist_msg = target:resistDam(type, {learn=true})
if tmp.resist then
dam = dt.resist_reduce and dt.resist_reduce(dam, target, type) or dam
end
tmp.immune, tmp.immune_msg = target:immuneDam(type, {learn=true})
if tmp.immune then dam = 0 end
if dt.learn_on and dt.learn_on.not_resist then
learn = learn or (not tmp.resist and not tmp.immune)
end
-- Apply pre-damage side effects of damage type.
if dt.side_effects_pre then
dam = dt:side_effects_pre(src, target, x, y, type, dam, p, tmp) or dam
end
-- Apply actual damage.
if not dt.damage_is_power then
local tname = target.getName and target:getName{def_art=true, check_seen=true} or target.name or 'something'
local Tname = tname:capitalize()
if dam == 0 and p.short_0dam then
game.logSeen(target, flash, fmt_0dam, tname)
elseif dam == 0 and tmp.immune_msg then
game.logSeen(target, Tname..tmp.immune_msg)
elseif dam > 0 or not p.silent_0dam then
if tmp.vuln_msg then game.logSeen(target, Tname..tmp.vuln_msg) end
if tmp.resist_msg then game.logSeen(target, Tname..tmp.resist_msg) end
game.logSeen(target, flash, fmt, tname, dam, dt_color, dt.name)
end
local sx, sy = game.level.map:getTileToScreen(x, y)
-- Dodgy and Ugly Hack(TM): Pass the melee weapon (if any) through
-- to Actor:takeHit() by a side channel.
target.__hack_melee_weapon = p.weapon
local died, did_dam = target:takeHit(dam, src)
target.__hack_melee_weapon = nil
if died then
if src == game.player or target == game.player then
game.flyers:add(sx, sy, 30, (rng.range(0,2)-1) * 0.5, -3, "Kill!", {255,0,255})
end
elseif did_dam > 0 or not p.silent_0dam then
local flyer_color = src == game.player and {0,255,0} or target == game.player and {255,0,0} or nil
if flyer_color then
game.flyers:add(sx, sy, 30, (rng.range(0,2)-1) * 0.5, -3, tostring(-math.ceil(did_dam)), flyer_color)
end
end
end
-- Apply post-damage side effects of damage type.
if not target.dead and dt.side_effects_post then
dam = dt:side_effects_post(src, target, x, y, type, dam, p, tmp) or dam
end
ret = dam
if dt.learn_on and dt.learn_on.side_effect then
learn = learn or tmp.learn
end
end
return ret, learn
end)
-- Standard resistance reduction for the most common damage types (fire,
-- cold, etc.): 1/3 damage for single resist, 1/9 for double resist, and
-- 1/9 damage for NPCs with the resist.
local function simple_resist_reduce(dam, target, type)
if target == game.player then
return target:resistDam(type, {count=true}) > 1 and dam/9 or dam/3
else
return dam/9, ' resists a lot.'
end
end
-- Another common resistance reduction for damage types in T2, reducing to
-- (dam*N)/(6+1d6), for varying values of N (N=3 is common).
local function resist_reduce_dam_div_7_12(dam, num, target, type)
return math.floor(dam*num/rng.range(7, 12)), ' resists.'
end
-- A factory for making resist_reduce() damage type methods using the above
-- (dam*N)/(6+1d6) pattern with a specified N. If 'num_plyr' is specified,
-- it's a separate N to be used for the player.
local function resist_reduce_dam_div_7_12_make(num, num_plyr)
if num_plyr then
return function(dam, target, type)
local nn = target == game.player and num_plyr or num
return resist_reduce_dam_div_7_12(dam, nn, target, type)
end
end
return function(dam, target, type)
return resist_reduce_dam_div_7_12(dam, num, target, type)
end
end
-- Commonly used pattern for NPCs resisting an effect: compare target
-- level to a random number with range based on effect power.
local function save_level_vs_power(who, pow, adj, sfx)
local ret = (who.level or 0) > rng.range(1, math.max(1, pow - 10)) + adj
if ret and sfx then game.logSeen(who, who:getName{def_art=true, check_seen=true} .. sfx) end
return ret
end
-- Tries to destroy stuff in the player's carried inventory. If 'pct' is
-- not provided, it is computed from 'dam', in the range 1-3. 'type' is
-- the damage type used to check for object projection, and 'res_type' is
-- the damage type used to check player resists, defaulting to 'type' if
-- not specified. 'bypass_chance' is a 1/N chance to destroy inventory
-- through resists if specified.
local function try_damage_player_inven(src, target, type, dam, pct, res_type, bypass_chance)
if target == game.player and src ~= game.player then
res_type = res_type or type
local resists = target:resistDam(res_type, {or_immune=true})
if not resists or (bypass_chance and rng.chance(bypass_chance)) then
pct = pct or math.min(1 + math.floor(dam/30), 3)
local inven = target:getInven('INVEN')
for idx = #inven, 1, -1 do
local o = inven[idx]
if not o.unique and o:hasFlag('destroyed_by', type) and not o:hasFlag('ignore_dam', type) then
local n, amt = o:getNumber(), 0
for _ = 1, n do
-- Check percent chance of destruction on each item in stack.
if rng.percent(pct) then amt = amt+1 end
end
if amt > 0 then
local name = o:getName{no_count=true}
local msg = (n == 1 and 'Your ' or
amt == n and 'All of your ' or
amt > 1 and 'Some of your ' or 'One of your ')
msg = msg .. name .. (amt > 1 and ' were' or ' was') .. 'destroyed!'
game.logPlayer(target, msg)
if o.on_destroy then o:on_destroy(src, target.x, target.y) end
-- TODO Handle stacked wands
for _ = 1, amt do
target:removeObject(inven, idx)
end
end
end
end
end
end
end
-- Attempts to destroy a random equipped armor piece with acid damage.
local function acid_damage_armor(dam, target, type)
if target ~= game.player then return end
local slots = {
target.INVEN_BODY, target.INVEN_OFF_HAND, target.INVEN_CLOAK,
target.INVEN_GLOVES, target.INVEN_BOOTS, target.INVEN_HELM,
}
local inven = target:getInven(rng.table(slots))
if #inven == 0 then return dam end
local o = inven[1]
if o.type ~= 'armor' then return dam end
if o.armor + o.to_a <= 0 then return dam end
local name = o:getName{ no_count=true }
if o:hasFlag('ignore_dam', type) then
game.logPlayer(target, 'Your %s is unaffected!', name)
return dam/2
end
game.logPlayer(target, 'Your %s is damaged!', name)
o.to_a = o.to_a - 1
target:recalcEverything()
return dam/2
end
newDamageType {
name = 'physical',
type = 'PHYSICAL',
text_color = '#SLATE#',
power = 60,
}
newDamageType {
name = 'poison',
type = 'POISON',
text_color = '#C__G#',
cloud_color = { colors.C_G, colors.C_G, colors.C_g, colors.C_g, colors.C_g },
power = 5,
resist_reduce = simple_resist_reduce,
vuln_increase = function(dam, target) return dam*3, ' is hit hard.' end,
learn_on = { side_effect=true },
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if not tmp.immune and not tmp.resist then
local dur = 0
if target == game.player then
dur = rng.range(1, dam) + 10
elseif rng.percent(25) then
-- Poison duration for NPCs in T2 depends on distance from the
-- center of the ball.
dur = reduce_by_dist(rng.range(10, 20), p)
if tmp.vuln then dur = dur*2 end
end
if dur > 0 and not p.no_eff then
target:setEffect(target.EFF_POISON, dur, { merge='combine', src=src })
tmp.learn = true
end
end
end,
}
newDamageType {
name = 'charge drain',
type = 'UN_POWER',
text_color = '#SLATE#',
power = 15,
learn_on = { side_effect=true },
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
local function match_cb(target, inven, item, o)
return o.type == 'utility' and
(o.subtype == 'wand' or o.subtype == 'staff') and
o.charges and o.charges > 0 and not o.unique
end
local function apply_cb(_, inven, item, o)
game.logSeen(target, "Energy drains from %s's pack!", target:getName{def_art=true, check_seen=true})
src:heal(src.level*o.charges, o)
o.charges = 0
-- Drain wand/staff may combine or reorder with others.
target:sortInven()
target.changed = true
-- Learn this attack.
tmp.learn = true
end
util.pick_inven(target, match_cb, apply_cb)
end,
}
newDamageType {
name = 'steal gold',
type = 'EAT_GOLD',
text_color = '#SLATE#',
power = 5,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
-- Slight Hack(TM): Apparently in T2 gold can only be stolen from the
-- player.
-- TODO Rebelling symbiotes shouldn't be able to steal gold either.
local blink_chance = 0
if target == game.player then
local save = target.level + target:getStatRelated('dex_safe')
-- TODO Disallow if paralyzed.
if rng.percent(save) then
game.log('You quickly protect your money pouch!')
blink_chance = 3
else
local amt = math.floor((target.gold or 0)/10) + rng.range(1, 25)
amt = math.max(amt, 2)
if amt > 5000 then
amt = math.floor(target.gold/20) + rng.range(1, 3000)
end
amt = math.min(amt, target.gold)
if amt <= 0 then
game.log('Nothing was stolen.')
else
local gold = game.zone:makeEntityByName(game.level, 'object', 'GOLD')
gold.money_value = amt
src:addObject(src.INVEN_INVEN, gold)
local inven = src:getInven('INVEN') or {}
game.log('Your purse feels lighter.')
local pfx = amt < target.gold and ('%d coins'):format(amt) or 'All your coins'
game.log(pfx..' were stolen!')
target:adjGold(-amt)
end
blink_chance = 1
end
else
blink_chance = 2
end
if blink_chance > 0 and rng.chance(blink_chance) then
-- We count on this being the last attack, so we don't have to worry
-- about telling the caller to stop attacking.
game.logSeen(src, '%s flees laughing!', src.name)
spell.teleport(src, 45)
end
end,
}
newDamageType {
name = 'steal item',
type = 'EAT_ITEM',
text_color = '#SLATE#',
power = 5,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
-- Slight Hack(TM): Apparently in T2 items can only be stolen from the
-- player.
-- TODO Rebelling symbiotes shouldn't be able to steal items either.
if target == game.player then
-- TODO Disallow if paralyzed
local save = target.level + target:getStatRelated('dex_safe')
if rng.percent(save) then
game.log('You grab hold of your backpack!')
else
local function match_cb(target, inven, item, o)
return not o.unique
end
local function apply_cb(_, inven, item, _)
local o, last = target:removeObject(inven, item)
local name = o:getName{no_count=true, force_qty=last and 2 or nil}
local msg = (last and 'Your' or 'One of your') .. ' %s was stolen!'
src:addObject(src.INVEN_INVEN, o)
game.logPlayer(target, msg:format(name))
-- We count on this being the last attack, so we don't have to
-- worry about telling the caller to stop attacking.
game.logSeen(src, '%s flees laughing!', src.name)
spell.teleport(src, 45)
end
util.pick_inven(target, match_cb, apply_cb)
end
end
end,
}
newDamageType {
name = 'eat food',
type = 'EAT_FOOD',
text_color = '#SLATE#',
power = 5,
learn_on = { side_effect=true },
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
local function match_cb(target, inven, item, o)
return o.type == 'food'
end
local function apply_cb(_, inven, item, _)
local o, last = target:removeObject(inven, item)
local name = o:getName{no_count=true, force_qty=last and 2 or nil}
local msg = (last and 'Your' or 'One of your') .. ' %s was eaten!'
game.logPlayer(target, msg:format(name))
tmp.learn = true
end
end,
}
newDamageType {
name = 'absorb light',
type = 'EAT_LITE',
text_color = '#SLATE#',
power = 5,
learn_on = { side_effect=true },
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
local inven = target:getInven('LITE')
if not inven or #inven == 0 then return end
local o = inven[1]
if o and o.lite_fuel and not o.unique then
o.lite_fuel = math.max(1, o.lite_fuel - rng.range(251, 500))
game.logPlayer(target, "%s's light dims.", target:getName{def_art=true, check_seen=true})
tmp.learn = not game.player:has('BLIND')
end
end,
}
newDamageType {
name = 'acid',
type = 'ACID',
text_color = '#C_G#',
cloud_color = { colors.C_y, colors.C_y, colors.C_G, colors.C_G, colors.C_G },
floor_dam = { tree = true },
power = 0,
resist_reduce = simple_resist_reduce,
vuln_increase = function(dam, target) return dam*3 end,
obj_destroy_sfx = { 'melts!', 'melt!' },
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if target == game.player and src ~= game.player then
if not tmp.resist and not tmp.immune then
if rng.chance(32) then target:bumpStatDown('CHA') end
dam = acid_damage_armor(dam, target, type)
try_damage_player_inven(src, target, type, dam)
end
end
return dam
end,
}
newDamageType {
name = 'lightning',
type = 'ELEC',
text_color = '#C__W#',
cloud_color = { colors.C_w, colors.C_w, colors.C_w, colors.C_w, colors.C_w, colors.C_b, colors.C_B, colors.C_B },
power = 10,
resist_reduce = simple_resist_reduce,
vuln_increase = function(dam, target) return dam*3 end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if target == game.player and src ~= game.player then
if not tmp.resist and not tmp.immune then
if rng.chance(32) then target:bumpStatDown('DEX') end
try_damage_player_inven(src, target, type, dam)
end
end
end,
aura_adj = 'electric',
}
newDamageType {
name = 'fire',
type = 'FIRE',
text_color = '#C__Y#',
cloud_color = { colors.C_y, colors.C_y, colors.C_y, colors.C_r, colors.C_R, colors.C_R },
power = 10,
floor_dam = { fire = true, tree = true, floor = { SHALLOW_LAVA = 10, ASH = 15 } },
resist_reduce = simple_resist_reduce,
vuln_increase = function(dam, target)
return target == game.player and (dam+2)*2 or dam*3
end,
obj_destroy_sfx = { 'burns up!', 'burn up!' },
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if target == game.player and src ~= game.player then
if not tmp.resist and not tmp.immune then
if rng.chance(32) then target:bumpStatDown('STR') end
try_damage_player_inven(src, target, type, dam)
end
end
end,
}
newDamageType {
name = 'cold',
type = 'COLD',
text_color = '#C__W#',
cloud_color = { colors.C_w, colors.C_W },
power = 10,
floor_dam = { floor = { ICE = 20 } },
resist_reduce = simple_resist_reduce,
vuln_increase = function(dam, target) return dam*3 end,
obj_destroy_sfx = { 'shatters!', 'shatter!' },
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if target == game.player and src ~= game.player then
if not tmp.resist and not tmp.immune then
if rng.chance(32) then target:bumpStatDown('STR') end
try_damage_player_inven(src, target, type, dam)
end
end
end,
}
newDamageType {
name = 'blind',
type = 'BLIND',
text_color = '#SLATE#',
learn_on = { side_effect=true },
power = 2,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
-- In T2 source, only player gets blinded by this damage type.
if target == game.player and src ~= game.player and not tmp.resist and not tmp.immune then
target:setEffect(target.EFF_BLIND, 10 + rng.range(1, src.level or 1), {merge='combine'})
tmp.learn = true
end
end,
}
newDamageType {
name = 'confusion',
type = 'CONFUSION',
text_color = '#C__R#',
cloud_color = { colors.C_r, colors.C_g, colors.C_b, colors.C_y },
power = 10,
learn_on = { side_effect=true },
resist_reduce = function(dam, target, type)
if target == game.player then
return resist_reduce_dam_div_7_12(dam, 5, target, type)
elseif target:knowTalent(target.T_NPC_BR_CONF) then
-- Confusion breath gives good resist.
return resist_reduce_dam_div_7_12(dam, 2, target, type)
elseif target.resist_dam and target.resist_dam[DamageType.CONFUSION] then
-- Ordinary resist confusion gives partial resist.
return dam/2
end
end,
is_resist = function(self, who, learn)
if who:knowTalent(who.T_NPC_BR_CONF) then
-- Confusion breath gives good resist.
return true, ' resists.'
elseif who.resist_dam and who.resist_dam[DamageType.CONFUSION] then
-- Ordinary resist confusion gives partial resist.
return true, ' resists somewhat.'
end
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
-- In T2 source, only player gets confused by this damage type.
if target == game.player and src ~= game.player and not tmp.resist and not tmp.immune then
target:setEffect(target.EFF_CONFUSED, rng.range(11, 30), {merge='combine'})
tmp.learn = true
end
end,
}
newDamageType {
name = 'fear',
type = 'FEAR',
text_color = '#SLATE#',
learn_on = { side_effect=true },
power = 10,
damage_is_power = true,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if tmp.resist then
game.logSeen(target, '%s is unaffected.', target:getName{def_art=true, check_seen=true}:capitalize())
tmp.learn = true
elseif target.saving_throw and rng.percent(target.saving_throw) then
game.logSeen(target, '%s is unaffected.', target:getName{def_art=true, check_seen=true}:capitalize())
tmp.learn = true
else
local d2 = math.floor(dam/2)
local dur = d2 + rng.range(1, d2)
target:setEffect(target.EFF_AFRAID, dur, { merge='combine' })
tmp.learn = true
end
end,
}
newDamageType {
name = 'paralysis',
type = 'PARALYZE',
text_color = '#SLATE#',
learn_on = { side_effect=true },
power = 2,
is_resist = function(self, who, learn)
-- Free action resists paralysis.
return who:has('FREE_ACT')
end,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
-- Slight Hack(TM) to prevent things like perma-paralysis from Floating
-- eyes. [cf. make_attack_normal() in src/melee1.c]
if target:hasEffect(target.EFF_PARALYZED) then dam = math.max(dam, 1) end
if tmp.resist then
game.logSeen(target, '%s is unaffected.', target:getName{def_art=true, check_seen=true}:capitalize())
tmp.learn = true
elseif target.saving_throw and rng.percent(target.saving_throw) then
game.logSeen(target, '%s is unaffected.', target:getName{def_art=true, check_seen=true}:capitalize())
tmp.learn = true
else
local rlev = math.max(self.level or 0, 1)
target:setEffect(target.EFF_PARALYZED, 3 + rng.range(1, rlev), {merge='combine'})
tmp.learn = true
end
return dam
end,
}
newDamageType {
name = 'drain STR',
type = 'LOSE_STR',
text_color = '#SLATE#',
learn_on = { side_effect=true },
power = 0,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
if not target.player then return end
local down, sust = target:bumpStatDown('STR')
tmp.learn = down or sust
end,
}
newDamageType {
name = 'drain INT',
type = 'LOSE_INT',
text_color = '#SLATE#',
learn_on = { side_effect=true },
power = 0,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
if not target.player then return end
local down, sust = target:bumpStatDown('INT')
tmp.learn = down or sust
end,
}
newDamageType {
name = 'drain WIS',
type = 'LOSE_WIS',
text_color = '#SLATE#',
learn_on = { side_effect=true },
power = 0,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
if not target.player then return end
local down, sust = target:bumpStatDown('WIS')
tmp.learn = down or sust
end,
}
newDamageType {
name = 'drain DEX',
type = 'LOSE_DEX',
text_color = '#SLATE#',
learn_on = { side_effect=true },
power = 0,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
if not target.player then return end
local down, sust = target:bumpStatDown('DEX')
tmp.learn = down or sust
end,
}
newDamageType {
name = 'drain CON',
type = 'LOSE_CON',
text_color = '#SLATE#',
learn_on = { side_effect=true },
power = 0,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
if not target.player then return end
local down, sust = target:bumpStatDown('CON')
tmp.learn = down or sust
end,
}
newDamageType {
name = 'drain CHA',
type = 'LOSE_CHR',
text_color = '#SLATE#',
learn_on = { side_effect=true },
power = 0,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
if not target.player then return end
local down, sust = target:bumpStatDown('CHA')
tmp.learn = down or sust
end,
}
newDamageType {
name = 'draon stats',
type = 'LOSE_ALL',
text_color = '#SLATE#',
learn_on = { side_effect=true },
power = 2,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
if not target.player then return end
local Stats = require 'mod.class.interface.ActorStats'
for _, s in ipairs(Stats.stats) do
local down, sust = target:bumpStatDown(s)
tmp.learn = down or sust or tmp.learn
end
end,
}
newDamageType {
name = 'shatter',
type = 'SHATTER',
text_color = '#SLATE#',
power = 60,
}
newDamageType {
name = 'minor exp drain',
type = 'EXP_10',
text_color = '#SLATE#',
power = 5,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
util.plyr_lose_exp(target, rng.dice(10, 6), 100, {hold_life_pct=95})
end,
}
newDamageType {
name = 'light exp drain',
type = 'EXP_20',
text_color = '#SLATE#',
power = 5,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
util.plyr_lose_exp(target, rng.dice(20, 6), 100, {hold_life_pct=90})
end,
}
newDamageType {
name = 'exp drain',
type = 'EXP_40',
text_color = '#SLATE#',
power = 5,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
util.plyr_lose_exp(target, rng.dice(40, 6), 100, {hold_life_pct=75})
end,
}
newDamageType {
name = 'heavy exp drain',
type = 'EXP_80',
text_color = '#SLATE#',
power = 5,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
util.plyr_lose_exp(target, rng.dice(80, 6), 100, {hold_life_pct=50})
end,
}
newDamageType {
name = 'disease',
type = 'DISEASE',
text_color = '#SLATE#',
text_color = '#SLATE#',
learn_on = { side_effect=true },
power = 5,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
if not target:resistDam(DamageType.POISON, {or_immune=true}) then
target:setEffect(target.EFF_POISON, rng.range(1, src.level or 1) + 5, {merge='combine'})
tmp.learn = true
end
if target.player and rng.percent(10) then
local down, sust = target:bumpStatDown('CON', {temporary=rng.percent(99)})
tmp.learn = tmp.learn or down or sust
end
end,
}
newDamageType {
name = 'time',
type = 'TIME',
text_color = '#C__W#',
cloud_color = { colors.C_w, colors.C_D },
power = 5,
floor_dam = { tree = true },
resist_reduce = resist_reduce_dam_div_7_12_make(3, 4),
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if not target.player or tmp.resist or tmp.immune then return end
local Stats = require 'mod.class.interface.ActorStats'
if rng.percent(50) then
util.plyr_lose_exp(target, 100, 100, {msg='You feel life has clocked back.', hold_life_pct=0})
elseif rng.percent(80) then
local s = rng.table(Stats.stats)
game.logPlayer(target, "You're not as %s as you used to be...", Stats.stat_change_desc[s][1])
-- Close enough...
target:reduceStat(s, 50)
else
game.logPlayer(target, "You're not as powerful as you used to be...")
for _, s in ipairs(Stats.stats) do
-- Close enough...
target:reduceStat(s, 50)
end
end
end,
}
newDamageType {
name = 'sanity',
type = 'INSANITY',
text_color = '#SLATE#',
power = 60,
damage_is_power = true,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if target.player then target:takeSanityHit(dam, src) end
end,
}
newDamageType {
name = 'hallucination',
type = 'HALLUCINATION',
text_color = '#SLATE#',
learn_on = { side_effect=true },
power = 10,
is_resist = function(self, who, learn)
return who:resistDam(DamageType.CHAOS, {learn=learn})
end,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
-- In T2 source, only player gets hallucination from this damage type.
if target == game.player and src ~= game.player and not tmp.resist and not tmp.immune then
target:setEffect(target.EFF_HALLUCINATION, 3 + rng.range(1, math.floor((src.level or 2)/2)), {merge='combine'})
tmp.learn = true
end
end,
}
newDamageType {
name = 'parasite',
type = 'PARASITE',
text_color = '#SLATE#',
power = 5,
}
newDamageType {
name = 'abomination',
type = 'ABOMINATION',
text_color = '#SLATE#',
power = 20,
}
newDamageType {
name = 'suffocation',
type = 'UNBREATH',
text_color = '#C__G#',
cloud_color = { colors.C_G, colors.C_G, colors.C_g, colors.C_g, colors.C_g, colors.C_g, colors.C_g },
is_immune = function(self, who, learn)
if who:has('NONLIVING') or who:has('UNDEAD') then
return true, ' is immune.'
end
return false
end,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
if not tmp.immune and rng.percent(15) then
-- Poison duration for NPCs in T2 depends on distance from the
-- center of the ball.
local dur = reduce_by_dist(rng.range(10, 20), p)
target:setEffect(target.EFF_POISON, dur, { merge='combine', src=src })
end
end,
}
newDamageType {
name = 'corpse explosion',
type = 'CORPSE_EXPL',
text_color = '#SLATE#',
}
newDamageType {
name = 'missile',
type = 'MISSILE',
text_color = '#C__S#',
cloud_color = colors.C_s,
}
newDamageType {
name = 'arrow',
type = 'ARROW',
text_color = '#C_U#',
cloud_color = colors.C_U,
}
newDamageType {
name = 'plasma',
type = 'PLASMA',
text_color = '#C_R#',
cloud_color = { colors.C_r, colors.C_R, colors.C_R, colors.C_R, colors.C_R },
floor_dam = { fire = true, tree = true, floor = { SHALLOW_LAVA = 10, ASH = 15 } },
resist_reduce = resist_reduce_dam_div_7_12_make(3),
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if target == game.player and src ~= game.player then
if not target:resistDam(DamageType.SOUND) then
local dur = rng.range(1, math.min(35, 5 + math.floor(dam*3/4)))
target:setEffect(target.EFF_STUN, dur, { merge='combine' })
end
try_damage_player_inven(src, target, DamageType.ACID, nil, 3, DamageType.FIRE)
end
end,
}
local function water_hit_player(dt, src, target, x, y, type, dam, p, tmp)
if target == game.player and src ~= game.player then
if not target:resistDam(DamageType.SOUND) then
target:setEffect(target.EFF_STUN, rng.range(1, 40), { merge='combine' })
end
if not target:resistDam(DamageType.CONFUSION) then
target:setEffect(target.EFF_CONFUSED, 5 + rng.range(1, 5), { merge='combine' })
end
if rng.chance(5) then
try_damage_player_inven(src, target, DamageType.COLD, nil, 3)
end
end
end
newDamageType {
name = 'wave',
type = 'WAVE',
text_color = '#C__B#',
cloud_color = { colors.C_B, colors.C_b, colors.C_b, colors.C_b },
floor_dam = {
water = function(_, _, dam) return dam >= 30 end,
},
resist_reduce = resist_reduce_dam_div_7_12_make(3),
is_immune = function(self, who, learn)
-- Slight Hack(TM): Water elemental-y things are immune to water.
if self.display == 'E' and self.name:find('Water', 1, true) then
return true, ' is immune.'
end
return false
end,
is_resist = function(self, who, learn)
return who:resistDam(DamageType.WATER, {learn=learn})
end,
side_effects_pre = water_hit_player,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
if target ~= game.player and not tmp.immune and not tmp.resist then
local srcx, srcy = src.x, src.y
if _G.type(dam) == 'table' and dam.x and dam.y then
srcx, srcy = dam.x, dam.y
end
target:knockback(srcx, srcy, 2)
end
end,
}
newDamageType {
name = 'water',
type = 'WATER',
text_color = '#C__B#',
cloud_color = { colors.C_B, colors.C_b, colors.C_b, colors.C_b },
floor_dam = {
water = function(_, _, dam) return spell.dam_num(dam) >= 30 end,
},
is_immune = function(self, who, learn)
-- Slight Hack(TM): Water elemental-y things are immune to water.
if self.display == 'E' and self.name:find('Water', 1, true) then
return true, ' is immune.'
end
return false
end,
resist_reduce = resist_reduce_dam_div_7_12_make(3),
side_effects_pre = water_hit_player,
}
newDamageType {
name = 'light',
type = 'LITE',
text_color = '#C__Y#',
cloud_color = { colors.C_o, colors.C_y, colors.C_y, colors.C_y },
resist_reduce = resist_reduce_dam_div_7_12_make(2),
vuln_increase = function(dam, target)
return dam*2, ' cringes from the light!'
end,
grid_side_effect = function(self, g, x, y, who)
game.level.map.lites(x, y, true)
end,
}
newDamageType {
name = 'darkness',
type = 'DARK',
text_color = '#C_D#',
cloud_color = { colors.C_d, colors.C_D, colors.C_D, colors.C_D },
grid_side_effect = function(self, g, x, y, who)
game.level.map.lites(x, y, false)
end,
}
newDamageType {
name = 'weak light',
type = 'LITE_WEAK',
text_color = '#C__Y#',
cloud_color = { colors.C_o, colors.C_y, colors.C_y },
harmless_unless_vulnerable = true,
grid_side_effect = function(self, g, x, y, who)
game.level.map.lites(x, y, true)
end,
}
newDamageType {
name = 'weak darkness',
type = 'DARK_WEAK',
text_color = '#C_D#',
cloud_color = { colors.C_d, colors.C_D, colors.C_D },
grid_side_effect = function(self, g, x, y, who)
game.level.map.lites(x, y, false)
end,
}
newDamageType {
name = 'shards',
type = 'SHARDS',
text_color = '#C__S#',
cloud_color = { colors.C_u, colors.C_u, colors.C_s, colors.C_s, colors.C_s },
floor_dam = { tree = true },
resist_reduce = resist_reduce_dam_div_7_12_make(3, 6),
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
if not tmp.resist and not tmp.immune then
if target == game.player and src ~= game.player then
target:setEffect(target.EFF_CUT, dam, { merge='combine', src=src })
-- TODO damage player inventory
end
end
end,
}
newDamageType {
name = 'sound',
type = 'SOUND',
text_color = '#C__W#',
cloud_color = { colors.C_v, colors.C_w, colors.C_w, colors.C_w },
resist_reduce = resist_reduce_dam_div_7_12_make(2, 5),
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if not tmp.resist and not tmp.immune then
local dur = rng.range(1, math.min(35, 5 + math.floor(dam/3)))
if target ~= game.player then
dur = reduce_by_dist(rng.range(11, 25), p)
-- NPCs get a save vs. attacker level.
if rng.range(0, 99 - src.level) < 50 then dur = 0 end
end
target:setEffect(target.EFF_STUN, dur, { merge='combine' })
try_damage_player_inven(src, target, DamageType.COLD, nil, 2, type, 13)
end
end,
}
newDamageType {
name = 'force',
type = 'FORCE',
text_color = '#C__O#',
cloud_color = { colors.C_W, colors.C_W, colors.C_o, colors.C_o, colors.C_o },
floor_dam = { tree = true },
resist_reduce = resist_reduce_dam_div_7_12_make(3),
is_resist = function(self, who, learn)
return who:knowTalent(who.T_NPC_BR_WALL)
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
-- Knock back.
local srcx, srcy = src.x, src.y
if _G.type(dam) == 'table' and dam.x and dam.y then
srcx, srcy = dam.x, dam.y
end
target:knockback(srcx, srcy, 2)
end,
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
-- If fired by monster at player or another monster, stun.
if src ~= game.player then
local dur = target == game.player and rng.range(1, 20) or reduce_by_dist(rng.range(1, 15), p)
target:setEffect(target.EFF_STUN, dur, {merge='combine'})
end
end,
}
newDamageType {
name = 'inertia',
type = 'INERTIA',
text_color = '#C_W#',
cloud_color = { colors.C_s, colors.C_s, colors.C_W, colors.C_W, colors.C_W },
resist_reduce = resist_reduce_dam_div_7_12_make(3),
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
if not tmp.resist then
target:setEffect(target.EFF_SLOW, rng.range(5, 8), { merge='combine' })
end
end,
}
newDamageType {
name = 'mana',
type = 'MANA',
text_color = '#C__V#',
cloud_color = { colors.C_v, colors.C_v, colors.C_v, colors.C_v, colors.C_B },
}
newDamageType {
name = 'meteor',
type = 'METEOR',
text_color = '#C__U#',
cloud_color = { colors.C_r, colors.C_u, colors.C_u },
floor_dam = { fire = true, tree = true, floor = { SHALLOW_LAVA = 10, ASH = 15 } },
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if not target:immuneDam(DamageType.FIRE) then
try_damage_player_inven(src, target, DamageType.FIRE, nil, 2, DamageType.SHARDS, 13)
end
try_damage_player_inven(src, target, DamageType.COLD, nil, 2, DamageType.SHARDS, 13)
end,
}
newDamageType {
name = 'ice',
type = 'ICE',
text_color = '#C__W#',
cloud_color = { colors.C_B, colors.C_w, colors.C_w, colors.C_w },
floor_dam = { floor = { ICE = 50 } },
resist_reduce = simple_resist_reduce,
vuln_increase = function(dam, target) return dam*3 end,
is_vulnerable = function(self, who, learn)
return who:vulnDam(DamageType.COLD, {learn=learn})
end,
is_resist = function(self, who, learn)
return who:resistDam(DamageType.COLD, {learn=learn})
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
-- TODO stun and cuts
if not target:immuneDam(DamageType.COLD) then
try_damage_player_inven(src, target, DamageType.COLD, nil, 2, nil, 12)
end
end,
}
newDamageType {
name = 'chaos',
type = 'CHAOS',
text_color = '#C__R#',
cloud_color = { colors.C_r, colors.C_g, colors.C_b, colors.C_y, colors.C_o, colors.C_v, colors.C_R, colors.C_G, colors.C_B, colors.C_u, colors.C_U, colors.C_s, colors.C_w, colors.C_W, colors.C_D },
resist_reduce = resist_reduce_dam_div_7_12_make(3, 6),
is_resist = function(self, who, learn)
-- Slight Hack(TM): Demons resist chaos with probability 1/3.
if who:has('DEMON') and rng.chance(3) then return true, ' resists.' end
-- Caller will catch resist chaos for us.
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if not target:resistDam(DamageType.CONFUSION, {or_immune=true}) then
local dur = rng.range(10, 30)
if target ~= game.player then
dur = reduce_by_dist(rng.range(6, 16), p)
end
target:setEffect(target.EFF_CONFUSED, dur, { merge='combine' })
end
-- TODO polymorph if target didn't resist.
if target.player and not tmp.resist and not tmp.immune then
target:setEffect(target.EFF_HALLUCINATION, rng.range(1, 10), {merge='combine'})
if not target:resistDam(DamageType.NETHER, {or_immune=true}) then
util.plyr_lose_exp(target, 5000, 100)
end
end
try_damage_player_inven(src, target, DamageType.ELEC, nil, 2, type, 9)
try_damage_player_inven(src, target, DamageType.FIRE, nil, 2, type, 9)
end,
}
newDamageType {
name = 'nether',
type = 'NETHER',
text_color = '#C_D#',
cloud_color = { colors.C_s, colors.C_D, colors.C_D, colors.C_D },
floor_dam = { tree = true },
resist_reduce = function(dam, target, type)
if target.resist_dam and target.resist_dam[DamageType.NETHER] then
-- Actual resist nether gives good resist.
return resist_reduce_dam_div_7_12(dam, 3, target, type)
elseif target:has('EVIL') then
-- Evilness gives partial resist.
return dam/2
end
end,
is_immune = function(self, who, learn)
if who:has('UNDEAD') then
if learn and who.learnFlags then who:learnFlags('flags', 'UNDEAD') end
return true, ' is immune.'
end
return false
end,
is_resist = function(self, who, learn)
if who.resist_dam and who.resist_dam[DamageType.NETHER] then
if learn and who.learnFlags then
who:learnFlags('resist_dam', DamageType.NETHER)
end
-- Actual resist nether gives good resist.
return true, ' resists.'
elseif who:has('EVIL') then
-- Evilness gives partial resist.
if learn and who.learnFlags then who:learnFlags('flags', 'EVIL') end
return true, ' resists somewhat.'
end
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if target.player and not tmp.resist and not tmp.immune then
util.plyr_lose_exp(target, 2000, 100)
end
end,
}
newDamageType {
name = 'disenchantment',
type = 'DISENCHANT',
text_color = '#C_B#',
power = 20,
cloud_color = { colors.C_B, colors.C_B, colors.C_B, colors.C_B, colors.C_v },
resist_reduce = resist_reduce_dam_div_7_12_make(3, 6),
learn_on = { not_resist=true },
side_effects_post = function(self, src, target, x, y, type, dam, p, tmp)
if not tmp.resist and not tmp.immune then spell.disenchant(nil, target) end
end,
}
newDamageType {
name = 'nexus',
type = 'NEXUS',
text_color = '#C__V#',
cloud_color = { colors.C_R, colors.C_R, colors.C_v, colors.C_v, colors.C_v },
floor_dam = { tree = true },
resist_reduce = resist_reduce_dam_div_7_12_make(3, 6),
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
-- TODO nexus effects for player
end,
}
newDamageType {
name = 'gravity',
type = 'GRAVITY',
text_color = '#C__U#',
cloud_color = { colors.C_U, colors.C_u, colors.C_u },
resist_reduce = resist_reduce_dam_div_7_12_make(3),
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
-- TODO stun, slow, teleport
if not target:has('FEATHER') or rng.chance(13) then
try_damage_player_inven(src, target, DamageType.COLD, nil, 2, type)
end
end,
}
newDamageType {
name = 'wall destruction',
type = 'KILL_WALL',
text_color = '#SLATE#',
harmless_unless_vulnerable = true,
target_flags = {
block_path = DamageType.pass_wall_block_path,
block_radius = DamageType.pass_wall_block_radius,
},
post_project = function(self, who, t, x, y, _, _, _, _, _, _)
spell.clobber_FOVs(x, y)
end,
}
-- A duplicate of KILL_WALL to use for digging with a digger. Basically
-- the difference is that this damage type won't print a 'turns to mud'
-- message.
newDamageType {
name = 'digging',
type = 'DIG',
text_color = '#SLATE#',
harmless_unless_vulnerable = true,
post_project = function(self, who, t, x, y, _, _, _, _, _, _)
spell.clobber_FOVs(x, y)
end,
}
newDamageType {
name = 'door destruction',
type = 'KILL_DOOR',
text_color = '#SLATE#',
grid_side_effect = function(self, g, x, y, who)
-- This damage type also destroys traps, apparently.
-- [cf. project_f() in src/spells1.c]
local p = {
force = true,
no_exp = true,
msg = 'There is a bright flash of light!',
}
local t = game.level.map(x, y, Map.TRAP)
if t then t:disarm(x, y, who, p) end
end,
post_project = function(self, who, t, x, y, _, _, _, _, _, _)
spell.clobber_FOVs(x, y)
end,
}
newDamageType {
name = 'trap destruction',
type = 'KILL_TRAP',
text_color = '#SLATE#',
harmless_unless_vulnerable = true,
grid_side_effect = function(self, g, x, y, who)
-- This also reveals secret doors, handled in grid definition.
-- [cf. project_f() in src/spells1.c]
local p = {
force = true,
no_exp = true,
msg = 'There is a bright flash of light!',
}
local t = game.level.map(x, y, Map.TRAP)
if t then t:disarm(x, y, who, p) end
end
}
newDamageType {
name = 'create door',
type = 'MAKE_DOOR',
text_color = '#SLATE#',
harmless_unless_vulnerable = true,
floor_dam = {
floor = function(grid, x, y, _, _)
if grid:clean_bold(x, y) and not grid.permanent then
return game.zone.grid_list.DOOR
end
return nil
end,
},
post_project = function(self, who, t, x, y, _, _, _, _, _, _)
spell.clobber_FOVs(x, y)
end,
}
newDamageType {
name = 'create trap',
type = 'MAKE_TRAP',
text_color = '#SLATE#',
harmless_unless_vulnerable = true,
floor_dam = {
floor = function(grid, x, y, _, _)
if grid:clean_bold(x, y) and not grid.permanent then
local gen = trap_util.generator()
if gen then gen:generateOne{x=x, y=y, force_lvl1=true} end
end
return nil
end,
},
}
newDamageType {
name = 'clone',
type = 'OLD_CLONE',
text_color = '#SLATE#',
}
newDamageType {
name = 'polymorph',
type = 'OLD_POLY',
text_color = '#SLATE#',
damage_is_power = true,
is_immune = function(self, who)
if who.unique or who.quest_mon then
return true, ' is unaffected!'
end
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
tmp.immune = tmp.immune or save_level_vs_power(target, dam, 10, ' is unaffected!')
if not tmp.immune then
-- TODO polymorph
end
end,
}
newDamageType {
name = 'healing',
type = 'OLD_HEAL',
text_color = '#SLATE#',
damage_is_power = true,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
-- Wake up target.
if target.ai_state then
target.ai_state.sleep = 0
target.changed = true
end
target:heal(dam)
game.logSeen(target, '%s looks healthier.', target:getName{def_art=true, check_seen=true}:capitalize())
end,
}
newDamageType {
name = 'speed',
type = 'OLD_SPEED',
text_color = '#SLATE#',
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if target.player then
target:setEffect(target.EFF_SPEED, rng.range(1, 5), { speed=10, merge='combine' })
else
game.logSeen(target, '%s starts moving faster.', target:getName{def_art=true, check_seen=true}:capitalize())
target.speed = math.min(target.base_speed + 15, target.speed + 10)
target:computeGlobalSpeed()
target.changed = true
end
end,
}
newDamageType {
name = 'slow',
type = 'OLD_SLOW',
text_color = '#SLATE#',
damage_is_power = true,
is_immune = function(self, who)
if who.unique then
return true, ' is unaffected!'
end
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
tmp.immune = tmp.immune or save_level_vs_power(target, dam, 10, ' is unaffected!')
if not tmp.immune then
if target.player then
target:setEffect(target.EFF_SLOW, rng.range(5, 8), {merge='combine'})
else
game.logSeen(target, '%s starts moving slower.', target:getName{def_art=true, check_seen=true}:capitalize())
target.speed = math.max(-50, target.speed - 10)
target:computeGlobalSpeed()
target.changed = true
end
end
end,
}
newDamageType {
name = 'confusion',
type = 'OLD_CONF',
text_color = '#SLATE#',
damage_is_power = true,
is_immune = function(self, who, learn)
if who:resistDam(DamageType.CONFUSION, {learn=learn}) then
return true, ' is unaffected!'
end
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
tmp.immune = tmp.immune or save_level_vs_power(target, dam, 10, ' is unaffected!')
if not tmp.immune then
local dur = rng.dice(3, math.floor(dam/2)) + 1
target:setEffect(target.EFF_CONFUSED, dur, {merge='combine'})
end
end,
}
newDamageType {
name = 'sleep',
type = 'OLD_SLEEP',
text_color = '#SLATE#',
damage_is_power = true,
is_immune = function(self, who, learn)
if who:resistDam(DamageType.OLD_SLEEP, {learn=learn}) then
return true, 'is unaffected!'
end
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
tmp.immune = tmp.immune or save_level_vs_power(target, dam, 10, ' is unaffected!')
if not tmp.immune then
-- Put target to sleep
if target.ai_state then
game.logSeen(target, '%s falls asleep!', target:getName{def_art=true, check_seen=true}:capitalize())
target.ai_state.sleep = 500
target.changed = true
end
end
end,
}
newDamageType {
name = 'life drain',
type = 'OLD_DRAIN',
text_color = '#SLATE#',
is_immune = function(self, who, learn)
if who:has('UNDEAD') or who:has('DEMON') or who:has('NONLIVING') or
who.display == 'E' or who.display == 'g' or who.display == 'v'
then
if learn and who.learnFlags then
who:learnFlags('flags', 'UNDEAD', 'DEMON')
end
return true, ' is unaffected.'
end
return false
end,
}
newDamageType {
name = 'teleport undead',
type = 'TELEPORT_UNDEAD',
text_color = '#SLATE#',
damage_is_power = true,
is_immune = function(self, who, learn)
if not who:has('UNDEAD') then return true end
if who:resistDam(DamageType.TELEPORT, {learn=learn}) then
if who.unique then return true, ' is unaffected!' end
if rng.percent(who.level or 0) then return true, ' resists!' end
end
if learn and who.learnFlags then who:learnFlags('flags', 'UNDEAD') end
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if not spell.level_ok_teleport() then return end
if not tmp.immune then
game.logSeen(target, '%s disappears!', target:getName{def_art=true, check_seen=true}:capitalize())
spell.teleport_away(src, target, dam)
end
end,
}
newDamageType {
name = 'teleport evil',
type = 'TELEPORT_EVIL',
text_color = '#SLATE#',
damage_is_power = true,
is_immune = function(self, who, learn)
if not who:has('EVIL') then return true end
if who:resistDam(DamageType.TELEPORT, {learn=learn}) then
if who.unique then return true, ' is unaffected!' end
if rng.percent(who.level or 0) then return true, ' resists!' end
end
if learn and who.learnFlags then who:learnFlags('flags', 'EVIL') end
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if not spell.level_ok_teleport() then return end
if not tmp.immune then
game.logSeen(target, '%s disappears!', target:getName{def_art=true, check_seen=true}:capitalize())
spell.teleport_away(src, target, dam)
end
end,
}
newDamageType {
name = 'teleport',
type = 'TELEPORT',
text_color = '#SLATE#',
damage_is_power = true,
is_immune = function(self, who, learn)
if who:resistDam(DamageType.TELEPORT, {learn=learn}) then
if who.unique then return true, ' is unaffected!' end
if rng.percent(who.level or 0) then return true, ' resists!' end
end
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if not spell.level_ok_teleport() then return end
if not tmp.immune then
game.logSeen(target, '%s disappears!', target:getName{def_art=true, check_seen=true}:capitalize())
spell.teleport_away(src, target, dam)
end
end,
}
newDamageType {
name = 'turn undead',
type = 'TURN_UNDEAD',
text_color = '#SLATE#',
damage_is_power = true,
is_immune = function(self, who, learn)
if who:has('UNDEAD') then
if learn and who.learnFlags then who:learnFlags('flags', 'UNDEAD') end
return false
end
return true, ' is unaffected!'
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
tmp.immune = tmp.immune or save_level_vs_power(target, dam, 10, ' is unaffected!')
if not tmp.immune then
local dur = rng.dice(3, math.floor(dam/2)) + 1
target:setEffect(target.EFF_AFRAID, dur, { merge='combine' })
end
end,
}
newDamageType {
name = 'turn evil',
type = 'TURN_EVIL',
text_color = '#SLATE#',
damage_is_power = true,
is_immune = function(self, who, learn)
if who:has('EVIL') then
if learn and who.learnFlags then who:learnFlags('flags', 'EVIL') end
return false
end
return true, ' is unaffected!'
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
tmp.immune = tmp.immune or save_level_vs_power(target, dam, 10, ' is unaffected!')
if not tmp.immune then
local dur = rng.dice(3, math.floor(dam/2)) + 1
target:setEffect(target.EFF_AFRAID, dur, { merge='combine' })
end
end,
}
newDamageType {
name = 'turn all',
type = 'TURN_ALL',
text_color = '#SLATE#',
damage_is_power = true,
is_immune = function(self, who, learn)
if who.unique or who:resistDam(DamageType.FEAR, {learn=learn}) then
return true, ' is unaffected!'
end
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
tmp.immune = tmp.immune or save_level_vs_power(target, dam, 10, ' is unaffected!')
if not tmp.immune and not tmp.resist then
local dur = rng.dice(3, math.floor(dam/2)) + 1
target:setEffect(target.EFF_AFRAID, dur, { merge='combine' })
end
end,
}
newDamageType {
name = 'dispel undead',
type = 'DISP_UNDEAD',
text_color = '#SLATE#',
harmless_unless_vulnerable = true,
is_vulnerable = function(self, who, learn)
if who:has('UNDEAD') then
if learn and who.learnFlags then who:learnFlags('flags', 'UNDEAD') end
return true, ' shudders.'
end
return false
end,
}
newDamageType {
name = 'dispel evil',
type = 'DISP_EVIL',
text_color = '#SLATE#',
harmless_unless_vulnerable = true,
is_vulnerable = function(self, who, learn)
if who:has('EVIL') then
if learn and who.learnFlags then who:learnFlags('flags', 'EVIL') end
return true, ' shudders.'
end
return false
end,
}
newDamageType {
name = 'dispel all',
type = 'DISP_ALL',
text_color = '#SLATE#',
harmless_unless_vulnerable = true,
is_vulnerable = function(self, who, learn)
-- Slight Hack(TM): Everything is "vulnerable" so that we get the
-- "shudders" message.
return true, ' shudders.'
end,
}
newDamageType {
name = 'dispel demon',
type = 'DISP_DEMON',
text_color = '#SLATE#',
harmless_unless_vulnerable = true,
is_vulnerable = function(self, who, learn)
if who:has('DEMON') then
if learn and who.learnFlags then who:learnFlags('flags', 'DEMON') end
return true, ' shudders.'
end
return false
end,
}
newDamageType {
name = 'dispel living',
type = 'DISP_LIVING',
text_color = '#SLATE#',
harmless_unless_vulnerable = true,
is_immune = function(self, who, learn)
if who:has('UNDEAD') or who:has('NONLIVING') then return false end
return true, ' shudders.'
end,
}
newDamageType {
name = 'rocket',
type = 'ROCKET',
text_color = '#C_R#',
cloud_color = { colors.C_R, colors.C_R, colors.C_R, colors.C_r, colors.C_U, colors.C_U },
resist_reduce = function(dam, target, type) return dam/2 end,
is_resist = function(self, who, learn)
if who:resistDam(DamageType.SHARDS, {learn=learn}) then
return true, ' resists somewhat.'
end
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if not tmp.resist and not tmp.immune then
local dur = reduce_by_dist(rng.range(11, 25), p)
if target == game.player then dur = math.floor(dam/2) end
target:setEffect(target.EFF_CUT, dur, {merge='combine', src=src})
end
if target == game.player and not target:resistDam(DamageType.SOUND, {or_immune=true}) then
target:setEffect(target.EFF_STUN, rng.range(1, 20), {merge='combine'})
end
try_damage_player_inven(src, target, DamageType.COLD, nil, 3, DamageType.SHARDS, 12)
end,
}
newDamageType {
name = 'nuke',
type = 'NUKE',
text_color = '#C__R#',
cloud_color = { colors.C_r, colors.C_g },
floor_dam = { tree = true },
is_resist = function(self, who, learn)
return who:resistDam(DamageType.POISON, {learn=learn})
end,
resist_reduce = resist_reduce_dam_div_7_12_make(3),
-- TODO 1/3 chance of polymorph for NPC
-- TODO 1/5 chance of polymorph (1/4) or corruption (3/4) for player
-- [we'll probably punt corruption...]
-- TODO 1/6 chance of acid-style inventory damage for player.
}
newDamageType {
name = 'glyph',
type = 'MAKE_GLYPH',
text_color = '#SLATE#',
harmless_unless_vulnerable = true,
floor_dam = {
floor = function(grid, x, y, _, _)
if grid:clean_bold(x, y) and not grid.permanent then
return game.zone.grid_list.GLYPH_OF_WARDING
end
return nil
end,
},
}
newDamageType {
name = 'stasis',
type = 'STASIS',
text_color = '#SLATE#',
damage_is_power = true,
is_immune = function(self, who, learn)
if who.unique then
return true, 'is unaffected!'
end
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
tmp.immune = tmp.immune or save_level_vs_power(target, dam, 10, ' is unaffected!')
if not tmp.immune then
-- Put target to sleep
if target.ai_state then
game.logSeen(target, '%s falls asleep!', target:getName{def_art=true, check_seen=true}:capitalize())
target.ai_state.sleep = 500
target.changed = true
end
end
end,
}
newDamageType {
name = 'create wall',
type = 'STONE_WALL',
text_color = '#SLATE#',
floor_dam = {
floor = function(grid, x, y, _, dam)
-- Slight Hack(TM): Turn the center of the Stone Wall ball into
-- floor instead of wall. [cf. wall_stone() in src/spell2.c]
local center = type(dam) == 'table' and dam.cx and dam.cy and dam.cx == x and dam.cy == y
if center and not grid.permanent and not grid.wall then
return game.zone.grid_list.FLOOR
elseif grid:clean_bold(x, y) and not grid.permanent and grid.floor then
return game.zone.grid_list.WALL
end
return nil
end,
},
post_project = function(self, who, t, x, y, _, _, _, _, _, _)
spell.clobber_FOVs(x, y)
end,
}
newDamageType {
name = 'stun',
type = 'STUN',
text_color = '#SLATE#',
damage_is_power = true,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
tmp.immune = tmp.immune or save_level_vs_power(target, dam, 5, ' is unaffected!')
if not tmp.immune then
local dur = rng.dice(math.floor((src.level or 0)/10) + 3, dam) + 1
target:setEffect(target.EFF_STUN, dur, {merge='combine'})
end
end,
}
newDamageType {
name = 'holy fire',
type = 'HOLY_FIRE',
text_color = '#C__W#',
cloud_color = { colors.C_o, colors.C_w, colors.C_w, colors.C_w, colors.C_w },
floor_dam = { fire = true, tree = true, floor = { SHALLOW_LAVA = 10, ASH = 15 } },
vuln_increase = function(dam, target) return dam*2 end,
resist_reduce = resist_reduce_dam_div_7_12_make(3),
is_immune = function(self, who, learn)
if who:has('GOOD') then
if learn and who.learnFlags then who:learnFlags('flags', 'GOOD') end
return true, ' is immune.'
end
return false
end,
is_vulnerable = function(self, who, learn)
if who:has('EVIL') then
if learn and who.learnFlags then who:learnFlags('flags', 'EVIL') end
return true, ' is hit hard.'
end
return false
end,
is_resist = function(self, who, learn)
if not who:has('GOOD') and not who:has('EVIL') then
return true, ' resists.'
end
return false
end,
}
newDamageType {
name = 'hellfire',
type = 'HELL_FIRE',
text_color = '#C_D#',
cloud_color = { colors.C_r, colors.C_D, colors.C_D, colors.C_D, colors.C_D, colors.C_D },
floor_dam = { fire = true, tree = true, floor = { SHALLOW_LAVA = 10, ASH = 15 } },
is_vulnerable = function(self, who, learn)
if who:has('EVIL') then
if learn and who.learnFlags then who:learnFlags('flags', 'EVIL') end
return true, ' is hit hard.'
end
return false
end,
vuln_increase = function(dam, target) return dam*2 end,
}
-- Used by Fiery Shield spell; basically copy of PHYSICAL with a bit of
-- flavor.
newDamageType {
name = 'shieldfire',
type = 'SHIELDFIRE',
text_color = '#C__Y#',
power = 60,
}
newDamageType {
name = 'disintegration',
type = 'DISINTEGRATE',
text_color = '#C_D#',
cloud_color = { colors.C_D, colors.C_o, colors.C_U },
floor_dam = { tree = 30, floor = { ASH = 30 } },
vuln_increase = function(dam, target) return dam*2 end,
resist_reduce = function(dam, target, type) return dam/8 end,
is_vulnerable = function(self, who, learn)
if who:vulnDam(DamageType.KILL_WALL) then
return true, ' loses some skin!'
end
return false
end,
is_resist = function(self, who, learn)
if who:has('UNIQUE') then
-- TODO check target level vs. source level
end
return false
end,
}
newDamageType {
name = 'charm',
type = 'CHARM',
text_color = '#SLATE#',
damage_is_power = true,
is_immune = function(self, who, learn)
if who:resistDam(DamageType.CONFUSION, {learn=learn}) or who.quest_mon then
return true, ' is unaffected!'
end
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
dam = dam + src:getStatRelated('cha_charm')
tmp.immune = tmp.immune or save_level_vs_power(target, dam, 5, ' is unaffected!')
if not tmp.immune and src:has('AGGRAVATE') then
-- Slight Hack(TM): Assume only the player will be slinging this
-- damage type.
game.logSeen(target, '%s hates you too much!', target:getName{def_art=true, check_seen=true}:capitalize())
tmp.immune = true
end
if not tmp.immune then
game.logSeen(target, '%s suddenly seems friendly!', target:getName{def_art=true, check_seen=true}:capitalize())
target.faction = spell.friendly_faction(src)
target.changed = true
end
end,
}
newDamageType {
name = 'dominate undead',
type = 'CONTROL_UNDEAD',
text_color = '#SLATE#',
damage_is_power = true,
is_immune = function(self, who, learn)
if who.unique or who.quest_mon then return true, ' is unaffected!' end
if not who:has('UNDEAD') then return true, ' is not undead and thus unaffected.' end
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
tmp.immune = tmp.immune or save_level_vs_power(target, dam, 10, ' is unaffected!')
if not tmp.immune and src:has('AGGRAVATE') then
-- Slight Hack(TM): Assume only the player will be slinging this
-- damage type.
game.logSeen(target, '%s hates you too much!', target:getName{def_art=true, check_seen=true}:capitalize())
tmp.immune = true
end
if not tmp.immune then
game.logSeen(target, '%s is in your thrall!', target:getName{def_art=true, check_seen=true}:capitalize())
target.faction = spell.friendly_faction(src)
target.changed = true
end
end,
}
newDamageType {
name = 'tame animal',
type = 'CONTROL_ANIMAL',
text_color = '#SLATE#',
damage_is_power = true,
is_immune = function(self, who, learn)
if who.unique or who.quest_mon or who:resistDam(DamageType.CONFUSION, {learn=learn}) then
return true, ' is unaffected!'
end
if not who:has('ANIMAL') then return true, ' is not an animal and thus unaffected.' end
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
tmp.immune = tmp.immune or save_level_vs_power(target, dam, 10, ' is unaffected!')
if not tmp.immune and src:has('AGGRAVATE') then
-- Slight Hack(TM): Assume only the player will be slinging this
-- damage type.
game.logSeen(target, '%s hates you too much!', target:getName{def_art=true, check_seen=true}:capitalize())
tmp.immune = true
end
if not tmp.immune then
game.logSeen(target, '%s is tamed!', target:getName{def_art=true, check_seen=true}:capitalize())
target.faction = spell.friendly_faction(src)
target.changed = true
end
end,
}
newDamageType {
name = 'psi',
type = 'PSI',
text_color = '#C_B#',
cloud_color = { colors.C_B, colors.C_B, colors.C_w },
is_immune = function(self, who, learn)
if who:has('EMPTY_MIND') then return true, ' is immune!' end
return false
end,
is_resist = function(self, who, learn)
if who:has('STUPID') or who:has('WEIRD_MIND') or who:has('ANIMAL') then
return true, ' resists.'
end
return false
-- TODO Backlash for undead/demon targets
-- (cf. src/spells1.c lines 5311-5377)
-- TODO Do confusion/stun/fear/sleep
-- (cf. src/spells1.c lines 5380-5397)
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if not tmp.immune and target.level > rng.range(1, 3*dam) then
game.logSeen(target, '%s resists.', target:getName{def_art=true, check_seen=true}:capitalize())
tmp.immune = true
end
end,
}
newDamageType {
name = 'psi-drain',
type = 'PSI_DRAIN',
text_color = '#C_B#',
cloud_color = { colors.C_B, colors.C_B, colors.C_w },
resist_reduce = function(dam, target, type) return dam/3 end,
is_immune = function(self, who, learn)
if who:has('EMPTY_MIND') then return true, ' is immune!' end
return false
end,
is_resist = function(self, who, learn)
if who:has('STUPID') or who:has('WEIRD_MIND') or who:has('ANIMAL') then
return true, ' resists.'
end
return false
-- TODO Backlash for undead/demon targets
-- (cf. src/spells1.c lines 5423-5446)
-- TODO Drain mana (cf. src/spells1.c lines 5448-5456)
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
if not tmp.immune and target.level > rng.range(1, 3*dam) then
game.logSeen(target, '%s resist.', target:getName{def_art=true, check_seen=true}:capitalize())
tmp.immune = true
end
end,
}
newDamageType {
name = 'telekinesis',
type = 'TELEKINESIS',
text_color = '#C_B#',
cloud_color = { colors.C_B, colors.C_B, colors.C_w },
-- TODO do stun
}
newDamageType {
name = 'jam door',
type = 'JAM_DOOR',
text_color = '#SLATE#',
}
newDamageType {
name = 'domination',
type = 'DOMINATION',
text_color = '#C_B#',
damage_is_power = true,
cloud_color = { colors.C_B, colors.C_B, colors.C_w },
-- TODO pretty much everything
}
newDamageType {
name = 'dispel good',
type = 'DISP_GOOD',
text_color = '#SLATE#',
harmless_unless_vulnerable = true,
is_vulnerable = function(self, who, learn)
if who:has('GOOD') then
if learn and who.learnFlags then who:learnFlags('flags', 'GOOD') end
return true, ' shudders.'
end
return false
end,
}
newDamageType {
name = 'identify',
type = 'IDENTIFY',
text_color = '#SLATE#',
harmless_unless_vulnerable = true,
-- TODO do monster probe in side_effects_post()
obj_special = function(self, o, who, x, y, damtype, dam)
o:identify(true, who)
-- Return false so the object is not destroyed.
return false
end,
}
newDamageType {
name = 'raise dead',
type = 'RAISE',
text_color = '#SLATE#',
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
-- Wake up target.
if target.ai_state then
target.ai_state.sleep = 0
target.changed = true
end
target:heal(dam)
game.logSeen(target, '%s looks healthier.', target:getName{def_art=true, check_seen=true}:capitalize())
end,
}
newDamageType {
name = '*identify*',
type = 'STAR_IDENTIFY',
text_color = '#SLATE#',
obj_special = function(self, o, who, x, y, damtype, dam)
o:identify('full', who)
-- Return false so the object is not destroyed.
return false
end,
}
newDamageType {
name = 'destruction',
type = 'DESTRUCTION',
text_color = '#SLATE#',
target_flags = {
block_path = DamageType.pass_wall_block_path,
block_radius = DamageType.pass_wall_block_radius,
},
grid_side_effect = function(self, g, x, y, who)
-- cf. src/spells1.c lines 3768-3772
local map = game.level.map
map.lites(x, y, false)
map.remembers(x, y, false)
map:unattr(x, y, 'room')
map:unattr(x, y, 'vault')
end,
change_grid = function(self, g, x, y, who)
-- cf. src/spells1.c lines 3774-3839
local map = game.level.map
local a = map(x, y, Map.TRAP)
if a and a.player then
if not a:has('BLIND') then
game.log('There is a searing blast of light!')
if not a:resistDam(DamageType.BLIND) and not a:resistDam(DamageType.LITE) then
a:setEffect(a.EFF_BLIND, 10 + rng.range(1, 10), {merge='combine'})
end
end
return nil
end
if a then a:remove() end
if g.permanent then return nil end
if not g:valid_bold(x, y) then return nil end
local o = map:getObject(x, y, 1)
while o do
map:removeObject(x, y, 1)
o:remove()
o = map:getObject(x, y, 1)
end
local i = rng.range(1, 200)
local grids = game.zone.grid_list
if i < 20 then return grids.WALL end
if i < 60 then return grids.QUARTZ_VEIN end
if i < 90 then return grids.MAGMA_VEIN end
if i < 110 then return grids.SANDWALL end
return grids.FLOOR
end,
post_project = function(self, who, t, x, y, _, _, _, _, _, _)
spell.clobber_FOVs(x, y)
end,
}
newDamageType {
name = 'death',
type = 'DEATH',
text_color = '#C_D#',
cloud_color = colors.C_D,
is_immune = function(self, who, learn)
if who.unique then return true, ' resists.' end
return false
end,
}
newDamageType {
name = 'dominate demon',
type = 'CONTROL_DEMON',
text_color = '#SLATE#',
damage_is_power = true,
is_immune = function(self, who, learn)
if who.unique or who.quest_mon then
-- Not sure why we're learning this, since we aren't checking against
-- it. [cf. src/spells1.c lines 6055-6058]
if learn and who.learnFlags then
who:learnFlags('resist_dam', DamageType.CONFUSION)
end
return true, ' is unaffected!'
end
if not who:has('DEMON') then return true, ' is unaffected!' end
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
tmp.immune = tmp.immune or save_level_vs_power(target, dam, 10, ' is unaffected!')
if not tmp.immune and src:has('AGGRAVATE') then
-- Slight Hack(TM): Assume only the player will be slinging this
-- damage type.
game.logSeen(target, '%s hates you too much!', target:getName{def_art=true, check_seen=true}:capitalize())
tmp.immune = true
end
if not tmp.immune then
game.logSeen(target, '%s obeys your commands!', target:getName{def_art=true, check_seen=true}:capitalize())
target.faction = spell.friendly_faction(src)
target.changed = true
end
end,
}
newDamageType {
name = 'melee',
type = 'ATTACK',
text_color = '#SLATE#',
damage_is_power = true,
side_effects_post = function(self, src, target, x, y, type, blows, p)
if blows > 0 then
src:attackTarget(target, blows)
end
end,
}
newDamageType {
name = 'charm unmoving',
type = 'CHARM_UNMOVING',
text_color = '#SLATE#',
damage_is_power = true,
is_immune = function(self, who, learn)
if who.unique or who.quest_mon then return true, ' is unaffected!' end
if not who:has('NEVER_MOVE') then return true, ' is not never-moving and thus unaffected.' end
return false
end,
side_effects_pre = function(self, src, target, x, y, type, dam, p, tmp)
tmp.immune = tmp.immune or save_level_vs_power(target, dam, 10, ' is unaffected!')
if not tmp.immune and src:has('AGGRAVATE') then
-- Slight Hack(TM): Assume only the player will be slinging this
-- damage type.
game.logSeen(target, '%s hates you too much!', target:getName{def_art=true, check_seen=true}:capitalize())
tmp.immune = true
end
if not tmp.immune then
game.logSeen(target, '%s is in your thrall!', target:getName{def_art=true, check_seen=true}:capitalize())
target.faction = spell.friendly_faction(src)
target.changed = true
end
end,
}
newDamageType {
name = 'elemental growth',
type = 'ELEMENTAL_GROWTH',
text_color = '#SLATE#',
harmless_unless_vulnerable = true,
floor_dam = {
floor = function(_, _, _, _, _) return spell.geomancy.random_floor() end,
}
}
newDamageType {
name = 'elemental wall',
type = 'ELEMENTAL_WALL',
text_color = '#SLATE#',
harmless_unless_vulnerable = true,
floor_dam = {
floor = function(g, x, y, _, _)
local a = game.level.map(x, y, Map.ACTOR)
if a and a == game.player then return nil end
return spell.geomancy.random_wall()
end
},
post_project = function(self, who, t, x, y, _, _, _, _, _, _)
spell.clobber_FOVs(x, y)
end,
}
-- Dummy damage type to handle archery. The actual ammo fired is passed to
-- side_effects_post() in the p{} parameter.
-- [cf. do_cmd_fire() in src/cmd2.c]
newDamageType {
name = 'archery',
type = 'ARCHERY',
text_color = '#SLATE#',
damage_is_power = true,
side_effects_post = function(self, src, target, x, y, type, _, p, tmp)
if not p.ammo then
-- Already broken or dropped.
return
end
local rsrc = src.resolveSource and src:resolveSource() or src
local src_name = src:getName{def_art=true, check_seen=true, show_setter=true}
local ammo_name = p.ammo:getName{no_count=true, no_mods=true, no_pval=true, no_inscr=true}
local target_name = target:getName{def_art=true, check_seen=true}
-- If a launcher is specified, use it (trap kits will be passed in this
-- way); otherwise, use the wielded launcher.
local launcher = p.launcher
if not launcher then
local l_inven = src:getInven(src.INVEN_LAUNCHER)
launcher = l_inven[1]
end
-- Shouldn't be possible, but just in case...
if not launcher then return end
local bonus = src.to_h + src.to_h_ranged + p.ammo.to_h + launcher.to_h
local chance = src.skill_archery + bonus*3
-- To-hit chance is penalized by distance to player.
local dist = core.fov.distance(x, y, src.x or x, src.y or y)
print(('[COMBAT] archery: %d + 3*(%d + %d + %d + %d) - %d -> %d'):format(src.skill_archery, src.to_h, src.to_h_ranged, p.ammo.to_h, launcher.to_h, dist, chance - dist))
if src:checkHit(target, chance - dist) then
-- Hit target.
local dam = src:ammoDam(p.ammo, target, p.launcher)
local fmt = [[%s's %s hits %s]]
if rsrc:canSee(target) then fmt = fmt .. ' for %s damage' end
game.logSeenXY(src.x or x, src.y or y, fmt .. '.', src_name, ammo_name, target_name, dam)
target:takeHit(dam, src)
if p.ammo.explode_dam_type then
-- Exploding ammo.
src:ammoExplode(p.ammo, x, y)
p.ammo = nil
else
-- Check for breakage.
local break_prob = math.floor(p.ammo.base_break_chance / (1 + rsrc:getSkillLevel('ARCHERY', 10)))
if p.ammo and not p.ammo.unique and rng.percent(break_prob) then
game.logSeenXY(x, y, [[%s's %s breaks.]], src_name, ammo_name)
p.ammo = nil
end
end
if p.ammo then
-- Ammo hasn't broken; it should pierce through or fall to the
-- floor here.
local pierce_prob = 45 + rsrc:getSkillLevel('ARCHERY')
if p.n_pierce and p.n_pierce >= 0 and rng.percent(pierce_prob) then
-- Ammo pierces.
p.n_pierce = p.n_pierce - 1
game.logSeenXY(x, y, [[%s's %s pierces through %s!]], src_name, ammo_name, target_name)
else
-- Ammo drops to floor here.
src:ammoDropFloor(p.ammo, x, y)
p.ammo = nil
end
end
else
-- Miss target; drop the ammo here.
if p.ammo.explode_dam_type then
-- ...Or, as I read the code, exploding ammo will explode here.
src:ammoExplode(p.ammo, x, y)
else
src:ammoDropFloor(p.ammo, x, y)
end
p.ammo = nil
end
end,
}
PK 74N7n
data/attack_types.lua-- $Id: attack_types.lua 2835 2017-04-11 00:13:59Z dsb $
-- ToME - Tales of Middle-Earth
-- Copyright © 2012-2017 Scott Bigham
--
-- 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 .
--
-- Scott Bigham "Zizzo"
-- dsb-tome@killerbunnies.org
-- Monster attack types extracted from T2's melee1.c
newAttackType {
id = 'HIT',
short_desc = 'hit',
msg = " hits %s",
}
newAttackType {
id = 'TOUCH',
short_desc = 'touch',
msg = " touches %s",
}
newAttackType {
id = 'PUNCH',
short_desc = 'punch',
msg = " punches %s",
}
newAttackType {
id = 'KICK',
short_desc = 'kick',
msg = " kicks %s",
}
newAttackType {
id = 'CLAW',
short_desc = 'claw',
msg = " claws %s",
}
newAttackType {
id = 'BITE',
short_desc = 'bite',
msg = " bites %s",
}
newAttackType {
id = 'STING',
short_desc = 'sting',
msg = " stings %s",
}
newAttackType {
id = 'XXX1',
short_desc = 'xxx1',
msg = " XXX1's %s",
}
newAttackType {
id = 'BUTT',
short_desc = 'butt',
msg = " butts %s",
}
newAttackType {
id = 'CRUSH',
short_desc = 'crush',
msg = " crushes %s",
}
newAttackType {
id = 'ENGULF',
short_desc = 'engulf',
msg = " engulfs %s",
}
newAttackType {
id = 'CHARGE',
short_desc = 'charge',
msg = " charges %s",
}
newAttackType {
id = 'CRAWL',
short_desc = 'crawl on target',
msg = " crawls on %s",
silent_miss = true,
}
newAttackType {
id = 'DROOL',
short_desc = 'drool',
msg = " drools on %s",
silent_miss = true,
no_retal = true,
}
newAttackType {
id = 'SPIT',
short_desc = 'spit',
msg = " spits on %s",
silent_miss = true,
no_retal = true,
}
newAttackType {
id = 'EXPLODE',
short_desc = 'explode',
msg = " explodes",
silent_miss = true,
}
newAttackType {
id = 'GAZE',
short_desc = 'gaze',
msg = " gazes at %s",
silent_miss = true,
no_retal = true,
}
newAttackType {
id = 'WAIL',
short_desc = 'wail',
msg = " wails at %s",
silent_miss = true,
no_retal = true,
}
newAttackType {
id = 'SPORE',
short_desc = 'release spores',
msg = " releases spores at %s",
silent_miss = true,
no_retal = true,
}
newAttackType {
id = 'XXX4',
short_desc = 'xxx4',
msg = " projects XXX4's at %s",
silent_miss = true,
no_retal = true,
}
newAttackType {
id = 'BEG',
short_desc = 'beg',
msg = " begs %s for money",
silent_miss = true,
no_retal = true,
}
newAttackType {
id = 'INSULT',
short_desc = 'insult',
msg = " insults %s",
silent_miss = true,
no_retal = true,
}
newAttackType {
id = 'MOAN',
short_desc = 'moan',
msg = " moans at %s",
silent_miss = true,
no_retal = true,
}
newAttackType {
id = 'SHOW',
short_desc = 'sing',
msg = " sings to %s",
silent_miss = true,
no_retal = true,
}
PK 24N \ \ data/background_data.lua-- $Id: background_data.lua 3516 2018-12-15 03:06:21Z dsb $
-- ToME - Tales of Middle-Earth
-- Copyright © 2012-2018 Scott Bigham
--
-- 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 .
--
-- Scott Bigham "Zizzo"
-- dsb-tome@killerbunnies.org
-- -----------------------------------------------------------------------
-- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
-- -----------------------------------------------------------------------
-- This file was generated automatically by the 'gen_plyr' Perl script.
-- DO NOT MODIFY THIS FILE DIRECTLY, as any changes will be lost the next
-- time the file is generated (with the possible exception of lines between
-- 'BEGIN VERBATIM' and 'END VERBATIM' markers, if supported by the script).
-- -----------------------------------------------------------------------
-- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
-- -----------------------------------------------------------------------
-- Player background text generated from T2's p_info.txt.
newBackground {
idx = 1,
text = {
{ prob=10, next=2, social=-25, text=[[You are the illegitimate and unacknowledged child ]] },
{ prob=20, next=2, social=-15, text=[[You are the illegitimate but acknowledged child ]] },
{ prob=95, next=2, social=-5, text=[[You are one of several children ]] },
{ prob=100, next=2, social=0, text=[[You are the first child ]] },
},
}
newBackground {
idx = 2,
text = {
{ prob=40, next=3, social=15, text=[[of a Serf. ]] },
{ prob=65, next=3, social=30, text=[[of a Yeoman. ]] },
{ prob=80, next=3, social=40, text=[[of a Townsman. ]] },
{ prob=90, next=3, social=55, text=[[of a Guildsman. ]] },
{ prob=96, next=3, social=70, text=[[of a Landed Knight. ]] },
{ prob=99, next=3, social=80, text=[[of a Noble Lord. ]] },
{ prob=100, next=3, social=90, text=[[of the Royal Blood Line. ]] },
},
}
newBackground {
idx = 3,
text = {
{ prob=20, next=50, social=-30, text=[[You are the black sheep of the family. ]] },
{ prob=80, next=50, social=5, text=[[You are a credit to the family. ]] },
{ prob=100, next=50, social=10, text=[[You are a well-liked child. ]] },
},
}
newBackground {
idx = 4,
text = {
{ prob=25, next=1, social=-10, text=[[Your mother was of the Avari. ]] },
{ prob=40, next=1, social=0, text=[[Your father was of the Avari. ]] },
{ prob=65, next=1, social=10, text=[[Your mother was of the Nandor. ]] },
{ prob=80, next=1, social=10, text=[[Your father was of the Nandor. ]] },
{ prob=96, next=1, social=20, text=[[Your mother was of the Sindar. ]] },
{ prob=99, next=1, social=20, text=[[Your father was of the Sindar. ]] },
{ prob=100, next=1, social=50, text=[[Your ancestry traces to Elrond. ]] },
},
}
newBackground {
idx = 5,
text = {
{ prob=60, next=6, social=0, text=[[You are one of several children ]] },
{ prob=100, next=6, social=5, text=[[You are the only child ]] },
},
}
newBackground {
idx = 6,
text = {
{ prob=40, next=9, social=-10, text=[[of an Avarin ]] },
{ prob=70, next=9, social=0, text=[[of a Nandorin ]] },
{ prob=100, next=9, social=10, text=[[of a Sindarin ]] },
},
}
newBackground {
idx = 7,
text = {
{ prob=60, next=8, social=0, text=[[You are one of several children ]] },
{ prob=100, next=8, social=5, text=[[You are the only child ]] },
},
}
newBackground {
idx = 8,
text = {
{ prob=75, next=9, social=0, text=[[of a Telerin ]] },
{ prob=95, next=9, social=5, text=[[of a Ñoldorin ]] },
{ prob=100, next=9, social=10, text=[[of a Vanyarin ]] },
},
}
newBackground {
idx = 9,
text = {
{ prob=40, next=54, social=30, text=[[Ranger. ]] },
{ prob=70, next=54, social=40, text=[[Archer. ]] },
{ prob=87, next=54, social=60, text=[[Warrior. ]] },
{ prob=95, next=54, social=75, text=[[Mage. ]] },
{ prob=99, next=54, social=90, text=[[Prince. ]] },
{ prob=100, next=54, social=95, text=[[King. ]] },
},
}
newBackground {
idx = 10,
text = {
{ prob=85, next=11, social=-5, text=[[You are one of several children of a Hobbit ]] },
{ prob=100, next=11, social=5, text=[[You are the only child of a Hobbit ]] },
},
}
newBackground {
idx = 11,
text = {
{ prob=20, next=3, social=5, text=[[Bum. ]] },
{ prob=30, next=3, social=30, text=[[Tavern Owner. ]] },
{ prob=40, next=3, social=40, text=[[Miller. ]] },
{ prob=50, next=3, social=50, text=[[Home Owner. ]] },
{ prob=80, next=3, social=60, text=[[Burglar. ]] },
{ prob=95, next=3, social=65, text=[[Warrior. ]] },
{ prob=99, next=3, social=75, text=[[Mage. ]] },
{ prob=100, next=3, social=90, text=[[Clan Elder. ]] },
},
}
newBackground {
idx = 13,
text = {
{ prob=85, next=14, social=-5, text=[[You are one of several children of a Gnome ]] },
{ prob=100, next=14, social=5, text=[[You are the only child of a Gnome ]] },
},
}
newBackground {
idx = 14,
text = {
{ prob=20, next=3, social=5, text=[[Beggar. ]] },
{ prob=50, next=3, social=20, text=[[Braggart. ]] },
{ prob=75, next=3, social=35, text=[[Prankster. ]] },
{ prob=95, next=3, social=50, text=[[Warrior. ]] },
{ prob=100, next=3, social=75, text=[[Mage. ]] },
},
}
newBackground {
idx = 16,
text = {
{ prob=25, next=17, social=-10, text=[[You are one of two children of a Dwarven ]] },
{ prob=100, next=17, social=0, text=[[You are the only child of a Dwarven ]] },
},
}
newBackground {
idx = 17,
text = {
{ prob=10, next=18, social=10, text=[[Thief. ]] },
{ prob=35, next=18, social=30, text=[[Smith. ]] },
{ prob=75, next=18, social=40, text=[[Miner. ]] },
{ prob=90, next=18, social=60, text=[[Warrior. ]] },
{ prob=99, next=18, social=80, text=[[Priest. ]] },
{ prob=100, next=18, social=100, text=[[King. ]] },
},
}
newBackground {
idx = 18,
text = {
{ prob=15, next=57, social=-40, text=[[You are the black sheep of the family. ]] },
{ prob=85, next=57, social=0, text=[[You are a credit to the family. ]] },
{ prob=100, next=57, social=5, text=[[You are a well liked child. ]] },
},
}
newBackground {
idx = 20,
text = {
{ prob=100, next=2, social=0, text=[[You are the adopted child ]] },
},
}
newBackground {
idx = 22,
text = {
{ prob=100, next=23, social=0, text=[[You are the offspring of a ]] },
},
}
newBackground {
idx = 23,
text = {
{ prob=30, next=24, social=-30, text=[[Forest-Troll ]] },
{ prob=60, next=24, social=-25, text=[[Cave-Troll ]] },
{ prob=75, next=24, social=-20, text=[[Hill-Troll ]] },
{ prob=90, next=24, social=-15, text=[[Stone-Troll ]] },
{ prob=95, next=24, social=-10, text=[[Snow-Troll ]] },
{ prob=100, next=24, social=-5, text=[[Water-Troll ]] },
},
}
newBackground {
idx = 24,
text = {
{ prob=25, next=62, social=0, text=[[Worker. ]] },
{ prob=95, next=62, social=5, text=[[Warrior. ]] },
{ prob=99, next=62, social=15, text=[[Shaman. ]] },
{ prob=100, next=62, social=30, text=[[Clan Chief. ]] },
},
}
newBackground {
idx = 25,
text = {
{ prob=100, next=26, social=0, text=[[You are one of several children of ]] },
},
}
newBackground {
idx = 26,
text = {
{ prob=40, next=27, social=-10, text=[[a Snaga ]] },
{ prob=80, next=27, social=0, text=[[an Orc ]] },
{ prob=100, next=27, social=10, text=[[an Uruk ]] },
},
}
newBackground {
idx = 27,
text = {
{ prob=30, next=28, social=-30, text=[[Slave ]] },
{ prob=60, next=28, social=0, text=[[Archer ]] },
{ prob=90, next=28, social=10, text=[[Warrior ]] },
{ prob=95, next=28, social=30, text=[[Shaman ]] },
{ prob=100, next=28, social=50, text=[[Chieftain ]] },
},
}
newBackground {
idx = 28,
text = {
{ prob=30, next=80, social=0, text=[[from the Misty Mountains. ]] },
{ prob=60, next=80, social=0, text=[[from the Grey Mountains. ]] },
{ prob=90, next=80, social=20, text=[[from the orc-hold of Mount Gundabad. ]] },
{ prob=100, next=80, social=30, text=[[from the Pits of Angband. ]] },
},
}
newBackground {
idx = 29,
text = {
{ prob=25, next=3, social=0, text=[[You are one of five children of a blue Yeek. ]] },
{ prob=75, next=3, social=25, text=[[You are one of five children of a brown Yeek. ]] },
{ prob=100, next=3, social=50, text=[[You are one of five children of a master Yeek. ]] },
},
}
newBackground {
idx = 50,
text = {
{ prob=20, next=51, social=0, text=[[You have dark brown eyes, ]] },
{ prob=60, next=51, social=0, text=[[You have brown eyes, ]] },
{ prob=70, next=51, social=0, text=[[You have hazel eyes, ]] },
{ prob=80, next=51, social=0, text=[[You have green eyes, ]] },
{ prob=90, next=51, social=0, text=[[You have blue eyes, ]] },
{ prob=100, next=51, social=0, text=[[You have blue-gray eyes, ]] },
},
}
newBackground {
idx = 51,
text = {
{ prob=70, next=52, social=0, text=[[straight ]] },
{ prob=90, next=52, social=0, text=[[wavy ]] },
{ prob=100, next=52, social=0, text=[[curly ]] },
},
}
newBackground {
idx = 52,
text = {
{ prob=30, next=53, social=0, text=[[black hair, ]] },
{ prob=70, next=53, social=0, text=[[brown hair, ]] },
{ prob=80, next=53, social=0, text=[[auburn hair, ]] },
{ prob=90, next=53, social=0, text=[[red hair, ]] },
{ prob=100, next=53, social=0, text=[[blond hair, ]] },
},
}
newBackground {
idx = 53,
text = {
{ prob=10, next=0, social=0, text=[[and a very dark complexion.]] },
{ prob=30, next=0, social=0, text=[[and a dark complexion.]] },
{ prob=80, next=0, social=0, text=[[and an average complexion.]] },
{ prob=90, next=0, social=0, text=[[and a fair complexion.]] },
{ prob=100, next=0, social=0, text=[[and a very fair complexion.]] },
},
}
newBackground {
idx = 54,
text = {
{ prob=85, next=55, social=0, text=[[You have light grey eyes, ]] },
{ prob=95, next=55, social=0, text=[[You have light blue eyes, ]] },
{ prob=100, next=55, social=0, text=[[You have light green eyes, ]] },
},
}
newBackground {
idx = 55,
text = {
{ prob=75, next=56, social=0, text=[[straight ]] },
{ prob=100, next=56, social=0, text=[[wavy ]] },
},
}
newBackground {
idx = 56,
text = {
{ prob=75, next=0, social=0, text=[[black hair, and a fair complexion.]] },
{ prob=85, next=0, social=0, text=[[brown hair, and a fair complexion.]] },
{ prob=95, next=0, social=0, text=[[blond hair, and a fair complexion.]] },
{ prob=100, next=0, social=0, text=[[silver hair, and a fair complexion.]] },
},
}
newBackground {
idx = 57,
text = {
{ prob=99, next=58, social=0, text=[[You have dark brown eyes, ]] },
{ prob=100, next=58, social=10, text=[[You have glowing red eyes, ]] },
},
}
newBackground {
idx = 58,
text = {
{ prob=90, next=59, social=0, text=[[straight ]] },
{ prob=100, next=59, social=0, text=[[wavy ]] },
},
}
newBackground {
idx = 59,
text = {
{ prob=75, next=60, social=0, text=[[black hair, ]] },
{ prob=100, next=60, social=0, text=[[brown hair, ]] },
},
}
newBackground {
idx = 60,
text = {
{ prob=25, next=61, social=0, text=[[a one foot beard, ]] },
{ prob=60, next=61, social=1, text=[[a two foot beard, ]] },
{ prob=90, next=61, social=3, text=[[a three foot beard, ]] },
{ prob=100, next=61, social=5, text=[[a four foot beard, ]] },
},
}
newBackground {
idx = 61,
text = {
{ prob=100, next=0, social=0, text=[[and a dark complexion.]] },
},
}
newBackground {
idx = 62,
text = {
{ prob=60, next=63, social=0, text=[[You have slime green eyes, ]] },
{ prob=85, next=63, social=0, text=[[You have puke yellow eyes, ]] },
{ prob=99, next=63, social=0, text=[[You have blue-bloodshot eyes, ]] },
{ prob=100, next=63, social=5, text=[[You have glowing red eyes, ]] },
},
}
newBackground {
idx = 63,
text = {
{ prob=33, next=64, social=0, text=[[dirty ]] },
{ prob=66, next=64, social=0, text=[[mangy ]] },
{ prob=100, next=64, social=0, text=[[oily ]] },
},
}
newBackground {
idx = 64,
text = {
{ prob=33, next=65, social=0, text=[[sea-weed green hair, ]] },
{ prob=66, next=65, social=0, text=[[bright red hair, ]] },
{ prob=100, next=65, social=0, text=[[dark purple hair, ]] },
},
}
newBackground {
idx = 65,
text = {
{ prob=25, next=66, social=0, text=[[and green ]] },
{ prob=50, next=66, social=0, text=[[and blue ]] },
{ prob=75, next=66, social=0, text=[[and white ]] },
{ prob=100, next=66, social=0, text=[[and black ]] },
},
}
newBackground {
idx = 66,
text = {
{ prob=33, next=0, social=0, text=[[ulcerous skin.]] },
{ prob=66, next=0, social=0, text=[[scabby skin.]] },
{ prob=100, next=0, social=0, text=[[leprous skin.]] },
},
}
newBackground {
idx = 69,
text = {
{ prob=85, next=70, social=-5, text=[[You are one of several children of a Dark Elven ]] },
{ prob=100, next=70, social=5, text=[[You are the only child of a Dark Elven ]] },
},
}
newBackground {
idx = 70,
text = {
{ prob=50, next=71, social=10, text=[[Warrior. ]] },
{ prob=80, next=71, social=25, text=[[Warlock. ]] },
{ prob=100, next=71, social=45, text=[[Noble. ]] },
},
}
newBackground {
idx = 71,
text = {
{ prob=100, next=72, social=0, text=[[You have black eyes, ]] },
},
}
newBackground {
idx = 72,
text = {
{ prob=70, next=73, social=0, text=[[straight ]] },
{ prob=90, next=73, social=0, text=[[wavy ]] },
{ prob=100, next=73, social=0, text=[[curly ]] },
},
}
newBackground {
idx = 73,
text = {
{ prob=100, next=0, social=0, text=[[black hair and a very dark complexion.]] },
},
}
newBackground {
idx = 74,
text = {
{ prob=25, next=20, social=-25, text=[[Your mother was an Ogre, but it is unacknowledged. ]] },
{ prob=100, next=20, social=-25, text=[[Your father was an Ogre, but it is unacknowledged. ]] },
},
}
newBackground {
idx = 75,
text = {
{ prob=90, next=76, social=0, text=[[You are a descendant of Beorn to the ]] },
{ prob=100, next=20, social=50, text=[[Your father was Beorn. ]] },
},
}
newBackground {
idx = 76,
text = {
{ prob=13, next=20, social=5, text=[[9th degree. ]] },
{ prob=25, next=20, social=10, text=[[8th degree. ]] },
{ prob=38, next=20, social=15, text=[[7th degree. ]] },
{ prob=50, next=20, social=20, text=[[6th degree. ]] },
{ prob=63, next=20, social=25, text=[[5th degree. ]] },
{ prob=75, next=20, social=30, text=[[4th degree. ]] },
{ prob=88, next=20, social=35, text=[[3rd degree. ]] },
{ prob=100, next=20, social=40, text=[[2nd degree. ]] },
},
}
newBackground {
idx = 78,
text = {
{ prob=100, next=79, social=0, text=[[You are one of several children of ]] },
},
}
newBackground {
idx = 79,
text = {
{ prob=50, next=80, social=0, text=[[a Brown Yeek. ]] },
{ prob=75, next=80, social=0, text=[[a Blue Yeek. ]] },
{ prob=95, next=80, social=35, text=[[a Master Yeek. ]] },
{ prob=100, next=80, social=70, text=[[Boldor, the King of the Yeeks. ]] },
},
}
newBackground {
idx = 80,
text = {
{ prob=25, next=81, social=0, text=[[You have pale eyes, ]] },
{ prob=50, next=81, social=0, text=[[You have glowing eyes, ]] },
{ prob=75, next=81, social=0, text=[[You have tiny black eyes, ]] },
{ prob=100, next=81, social=0, text=[[You have shining black eyes, ]] },
},
}
newBackground {
idx = 81,
text = {
{ prob=20, next=65, social=0, text=[[no hair at all, ]] },
{ prob=40, next=65, social=0, text=[[short black hair, ]] },
{ prob=60, next=65, social=0, text=[[long black hair, ]] },
{ prob=80, next=65, social=0, text=[[bright red hair, ]] },
{ prob=100, next=65, social=0, text=[[colourless albino hair, ]] },
},
}
newBackground {
idx = 82,
text = {
{ prob=100, next=83, social=0, text=[[You are one of several children of ]] },
},
}
newBackground {
idx = 83,
text = {
{ prob=40, next=80, social=0, text=[[a Small Kobold. ]] },
{ prob=75, next=80, social=5, text=[[a Kobold. ]] },
{ prob=95, next=80, social=15, text=[[a Large Kobold. ]] },
{ prob=100, next=80, social=50, text=[[Mughash, the Kobold Lord. ]] },
},
}
newBackground {
idx = 84,
text = {
{ prob=85, next=85, social=-5, text=[[You are one of several children ]] },
{ prob=100, next=85, social=0, text=[[You are the first child ]] },
},
}
newBackground {
idx = 85,
text = {
{ prob=60, next=50, social=-10, text=[[of a Serf. ]] },
{ prob=85, next=50, social=5, text=[[of a Devoted Mercenary. ]] },
{ prob=96, next=50, social=10, text=[[of a Landed Knight. ]] },
{ prob=99, next=50, social=50, text=[[of a Marshal of the Riddermark. ]] },
{ prob=100, next=50, social=70, text=[[of a King of the Rohirrim. ]] },
},
}
newBackground {
idx = 87,
text = {
{ prob=100, next=88, social=39, text=[[You are one of several children of ]] },
},
}
newBackground {
idx = 88,
text = {
{ prob=30, next=18, social=-30, text=[[a Petty-Dwarf Slave. ]] },
{ prob=50, next=18, social=-10, text=[[a Petty-Dwarf Thief. ]] },
{ prob=70, next=18, social=10, text=[[a Petty-Dwarf Smith. ]] },
{ prob=90, next=18, social=25, text=[[a Petty-Dwarf Miner. ]] },
{ prob=95, next=18, social=50, text=[[a Petty-Dwarf Shaman. ]] },
{ prob=100, next=18, social=50, text=[[Mîm, Betrayer of Túrin. ]] },
},
}
newBackground {
idx = 89,
text = {
{ prob=85, next=90, social=0, text=[[You are one of many Manwë Maia. ]] },
{ prob=100, next=90, social=10, text=[[You are the one of the most famous Manwë Maia. ]] },
},
}
newBackground {
idx = 90,
text = {
{ prob=90, next=93, social=50, text=[[Your eagle looks very good. ]] },
{ prob=100, next=93, social=70, text=[[Your eagle is splendid. ]] },
},
}
newBackground {
idx = 91,
text = {
{ prob=10, next=92, social=-30, text=[[You are a unnoticed minion of ]] },
{ prob=25, next=92, social=-20, text=[[You are a minor servant of ]] },
{ prob=45, next=92, social=-10, text=[[You are a subject of ]] },
{ prob=65, next=92, social=0, text=[[You have attached yourself to ]] },
{ prob=85, next=92, social=15, text=[[You are associated with ]] },
{ prob=95, next=92, social=30, text=[[You are a notable follower of ]] },
{ prob=100, next=92, social=50, text=[[You are a celebrated assistant to ]] },
},
}
newBackground {
idx = 92,
text = {
{ prob=20, next=93, social=5, text=[[Nessa. ]] },
{ prob=40, next=93, social=10, text=[[Vána. ]] },
{ prob=50, next=93, social=15, text=[[Tulkas. ]] },
{ prob=80, next=93, social=25, text=[[Mandos. ]] },
{ prob=90, next=93, social=30, text=[[Nienna. ]] },
{ prob=95, next=93, social=40, text=[[Varda. ]] },
{ prob=100, next=93, social=45, text=[[Manwë. ]] },
},
}
newBackground {
idx = 93,
text = {
{ prob=100, next=94, social=0, text=[[In the past you dwelt on earth in the form of ]] },
},
}
newBackground {
idx = 94,
text = {
{ prob=25, next=0, social=0, text=[[various animals.]] },
{ prob=55, next=0, social=5, text=[[a spirit of forest and river.]] },
{ prob=70, next=0, social=10, text=[[a beneficent but unseen force.]] },
{ prob=96, next=0, social=20, text=[[a wise and ancient counsellor.]] },
{ prob=100, next=0, social=30, text=[[a Wizard of legend.]] },
},
}
newBackground {
idx = 95,
text = {
{ prob=30, next=96, social=-20, text=[[You are of an unknown generation of the Ents. ]] },
{ prob=40, next=96, social=0, text=[[You are of the third generation of the Ents. ]] },
{ prob=60, next=96, social=10, text=[[You are of the second generation of the Ents. ]] },
{ prob=100, next=96, social=30, text=[[You are one of the first beings who awoke on Arda. ]] },
},
}
newBackground {
idx = 96,
text = {
{ prob=50, next=0, social=0, text=[[You have green skin and inflexible members.]] },
{ prob=100, next=0, social=0, text=[[You have brown skin and inflexible members.]] },
},
}
newBackground {
idx = 100,
text = {
{ prob=10, next=101, social=-20, text=[[You were born in dirty bilge-water, ]] },
{ prob=20, next=101, social=-15, text=[[You were born in dirty straw, ]] },
{ prob=30, next=101, social=-10, text=[[You were born in wet mud, ]] },
{ prob=40, next=101, social=-5, text=[[You were born in a pile of dust, ]] },
{ prob=50, next=101, social=0, text=[[You were born in sand, ]] },
{ prob=60, next=101, social=0, text=[[You were born in pebbles, ]] },
{ prob=70, next=101, social=5, text=[[You were born in a kobold corpse, ]] },
{ prob=80, next=101, social=10, text=[[You were born in dragon droppings, ]] },
{ prob=90, next=101, social=15, text=[[You were born in a pile of bones, ]] },
{ prob=100, next=101, social=20, text=[[You were born in a corpse of a mighty hero, ]] },
},
}
newBackground {
idx = 101,
text = {
{ prob=10, next=102, social=-20, text=[[created by rotting flesh. ]] },
{ prob=20, next=102, social=-15, text=[[created by a kobold magician. ]] },
{ prob=30, next=102, social=-10, text=[[created by a corrupted apprentice. ]] },
{ prob=40, next=102, social=-5, text=[[created by a curious mage apprentice. ]] },
{ prob=50, next=102, social=0, text=[[created by an evil Symbiant. ]] },
{ prob=60, next=102, social=0, text=[[created by a practicing Necromancer. ]] },
{ prob=70, next=102, social=5, text=[[created by the Mutant Breeders. ]] },
{ prob=80, next=102, social=10, text=[[created by a curious adventurer. ]] },
{ prob=90, next=102, social=15, text=[[called to life by the Witch-King of Angmar. ]] },
{ prob=100, next=102, social=20, text=[[called to life by Sauron himself. ]] },
},
}
newBackground {
idx = 102,
text = {
{ prob=100, next=103, social=0, text=[[Since then you have given life to ]] },
},
}
newBackground {
idx = 103,
text = {
{ prob=10, next=104, social=-20, text=[[no ]] },
{ prob=20, next=104, social=-15, text=[[one weak-willed ]] },
{ prob=30, next=104, social=-10, text=[[two ]] },
{ prob=40, next=104, social=-5, text=[[three ]] },
{ prob=50, next=104, social=0, text=[[four ]] },
{ prob=60, next=104, social=0, text=[[five ]] },
{ prob=70, next=104, social=5, text=[[about twenty ]] },
{ prob=80, next=104, social=10, text=[[dozens of ]] },
{ prob=90, next=104, social=15, text=[[hundreds of ]] },
{ prob=100, next=104, social=20, text=[[uncounted multitudes of ]] },
},
}
newBackground {
idx = 104,
text = {
{ prob=100, next=0, social=0, text=[[foul offspring.]] },
},
}
PK 74NIټO O data/mimicry_forms.lua-- $Id: mimicry_forms.lua 2875 2017-05-11 01:23:13Z dsb $
-- ToME - Tales of Middle-Earth
-- Copyright © 2012-2017 Scott Bigham
--
-- 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 .
--
-- Scott Bigham "Zizzo"
-- dsb-tome@killerbunnies.org
local function num_info(...)
local args = {...}
local desc = tstring{}
for i = 1, #args, 2 do
local f, v = args[i], args[i+1]
if i > 1 then desc:add(', ') end
desc:merge(util.colorize_num(v):toTString())
desc:add(' '..f)
end
return desc
end
local function list_info(nl, lev, ...)
local args = {...}
local ff = {}
for i = 1, #args, 2 do
local l, f = args[i], args[i+1]
if lev >= l then table.insert(ff, f) end
end
if #ff == 0 then return tstring{} end
ff = table.concat(ff, ', ')
return nl and tstring{true, ff:capitalize()} or tstring{ff}
end
local function resist_info(pfx, nl, lev, ...)
local DamageType = require 'engine.DamageType'
local args = {...}
local desc = nl and tstring{ true, pfx..' '} or tstring{ pfx..' ' }
local any = false
for i = 1, #args, 2 do
local l, id = args[i], args[i+1]
if lev >= l then
local dt = DamageType:get(id)
if any then desc:add(', ') end
desc:merge(util.colorize_damtype(id, true))
any = true
end
end
return any and desc or tstring{}
end
newMimicryForm {
name = 'Mouse',
cloak_name = '& Mouse Fur~',
desc = 'Mice are small, fast and very stealthy',
realm = 'nature',
level = 1,
rarity = 10,
duration = { 20, 40 },
eff_id = 'MOUSE_FORM',
apply = function(who, lev)
who.speed = who.speed + 5 + math.floor(lev/7)
who:adjustDis('to_h', 10 + math.floor(lev/5), true, 'Mouse form')
who.to_d = math.floor(who.to_d/5)
who.dis_to_d = math.floor(who.dis_to_d/5)
who.skill_stealth = who.skill_stealth + 10 + math.floor(lev/5)
who:adjustStatForEquip('STR', -5)
who:adjustStatForEquip('DEX', 3)
who:adjustStatForEquip('CON', 1)
who:accumTargetTalent('T_CLOAK_MOUSE_INVIS')
end,
info = function(who, lev)
local desc = num_info('STR', -5, 'DEX', 3, 'CON', 1)
desc:add(true)
desc:merge(num_info('to hit', 10 + math.floor(lev/5),
'to damage', math.floor(who.to_d/5) - who.to_d))
desc:add(true)
desc:merge(num_info('speed', 5 + math.floor(lev/7),
'stealth', 10 + math.floor(lev/5)))
desc:add(true, 'Invisibility power')
return desc
end,
}
newMimicryForm {
name = 'Eagle',
cloak_name = '& Feathers Cloak~',
desc = 'Eagles are master of the air, good hunters with excellent vision',
realm = 'nature',
level = 10,
rarity = 30,
duration = { 10, 50 },
apply = function(who, lev)
who.flags.FEATHER = true
who.speed = who.speed + 2 + math.floor(lev/6)
who:adjustStatForEquip('STR', -3)
who:adjustStatForEquip('DEX', 2 + math.floor(lev/15))
who:adjustStatForEquip('CON', 4 + math.floor(lev/20))
who:adjustStatForEquip('INT', -1)
who:adjustStatForEquip('WIS', 1)
who:adjustStatForEquip('CHA', -1)
if lev >= 20 then
who.flags.FLY = true
who.flags.SEE_INVIS = true
end
if lev >= 25 then
who.flags.FREE_ACT = true
end
if lev >= 30 then
local DamageType = require 'engine.DamageType'
who.resist_dam[DamageType.ELEC] = true
who.flags.SH_ELEC = true
end
end,
info = function(who, lev)
local DamageType = require 'engine.DamageType'
local desc = num_info('STR', -3, 'INT', -1, 'WIS', 1,
'DEX', 2 + math.floor(lev/15),
'CON', 4 + math.floor(lev/20),
'CHA', -1)
desc:add(true)
desc:merge(num_info('speed', 2 + math.floor(lev/6)))
desc:merge(resist_info('Resist', true, lev, 30, DamageType.ELEC))
desc:merge(list_info(true, lev, 20, 'flight', 20, 'see invisible',
25, 'free action', 30, 'lightning aura'))
return desc
end,
}
newMimicryForm {
name = 'Wolf',
cloak_name = '& Wolf Pelt~',
desc = 'Wolves are masters of movement, strong and have excellent eyesight',
ream = 'nature',
level = 20,
rarity = 40,
duration = { 10, 50 },
apply = function(who, lev)
local DamageType = require 'engine.DamageType'
who:adjustStatForEquip('STR', 2 + math.floor(lev/20))
who:adjustStatForEquip('DEX', 3 + math.floor(lev/20))
who:adjustStatForEquip('INT', -3)
who:adjustStatForEquip('CHA', -2)
who.speed = who.speed + 10 + math.floor(lev/5)
who.flags.FREE_ACT = true
who.resist_dam[DamageType.FEAR] = true
if lev >= 10 then
who.resist_dam[DamageType.COLD] = true
end
if lev >= 15 then
who.flags.SEE_INVIS = true
end
if lev >= 30 then
who.resist_dam[DamageType.DARK] = true
end
if lev >= 35 then
who.resist_dam[DamageType.CONFUSION] = true
end
end,
info = function(who, lev)
local DamageType = require 'engine.DamageType'
local desc = num_info('STR', 2 + math.floor(lev/20),
'INT', -3,
'DEX', 3 + math.floor(lev/20),
'CHA', -2)
desc:add(true)
desc:merge(num_info('speed', 10 + math.floor(lev/5)))
desc:merge(resist_info('Resist', true, lev,
10, DamageType.COLD,
30, DamageType.DARK,
35, DamageType.CONFUSION))
desc:merge(list_info(true, lev, 15, 'see invisible'))
return desc
end,
}
newMimicryForm {
name = 'Spider',
cloak_name = '& Spider Web~',
desc = 'Spiders are clever and become good climbers',
realm = 'nature',
level = 25,
rarity = 50,
duration = { 10, 50 },
apply = function(who, lev)
local DamageType = require 'engine.DamageType'
who:adjustStatForEquip('STR', -4)
who:adjustStatForEquip('DEX', 1 + math.floor(lev/8))
who:adjustStatForEquip('INT', 1 + math.floor(lev/5))
who:adjustStatForEquip('WIS', 1 + math.floor(lev/5))
who:adjustStatForEquip('CON', -5)
who:adjustStatForEquip('CHA', -10)
who.speed = who.speed + 5
who.resist_dam[DamageType.POISON] = true
who.resist_dam[DamageType.FEAR] = true
who.resist_dam[DamageType.DARK] = true
who.flags.SPIDER = true
if lev >= 40 then
who.flags.CLIMB = true
end
if lev >= 25 then
who:accumTargetTalent('T_CLOAK_SPIDER_WEB')
end
end,
info = function(who, lev)
local DamageType = require 'engine.DamageType'
local desc = num_info('STR', -4,
'INT', 1 + math.floor(lev/5),
'WIS', 1 + math.floor(lev/5),
'DEX', 1 + math.floor(lev/8),
'CON', -5,
'CHA', -10)
desc:add(true)
desc:merge(num_info('speed', 5))
desc:merge(resist_info('Resist', true, lev,
0, DamageType.FEAR, 0, DamageType.DARK))
desc:merge(list_info(true, lev, 40, 'mountain climbing', 20, 'web power',
0, 'can traverse webs'))
return desc
end,
}
newMimicryForm {
name = 'Elder Ent',
cloak_name = '& Entish Bark~',
desc = 'Ents are powerful tree-like beings dating from the dawn of time',
ream = 'nature',
level = 40,
rarity = 60,
duration = { 10, 30 },
no_potion = true,
apply = function(who, lev)
local DamageType = require 'engine.DamageType'
who.speed = who.speed - 5 - math.floor(lev/10)
who:adjustDis('to_a', 10 + lev, true)
who:adjustStatForEquip('STR', math.floor(lev/5))
who:adjustStatForEquip('INT', -math.floor(lev/7))
who:adjustStatForEquip('WIS', -math.floor(lev/7))
who:adjustStatForEquip('DEX', -4)
who:adjustStatForEquip('CON', math.floor(lev/5))
who:adjustStatForEquip('CHA', -7)
who.resist_dam[DamageType.POISON] = true
who.resist_dam[DamageType.COLD] = true
who.flags.FREE_ACT = true
who.flags.REGENERATE = true
who.flags.SEE_INVIS = true
who.flags.PASS_TREE = true
who.vuln_dam[DamageType.FIRE] = true
who:accumTargetTalent('T_ENT_GROW_TREES')
end,
info = function(who, lev)
local DamageType = require 'engine.DamageType'
local desc = num_info('STR', math.floor(lev/5),
'INT', -math.floor(lev/7),
'WIS', -math.floor(lev/7),
'DEX', -4,
'CON', math.floor(lev/5),
'CHA', -7)
desc:add(true)
desc:merge(num_info('AC', 10 + lev, 'speed', -5 - math.floor(lev/10)))
desc:merge(resist_info('Resist', true, lev,
0, DamageType.POISON, 0, DamageType.COLD))
desc:merge(resist_info('Vulnerable to', true, lev, 0, DamageType.FIRE))
desc:merge(list_info(true, lev, 0, 'free action', 0, 'regeneration',
0, 'see invisible',
0, 'can traverse trees'))
desc:merge(list_info(true, lev, 0, 'grow-tree power'))
return desc
end,
}
newMimicryForm {
name = 'Vapour',
cloak_name = '& Cloak~ of Mist',
desc = 'A sentient cloud, darting around',
realm = 'nature',
level = 15,
rarity = 10,
duration = { 10, 40 },
apply = function(who, lev)
local DamageType = require 'engine.DamageType'
who.speed = who.speed + 5
who:adjustDis('to_a', 40 + lev, true)
who:adjustDis('to_h', -40, true, 'Vapour form')
who:adjustStatForEquip('STR', -4)
who:adjustStatForEquip('DEX', 5)
who:adjustStatForEquip('CON', -4)
who:adjustStatForEquip('CHA', -10)
who.skill_stealth = who.skill_stealth + 10 + math.floor(lev/5)
who.resist_dam[DamageType.POISON] = true
who.resist_dam[DamageType.SHARDS] = true
who.immune_dam[DamageType.COLD] = true
who.vuln_dam[DamageType.FIRE] = true
who.flags.FREE_ACT = true
who.flags.REGENERATE = true
who.flags.SEE_INVIS = true
who.flags.FEATHER = true
end,
info = function(who, lev)
local DamageType = require 'engine.DamageType'
local desc = num_info('STR', -4, 'DEX', 5, 'CON', -4, 'CHA', -10)
desc:add(true)
desc:merge(num_info('AC', 40 + lev, 'to hit', -40, 'speed', 5,
'stealth', 10 + math.floor(lev/5)))
desc:merge(resist_info('Resist', true, lev,
0, DamageType.POISON, 0, DamageType.SHARDS,
0, DamageType.COLD))
desc:merge(resist_info('Vulnerable to', true, lev, 0, DamageType.FIRE))
desc:merge(list_info(true, lev, 0, 'free action', 0, 'regeneration',
0, 'see invisible', 0, 'feather fall'))
return desc
end,
}
newMimicryForm {
name = 'Serpent',
cloak_name = '& Snakeskin Cloak~',
desc = 'Serpents are fast, lethal predators',
realm = 'nature',
level = 30,
rarity = 25,
duration = { 15, 20 },
apply = function(who, lev)
local DamageType = require 'engine.DamageType'
who.speed = who.speed + 10 + math.floor(lev/6)
who:adjustDis('to_a', 3 + math.floor(lev/8), true)
who:adjustStatForEquip('STR', math.floor(lev/8))
who:adjustStatForEquip('INT', -6)
who:adjustStatForEquip('WIS', -6)
who:adjustStatForEquip('DEX', -4)
who:adjustStatForEquip('CON', math.floor(lev/7))
who:adjustStatForEquip('CHA', -6)
who.resist_dam[DamageType.POISON] = true
if lev >= 25 then
who.flags.FREE_ACT = true
end
end,
info = function(who, lev)
local DamageType = require 'engine.DamageType'
local desc = num_info('STR', math.floor(lev/8), 'INT', -6, 'WIS', -6,
'DEX', -4, 'CON', math.floor(lev/7), 'CHA', -6)
desc:add(true)
desc:merge(num_info('AC', 3 + math.floor(lev/8),
'speed', 10 + math.floor(lev/6)))
desc:merge(resist_info('Resist', true, lev, 0, DamageType.POISON))
desc:merge(list_info(true, lev, 25, 'free action'))
return desc
end,
}
newMimicryForm {
name = 'Mumak',
cloak_name = '& Mumak Hide~',
desc = 'A giant, elephantine form',
realm = 'nature',
level = 40,
rarity = 40,
duration = { 15, 20 },
apply = function(who, lev)
local DamageType = require 'engine.DamageType'
who.speed = who.speed - 5 - math.floor(lev/10)
who:adjustDis('to_a', 10 + math.floor(lev/6), true)
who:adjustDis('to_d', 5 + math.floor((lev*2)/3), true)
who:adjustStatForEquip('STR', math.floor(lev/4))
who:adjustStatForEquip('INT', -8)
who:adjustStatForEquip('WIS', -4)
who:adjustStatForEquip('DEX', -5)
who:adjustStatForEquip('CON', math.floor(lev/3))
who:adjustStatForEquip('CHA', -10)
if lev >= 10 then
who.resist_dam[DamageType.FEAR] = true
end
if lev >= 25 then
who.resist_dam[DamageType.CONFUSION] = true
end
if lev >= 30 then
who.flags.FREE_ACT = true
end
if lev >= 35 then
who.resist_dam[DamageType.NEXUS] = true
end
end,
info = function(who, lev)
local DamageType = require 'engine.DamageType'
local desc = num_info('STR', math.floor(lev/4), 'INT', -8, 'WIS', -4,
'DEX', -5, 'CON', math.floor(lev/3), 'CHA', -10)
desc:add(true)
desc:merge(num_info('AC', 10 + math.floor(lev/6),
'to dam', 5 + math.floor((lev*2)/3),
'speed', -5 - math.floor(lev/10)))
desc:merge(resist_info('Resist', true, lev,
10, DamageType.FEAR, 25, DamageType.CONFUSION,
35, DamageType.NEXUS))
desc:merge(list_info(true, lev, 30, 'free action'))
return desc
end,
}
newMimicryForm {
name = 'Bear',
desc = 'A fierce, terrible bear',
duration = { 50, 200 },
no_potion = true,
on_gain = function(who, mf)
-- Switch to Bearform combat if user requested it.
if config.settings.tome2.auto_bear_combat then
who:setMeleeStyle('BEARFORM_COMBAT', true)
end
end,
on_lose = function(who, mf)
-- Switch from Bearform combat to Weapon combat.
if who.melee_style == 'BEARFORM_COMBAT' then
who:setMeleeStyle('WEAPON_COMBAT', true)
end
end,
apply = function(who, lev)
local DamageType = require 'engine.DamageType'
who.speed = who.speed - 5 + math.floor(lev/5)
who:adjustDis('to_a', 5 + math.floor((lev*2)/3), true)
who:adjustStatForEquip('STR', math.floor(lev/11))
who:adjustStatForEquip('INT', math.floor(lev/11))
who:adjustStatForEquip('WIS', math.floor(lev/11))
who:adjustStatForEquip('DEX', -1)
who:adjustStatForEquip('CON', math.floor(lev/11))
who:adjustStatForEquip('CHA', -10)
if lev >= 10 then
who.flags.FREE_ACT = true
end
if lev >= 20 then
who.flags.REGENERATE = true
end
if lev >= 30 then
who.resist_dam[DamageType.CONFUSION] = true
end
if lev >= 35 then
who.resist_dam[DamageType.NEXUS] = true
end
end,
info = function(who, lev)
local DamageType = require 'engine.DamageType'
local desc = num_info('STR', math.floor(lev/11),
'INT', math.floor(lev/11),
'WIS', math.floor(lev/11),
'DEX', -1,
'CON', math.floor(lev/11),
'CHA', -10)
desc:add(true)
desc:merge(num_info('AC', 5 + math.floor((lev*2)/3),
'speed', -5 + math.floor(lev/5)))
desc:merge(resist_info('Resist', true, lev,
30, DamageType.CONFUSION, 35, DamageType.NEXUS))
desc:merge(list_info(true, 30, 10, 'free action', 20, 'regeneration'))
return desc
end,
}
newMimicryForm {
name = 'Balrog',
desc = 'A corrupted Maia',
duration = { 30, 70 },
no_potion = true,
apply = function(who, lev)
local DamageType = require 'engine.DamageType'
who:adjustStatForEquip('STR', 5 + math.floor(lev/5))
who:adjustStatForEquip('INT', math.floor(lev/10))
who:adjustStatForEquip('WIS', -5 - math.floor(lev/10))
who:adjustStatForEquip('DEX', math.floor(lev/10))
who:adjustStatForEquip('CON', 5 + math.floor(lev/5))
who:adjustStatForEquip('CHA', -5 - math.floor(lev/10))
who.immune_dam[DamageType.ACID] = true
who.immune_dam[DamageType.FIRE] = true
who.immune_dam[DamageType.ELEC] = true
who.resist_dam[DamageType.DARK] = true
who.resist_dam[DamageType.CHAOS] = true
who.resist_dam[DamageType.POISON] = true
who.flags.HOLD_LIFE = true
who.flags.FEATHER = true
who.flags.REGENERATE = true
who.flags.SH_FIRE = true
who.lite = who.lite + 1
end,
info = function(who, lev)
local DamageType = require 'engine.DamageType'
local desc = num_info('STR', 5 + math.floor(lev/5),
'INT', math.floor(lev/10),
'WIS', -5 - math.floor(lev/10),
'DEX', math.floor(lev/10),
'CON', 5 + math.floor(lev/5),
'CHA', -5 - math.floor(lev/10))
desc:merge(resist_info('Immune to', true, lev,
0, DamageType.FIRE, 0, DamageType.ACID,
0, DamageType.ELEC))
desc:merge(resist_info('Resist', true, lev,
0, DamageType.DARK, 0, DamageType.CHAOS,
0, DamageType.POISON))
desc:merge(list_info(true, lev, 0, 'hold life', 0, 'feather fall',
0, 'regeneration', 0, 'fire aura'))
desc:add(true)
desc:merge(num_info('light radius', 1))
return desc
end,
}
newMimicryForm {
name = 'Maia',
desc = 'A near god-like being',
duration = { 30, 70 },
no_potion = true,
apply = function(who, lev)
local DamageType = require 'engine.DamageType'
who:adjustStatForEquip('STR', 5 + math.floor(lev/5))
who:adjustStatForEquip('INT', 5 + math.floor(lev/5))
who:adjustStatForEquip('WIS', 5 + math.floor(lev/5))
who:adjustStatForEquip('DEX', 5 + math.floor(lev/5))
who:adjustStatForEquip('CON', 5 + math.floor(lev/5))
who:adjustStatForEquip('CHA', 5 + math.floor(lev/5))
who.immune_dam[DamageType.FIRE] = true
who.immune_dam[DamageType.ELEC] = true
who.immune_dam[DamageType.ACID] = true
who.immune_dam[DamageType.COLD] = true
who.resist_dam[DamageType.POISON] = true
who.resist_dam[DamageType.LITE] = true
who.resist_dam[DamageType.DARK] = true
who.resist_dam[DamageType.CHAOS] = true
who.flags.HOLD_LIFE = true
who.flags.FEATHER = true
who.flags.REGENERATE = true
end,
info = function(who, lev)
local DamageType = require 'engine.DamageType'
local desc = num_info('STR', 5 + math.floor(lev/5),
'INT', 5 + math.floor(lev/5),
'WIS', 5 + math.floor(lev/5),
'DEX', 5 + math.floor(lev/5),
'CON', 5 + math.floor(lev/5),
'CHA', 5 + math.floor(lev/5))
desc:merge(resist_info('Immune to', true, lev,
0, DamageType.FIRE, 0, DamageType.COLD,
0, DamageType.ACID, 0, DamageType.ELEC))
desc:merge(resist_info('Resist', true, lev,
0, DamageType.POISON, 0, DamageType.LITE,
0, DamageType.DARK, 0, DamageType.CHAOS))
desc:merge(list_info(true, lev, 0, 'hold life', 0, 'feather fall',
0, 'regeneration'))
return desc
end,
}
newMimicryForm {
name = 'Fire Elem.',
desc = 'A towering column of flames',
duration = { 10, 10 },
no_potion = true,
apply = function(who, lev)
local DamageType = require 'engine.DamageType'
who:adjustStatForEquip('STR', 5 + math.floor(lev/5))
who:adjustStatForEquip('DEX', 5 + math.floor(lev/5))
who:adjustStatForEquip('WIS', -5 - math.floor(lev/5))
who.immune_dam[DamageType.FIRE] = true
who.resist_dam[DamageType.POISON] = true
who.flags.SH_FIRE = true
who.lite = who.lite + 1
end,
info = function(who, lev)
local DamageType = require 'engine.DamageType'
local desc = num_info('STR', 5 + math.floor(lev/5),
'DEX', 5 + math.floor(lev/5),
'WIS', -5 - math.floor(lev/5))
desc:merge(resist_info('Immune to', true, lev, 0, DamageType.FIRE))
desc:merge(resist_info('Resist', true, lev, 0, DamageType.POISON))
desc:merge(list_info(true, lev, 0, 'fire aura'))
desc:add(true)
desc:merge(num_info('light radius', 1))
return desc
end,
}
newMimicryForm {
name = 'Abomination',
cloak_name = '& Abominable Cloak~',
desc = 'Abominations are failed experiments of powerful wizards.',
duration = { 20, 100 },
apply = function(who, lev)
who:adjustStatForEquip('STR', -10)
who:adjustStatForEquip('INT', -10)
who:adjustStatForEquip('WIS', -10)
who:adjustStatForEquip('DEX', -10)
who:adjustStatForEquip('CON', -10)
who:adjustStatForEquip('CHA', -10)
who.speed = who.speed - 10
who.flags.AGGRAVATE = true
end,
info = function(who, lev)
local desc = num_info('STR', -10, 'INT', -10, 'WIS', -10,
'DEX', -10, 'CON', -10, 'CHA', -10)
desc:add(true)
desc:merge(num_info('speed', -10))
desc:merge(list_info(true, lev, 0, 'aggravation'))
return desc
end,
}
PK 74NcL L data/junkart_data.lua-- $Id: junkart_data.lua 2937 2017-06-16 00:05:34Z dsb $
-- ToME - Tales of Middle-Earth
-- Copyright © 2012-2017 Scott Bigham
--
-- 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 .
--
-- Scott Bigham "Zizzo"
-- dsb-tome@killerbunnies.org
newJunkartName {
name = [[The Bag of Tricks]],
unid_name = [[a Bag]],
}
newJunkartName {
name = [[Kenault's Cantrip Generator]],
unid_name = [[a Shiny, Black Box]],
}
newJunkartName {
name = [[The Deck of Wild Magic]],
unid_name = [[a Deck of Cards]],
}
newJunkartName {
name = [[Baalzebub's Tormented Box]],
unid_name = [[a Rusty, Slimy Box]],
}
newJunkartName {
name = [[Baalzebub's All-seeing Servant]],
unid_name = [[an Eyeball]],
}
newJunkartName {
name = [[Boccob's Unfinished Works]],
unid_name = [[a Red Tome]],
}
newJunkartName {
name = [[The Gem of Fire]],
unid_name = [[a Red Gem]],
}
newJunkartName {
name = [[The Gem of Ice]],
unid_name = [[a Blue Gem]],
}
newJunkartName {
name = [[The Gem of Venom]],
unid_name = [[a Green Gem]],
}
newJunkartName {
name = [[The Gem of Knowledge]],
unid_name = [[a White Gem]],
}
newJunkartName {
name = [[The Gem of Rage]],
unid_name = [[an Orange Gem]],
}
newJunkartName {
name = [[The Gem of Hate]],
unid_name = [[a Black Gem]],
}
newJunkartName {
name = [[The Gem of Wisdom]],
unid_name = [[a Gray Gem]],
}
newJunkartName {
name = [[The Gem of Ghosts]],
unid_name = [[a Translucent Gem]],
}
newJunkartName {
name = [[The Shrunken Head of Nightmares]],
unid_name = [[a Shrunken Head]],
}
newJunkartName {
name = [[Raal's Voodoo Doll of Revenge]],
unid_name = [[a Voodoo Doll]],
}
newJunkartName {
name = [[The Mirror of Alternate Dimensions]],
unid_name = [[a Mirror]],
}
newJunkartName {
name = [[The Ebon Cube of Darkness]],
unid_name = [[a Black Cube]],
}
newJunkartName {
name = [[The Diamond Prism of Light]],
unid_name = [[a Prism]],
}
newJunkartName {
name = [[Gruumsh's Bottle of Death]],
unid_name = [[a Black Bottle]],
}
newJunkartName {
name = [[The Devil's Pentagram]],
unid_name = [[a Pentagram]],
}
newJunkartName {
name = [[Mordekainen's Sneaking Eye]],
unid_name = [[a Glass Eyeball]],
}
newJunkartName {
name = [[Kelek's Practical Joke]],
unid_name = [[something weird]],
}
newJunkartName {
name = [[Kelek's Wormhole Machine]],
unid_name = [[a Portable Vortex]],
}
newJunkartName {
name = [[Tenser's Alteration Manual]],
unid_name = [[a Green Tome]],
}
newJunkartName {
name = [[Raal's Tome of Unconventional Warfare]],
unid_name = [[a Black Tome]],
}
newJunkartName {
name = [[The Tome of Collected Weird Magic]],
unid_name = [[a White Tome]],
}
newJunkartName {
name = [[The Pendulum of Orcus]],
unid_name = [[a Pendulum]],
}
newJunkartName {
name = [[Raal's Black Candle]],
unid_name = [[a Black Candle]],
}
newJunkartName {
name = [[Mordekainen's Pocket Magician]],
unid_name = [[a Tiny Doll]],
}
newJunkartName {
name = [[Benetar's Death Ray Experiment]],
unid_name = [[a Black Staff]],
}
newJunkartName {
name = [[Valdarbon's Automatic Alchemist]],
unid_name = [[a Tiny Doll]],
}
newJunkartName {
name = [[Lloth's Ceremonial Dagger]],
unid_name = [[a Slimy Dagger]],
}
newJunkartName {
name = [[The Tome of Elven Household Magic]],
unid_name = [[a Gray Tome]],
}
newJunkartName {
name = [[a Parchment titled "Demigods and their Uses"]],
unid_name = [[a Parchment]],
}
newJunkartName {
name = [[a Parchment titled "Magic for the Layman"]],
unid_name = [[a Parchment]],
}
newJunkartName {
name = [[Balrilbon's Bag of Wondrous Tricks]],
unid_name = [[a Bag]],
}
newJunkartName {
name = [[Bumganir's Bag of Magic Toys]],
unid_name = [[a Bag]],
}
newJunkartName {
name = [[The Skull of Ancient Wisdom]],
unid_name = [[a Human Skull]],
}
newJunkartName {
name = [[Benetar's Mana Battery]],
unid_name = [[a Blue Box]],
}
newJunkartName {
name = [[Benetar's Portable Plague]],
unid_name = [[a Gray Bottle]],
}
newJunkartName {
name = [[Gamenlon's Summoning Manual]],
unid_name = [[an Orange Tome]],
}
newJunkartName {
name = [[Balrilbon's Soulgem]],
unid_name = [[a Shiny Gem]],
}
newJunkartName {
name = [[The Lost Works of Kenault]],
unid_name = [[an Ancient Gray Tome]],
}
newJunkartName {
name = [[The Crystal Ball of Godly Sights]],
unid_name = [[a Crystal Ball]],
}
newJunkartName {
name = [[The Box of Many Wonders]],
unid_name = [[a Golden Box]],
}
newJunkartName {
name = [[Baalzebub's Tormented Skullcap]],
unid_name = [[a Skullcap]],
}
newJunkartName {
name = [[The Jester's Cap of Insanity]],
unid_name = [[a Jester's Cap]],
}
newJunkartName {
name = [[The Bottomless Bottle]],
unid_name = [[a Broken Bottle]],
}
newJunkartName {
name = [[a Parchment titled "Planar Travel Made Easy"]],
unid_name = [[an Arcane Parchment]],
}
newJunkartName {
name = [[The Wand Construction Kit]],
unid_name = [[many Small Wooden Sticks]],
}
newJunkartName {
name = [[The Clay Tablets of Antiquity]],
unid_name = [[some Clay Tablets]],
}
newJunkartName {
name = [[Raal's Tormented Spirits]],
unid_name = [[a Smoky Vial]],
}
newJunkartName {
name = [[Tenser's Mechanical Magician]],
unid_name = [[a Doll]],
}
newJunkartName {
name = [[Boccob's Magical Mish-mash]],
unid_name = [[some Multi-colored Clay]],
}
newJunkartName {
name = [[The Grail of Kenault]],
unid_name = [[a Clay Jar]],
}
newJunkartName {
name = [[a Parchment titled "Household Magic"]],
unid_name = [[an Arcane Parchment]],
}
newJunkartName {
name = [[a Parchment titled "Tenser's Last Words"]],
unid_name = [[an Arcane Parchment]],
}
newJunkartName {
name = [[The Hand of Vecna]],
unid_name = [[a Decayed Hand]],
}
newJunkartName {
name = [[The Skull of Vecna]],
unid_name = [[a Decayed Skull]],
}
newJunkartName {
name = [[The Eye of Vecna]],
unid_name = [[a Decayed Eye]],
}
newJunkartName {
name = [[The Crystal Ball of The Witch-King of Angmar]],
unid_name = [[a Large Crystal Ball]],
}
newJunkartName {
name = [[a Parchment titled "Secrets of the Gnomish Wizards"]],
unid_name = [[an Arcane Parchment]],
}
newJunkartName {
name = [[The Medallion of Good Will]],
unid_name = [[a Medallion]],
}
newJunkartName {
name = [[The Immortal Skull of Benetar]],
unid_name = [[a Jewel-Encrusted Skull]],
}
newJunkartName {
name = [[Heward's Excellent Experimental Earmuffs]],
unid_name = [[some Earmuffs]],
}
newJunkartName {
name = [[Bigby's Big Book of Brutality]],
unid_name = [[a Battered Book]],
}
newJunkartName {
name = [[The Cunning Plan of Zog]],
unid_name = [[a Small Note]],
}
newJunkartName {
name = [[a Parchment titled "Immortality For Dummies"]],
unid_name = [[a Parchment]],
}
newJunkartName {
name = [[Raistlin's Ready Ranger]],
unid_name = [[a Small Figurine]],
}
newJunkartName {
name = [[Tenser's Torch of Spontaneous Combustion]],
unid_name = [[a Torch]],
}
newJunkartName {
name = [[Mordenkainen's Mysterious Mind-Masher]],
unid_name = [[a Rune]],
}
newJunkartName {
name = [[a Parchment titled "Famous Last Words"]],
unid_name = [[a Singed Parchment]],
}
newJunkartName {
name = [[Jor's Buckler of Missile Attraction]],
unid_name = [[a Holed Buckler]],
}
newJunkartName {
name = [[Jor's Compendium of Strange Behaviour]],
unid_name = [[a Red Book]],
}
newJunkartName {
name = [[Agannazar's Antique Acorn]],
unid_name = [[an Acorn]],
}
newJunkartName {
name = [[Cathal's Corrupting Cymbal]],
unid_name = [[a Cymbal]],
}
newJunkartName {
name = [[Pytar's Portable Pandemonium]],
unid_name = [[a Mechanical Music-Box]],
}
newJunkartName {
name = [[The Toenail of Vecna]],
unid_name = [[a Toenail]],
}
newJunkartName {
name = [[Jor's Book of Impossible Occurences]],
unid_name = [[a Torn Book]],
}
newJunkartName {
name = [[a Parchment titled "Finer Points of Munchkinism"]],
unid_name = [[a Greasy Parchment]],
}
newJunkartName {
name = [[Agannazar's Altruistic Assassin]],
unid_name = [[a Blackened Figurine]],
}
newJunkartName {
name = [[Tenser's Top-Heavy Teaspoon]],
unid_name = [[a Heavy Teaspoon]],
}
newJunkartName {
name = [[Olive's Omnipotent Ostrich]],
unid_name = [[an Avian Figurine]],
}
newJunkartName {
name = [[Cathal's Collapsible Crutch]],
unid_name = [[a Crutch]],
}
newJunkartActivation {
desc = [[die]],
use = function(self, who)
-- 'special_death_msg' isn't implemented yet; planning ahead... >;)
who:takeHit(5000, self, { special_death_msg = 'foolishly activated a death artifact' })
return { used = true }
end,
}
newJunkartActivation {
desc = [[ruin yourself]],
use = function(self, who)
who:takeHit(rng.dice(10, 10), self)
local Stats = require 'mod.class.interface.ActorStats'
for _, s in ipairs(Stats.stats) do
who:reduceStat(s, 25, { quiet=1, temporary=1 })
end
return { used = true }
end,
}
newJunkartActivation {
desc = [[wreck the surrounding terrain]],
cost = 100,
use = function(self, who)
spell.earthquake(who.x, who.y, 12)
return { used = true }
end,
}
newJunkartActivation {
desc = [[reduce your Intelligence]],
use = function(self, who)
who:reduceStat('INT', 25)
return { used = true }
end,
}
newJunkartActivation {
desc = [[reduce your Strength]],
use = function(self, who)
who:reduceStat('STR', 25)
return { used = true }
end,
}
newJunkartActivation {
desc = [[reduce your Constitution]],
use = function(self, who)
who:reduceStat('CON', 25)
return { used = true }
end,
}
newJunkartActivation {
desc = [[reduce your Charisma]],
use = function(self, who)
who:reduceStat('CHA', 25)
return { used = true }
end,
}
newJunkartActivation {
desc = [[reduce your Dexterity]],
use = function(self, who)
who:reduceStat('DEX', 25)
return { used = true }
end,
}
newJunkartActivation {
desc = [[reduce your Wisdom]],
use = function(self, who)
who:reduceStat('WIS', 25)
return { used = true }
end,
}
newJunkartActivation {
desc = [[reduce all your stats]],
use = function(self, who)
local Stats = require 'mod.class.interface.ActorStats'
for _, s in ipairs(Stats.stats) do
who:reduceStat(s, 15)
end
return { used = true }
end,
}
newJunkartActivation {
desc = [[severely reduce all your stats]],
use = function(self, who)
local Stats = require 'mod.class.interface.ActorStats'
for _, s in ipairs(Stats.stats) do
who:reduceStat(s, 25)
end
return { used = true }
end,
}
newJunkartActivation {
desc = [[reduce your experience]],
use = function(self, who)
who:gainExp(-math.floor(who.exp/20))
return { used = true }
end,
}
newJunkartActivation {
desc = [[severely reduce your experience]],
use = function(self, who)
who:gainExp(-math.floor(who.exp/10))
return { used = true }
end,
}
newJunkartActivation {
desc = [[teleport with range 100]],
cost = 45,
use = function(self, who)
spell.teleport(who, 100)
return { used = true }
end,
}
newJunkartActivation {
desc = [[summon a monster]],
cost = 0.5,
use = function(self, who)
spell.summon(nil, who.x, who.y, {})
return { used = true }
end,
}
newJunkartActivation {
desc = [[paralyze yourself]],
use = function(self, who)
who:setEffect(who.EFF_PARALYZED, 20 + rng.range(1, 10), {merge='combine'})
return { used = true }
end,
}
newJunkartActivation {
desc = [[induce hallucinations]],
cost = 10,
use = function(self, who)
who:setEffect(who.EFF_HALLUCINATION, 20 + rng.range(1, 10), {merge='combine'})
return { used = true }
end,
}
newJunkartActivation {
desc = [[poison yourself]],
use = function(self, who)
who:setEffect(who.EFF_POISON, 20 + rng.range(1, 10), {merge='combine'})
return { used = true }
end,
}
newJunkartActivation {
desc = [[increase your hunger]],
use = function(self, who)
who:setFood(util.FOOD.weak)
return { used = true }
end,
}
newJunkartActivation {
desc = [[stun yourself]],
use = function(self, who)
who:setEffect(who.EFF_STUN, 20 + rng.range(1, 10), {merge='combine'})
return { used = true }
end,
}
newJunkartActivation {
desc = [[give yourself cuts]],
use = function(self, who)
who:setEffect(who.EFF_CUT, 20 + rng.range(1, 10), {merge='combine'})
return { used = true }
end,
}
newJunkartActivation {
desc = [[confuse yourself]],
use = function(self, who)
-- This activation is tagged as "paranoia" in-game, but the
-- implementation does confusion instead.
who:setEffect(who.EFF_CONFUSED, 30 + rng.range(1, 10), {merge='combine'})
return { used = true }
end,
}
newJunkartActivation {
desc = [[confuse yourself]],
use = function(self, who)
who:setEffect(who.EFF_CONFUSED, 20 + rng.range(1, 10), {merge='combine'})
return { used = true }
end,
}
newJunkartActivation {
desc = [[blind yourself]],
use = function(self, who)
who:setEffect(who.EFF_BLIND, 20 + rng.range(1, 10), {merge='combine'})
return { used = true }
end,
}
newJunkartActivation {
desc = [[summon a pet]],
cost = 101,
use = function(self, who)
spell.summon(who, who.x, who.y, {})
return { used = true }
end,
}
newJunkartActivation {
desc = [[cure yourself of confusion]],
cost = 500,
use = function(self, who)
-- This activation is tagged as "cure paralysis" in-game, but the
-- implementation cures confusion instead.
who:removeEffect(who.EFF_CONFUSED)
return { used = true }
end,
}
newJunkartActivation {
desc = [[free yourself from hallucinations]],
cost = 100,
use = function(self, who)
who:removeEffect(who.EFF_HALLUCINATION)
return { used = true }
end,
}
newJunkartActivation {
desc = [[cure yourself of poison]],
cost = 100,
use = function(self, who)
who:removeEffect(who.EFF_POISON)
return { used = true }
end,
}
newJunkartActivation {
desc = [[reduce your hunger]],
cost = 100,
use = function(self, who)
who:setFood(util.FOOD.max - 1)
return { used = true }
end,
}
newJunkartActivation {
desc = [[recover from being stunned]],
cost = 100,
use = function(self, who)
who:removeEffect(who.EFF_STUN)
return { used = true }
end,
}
newJunkartActivation {
desc = [[cure cuts]],
cost = 100,
use = function(self, who)
who:removeEffect(who.EFF_CUT)
return { used = true }
end,
}
newJunkartActivation {
desc = [[remove your fear]],
cost = 100,
use = function(self, who)
who:removeEffect(who.EFF_AFRAID)
return { used = true }
end,
}
newJunkartActivation {
desc = [[cure yourself of confusion]],
cost = 100,
use = function(self, who)
who:removeEffect(who.EFF_CONFUSED)
return { used = true }
end,
}
newJunkartActivation {
desc = [[cure yourself of blindness]],
cost = 100,
use = function(self, who)
who:removeEffect(who.EFF_BLIND)
return { used = true }
end,
}
newJunkartActivation {
desc = [[heal 30hp and remove fear]],
cost = 50,
use = function(self, who)
who:heal(30, self)
who:removeEffect(who.EFF_AFRAID)
return { used = true }
end,
}
newJunkartActivation {
desc = [[heal 4d8hp and reduce cuts]],
cost = 75,
use = function(self, who)
who:heal(rng.dice(4, 8), self)
if who:hasEffect(who.EFF_CUT) then
who:setEffect(who.EFF_CUT, 0, { merge=function(cur, new) return math.max(0, math.floor(cur/2 - 50)) end })
end
return { used = true }
end,
}
newJunkartActivation {
desc = [[heal 700hp and cure cuts]],
cost = 100,
use = function(self, who)
who:heal(700, self)
who:removeEffect(who.EFF_CUT)
return { used = true }
end,
}
newJunkartActivation {
desc = [[cure various ailments]],
cost = 110,
use = function(self, who)
who:removeEffect(who.EFF_BLIND)
who:removeEffect(who.EFF_POISON)
who:removeEffect(who.EFF_CONFUSED)
who:removeEffect(who.EFF_STUN)
who:removeEffect(who.EFF_CUT)
who:removeEffect(who.EFF_HALLUCINATION)
return { used = true }
end,
}
newJunkartActivation {
desc = [[cast genocide]],
cost = 500,
use = function(self, who)
local ret = spell.genocide(who, 'target', nil, 4)
return { used=ret }
end,
}
newJunkartActivation {
desc = [[cast mass genocide]],
cost = 1000,
use = function(self, who)
spell.genocide(who, nil, 20, 3)
return { used=true }
end,
}
newJunkartActivation {
desc = [[restore your stats and experience]],
cost = 200,
use = function(self, who)
local Stats = require 'mod.class.interface.ActorStats'
for _, s in ipairs(Stats.stats) do who:restoreStat(s) end
if who.restoreExp then who:restoreExp() end
return { used = true }
end,
}
newJunkartActivation {
desc = [[illuminate your surroundings]],
cost = 100,
use = function(self, who)
game.logPlayer(who, 'Your %s wells with clear light...', self:getName{no_count=true, no_mods=true, no_pval=true, no_inscr=true})
spell.lite_room(who.x, who.y)
spell.ball_spell(who, nil, DamageType.LITE_WEAK, rng.dice(2, 15), 3, who.x, who.y)
return { used = true }
end,
}
newJunkartActivation {
desc = [[plunge your surroundings into darkness]],
use = function(self, who)
spell_unlite_room(who.x, who.y)
spell.ball_spell(who, nil, DamageType.DARK_WEAK, rng.dice(2, 10), 10, who.x, who.y)
return { used = true }
end,
}
-- This appears to be a duplicate in T2; we faithfully reproduce it. ;)
newJunkartActivation {
desc = [[teleport with range 100]],
cost = 45,
use = function(self, who)
spell.teleport(who, 100)
return { used = true }
end,
}
newJunkartActivation {
desc = [[teleport to another level]],
cost = 50,
use = function(self, who)
spell.player_teleport_level()
return { used = true }
end,
}
newJunkartActivation {
desc = [[create a magical item]],
cost = 3000,
use = function(self, who)
obj_util.acquirement(who.x, who.y, 1, false, false, self)
return { used = true }
end,
}
newJunkartActivation {
desc = [[do something weird]],
cost = 5,
use = function(self, who)
game.log("Nothing happens. That's weird...")
return { used = true }
end,
}
newJunkartActivation {
desc = [[aggravate nearby monsters]],
use = function(self, who)
spell.aggravate_near(who)
return { used = true }
end,
}
newJunkartActivation {
desc = [[corrupt your body]],
cost = 10,
use = function(self, who)
-- Maybe implement later?
game.log('...Dude. Seriously, why would you want to do that?')
-- return { used = true }
return {}
end,
}
newJunkartActivation {
desc = [[cure insanity]],
cost = 200,
use = function(self, who)
who:healSanity(10, 10)
return { used = true }
end,
}
newJunkartActivation {
desc = [[absorb light from your surroundings and release it as a magical attack]],
cost = 80,
use = function(self, who)
game.log('Sorry, not implemented. (ACT_LIGHT_ABSORBTION)')
-- return { used = true }
return {}
end,
}
PK 74N&SkK3