diff --git a/game/modules/tome/class/EscortRewards.lua b/game/modules/tome/class/EscortRewards.lua new file mode 100644 index 0000000000000000000000000000000000000000..84d9c953796b645f96d74c16de0d4a31fb169506 --- /dev/null +++ b/game/modules/tome/class/EscortRewards.lua @@ -0,0 +1,587 @@ +-- ToME - Tales of Maj'Eyal +-- Copyright (C) 2009 - 2019 Nicolas Casalini +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program. If not, see <http://www.gnu.org/licenses/>. +-- +-- Nicolas Casalini "DarkGod" +-- darkgod@te4.org + +require "engine.class" +local Talents = require("engine.interface.ActorTalents") +local Stats = require("engine.interface.ActorStats") + +module(..., package.seeall, class.make) + +function _M:listGivers() + local possible_types = { + warrior = { + chance = 70, + classes = {"Berserker", "Bulwark"}, + escort = { name="lost warrior", random="male", + text = _t[[Please help me! I am afraid I lost myself in this place. I know there is a recall portal left around here by a friend, but I have fought too many battles, and I fear I will not make it. Would you help me?]], + actor = { + type = "humanoid", subtype = "human", image = "player/higher_male.png", + display = "@", color=colors.UMBER, + name = _t"%s, the lost warrior", + desc = _t[[He looks tired and wounded.]], + autolevel = "warrior", + ai = "escort_quest", ai_state = { talent_in=4, }, + stats = { str=18, dex=13, mag=5, con=15 }, + + body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 }, + resolvers.equip{ {type="weapon", subtype="greatsword", autoreq=true} }, + resolvers.talents{ [Talents.T_STUNNING_BLOW]=1, }, + lite = 4, + rank = 2, + exp_worth = 1, + antimagic_ok = true, + + max_life = 50, life_regen = 0, + life_rating = 12, + combat_armor = 3, combat_def = 3, + inc_damage = {all=-50}, + }, + }, + }, + divination = { + chance = 70, + classes = {"Archmage"}, + escort = { name="injured seer", random="female", + text = _t[[Please help me! I am afraid I lost myself in this place. I know there is a recall portal left around here by a friend, but I will not be able to continue the road alone. Would you help me?]], + actor = { + name = _t"%s, the injured seer", + type = "humanoid", subtype = "elf", female=true, image = "player/halfling_female.png", + display = "@", color=colors.LIGHT_BLUE, + desc = _t[[She looks tired and wounded.]], + autolevel = "caster", + ai = "escort_quest", ai_state = { talent_in=4, }, + stats = { str=8, dex=7, mag=18, con=12 }, + + body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 }, + resolvers.equip{ {type="weapon", subtype="staff", autoreq=true} }, + resolvers.talents{ [Talents.T_MANATHRUST]=1, }, + lite = 4, + rank = 2, + exp_worth = 1, + + max_life = 50, life_regen = 0, + life_rating = 11, + combat_armor = 3, combat_def = 3, + inc_damage = {all=-50}, + }, + }, + }, + survival = { + chance = 70, + classes = {"Rogue", "Shadowblade", "Marauder"}, + escort = { name="repented thief", random="male", + text = _t[[Please help me! I am afraid I lost myself in this place. I know there is a recall portal left around here by a friend, but I have fought too many battles, and I fear I will not make it. Would you help me?]], + actor = { + name = _t"%s, the repented thief", + type = "humanoid", subtype = "halfling", image = "player/cornac_male.png", + display = "@", color=colors.BLUE, + desc = _t[[He looks tired and wounded.]], + autolevel = "rogue", + ai = "escort_quest", ai_state = { talent_in=4, }, + stats = { str=8, dex=7, mag=18, con=12 }, + + body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 }, + resolvers.equip{ {type="weapon", subtype="dagger", autoreq=true}, {type="weapon", subtype="dagger", autoreq=true} }, + resolvers.talents{ [Talents.T_DIRTY_FIGHTING]=1, }, + lite = 4, + rank = 2, + exp_worth = 1, + antimagic_ok = true, + + max_life = 50, life_regen = 0, + life_rating = 11, + combat_armor = 3, combat_def = 3, + inc_damage = {all=-50}, + }, + }, + }, + alchemy = { + chance = 70, + classes = {"Alchemist"}, + escort = { name="lone alchemist", random="male", + text = _t[[Please help me! I am afraid I lost myself in this place. I know there is a recall portal left around here by a friend, but I have fought too many battles, and I fear I will not make it. Would you help me?]], + actor = { + name = _t"%s, the lone alchemist", + type = "humanoid", subtype = "human", image = "player/shalore_male.png", + display = "@", color=colors.AQUAMARINE, + desc = _t[[He looks tired and wounded.]], + autolevel = "rogue", + ai = "escort_quest", ai_state = { talent_in=4, }, + stats = { str=8, dex=7, mag=18, con=12 }, + + body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 }, + resolvers.equip{ {type="weapon", subtype="staff", autoreq=true} }, + resolvers.talents{ [Talents.T_HEAT]=1, }, + lite = 4, + rank = 2, + exp_worth = 1, + + max_life = 50, life_regen = 0, + life_rating = 11, + combat_armor = 3, combat_def = 3, + inc_damage = {all=-50}, + }, + }, + }, + sun_paladin = { + chance = 70, + classes = {"Sun Paladin"}, + escort = { name="lost sun paladin", random="female", + text = _t[[Please help me! I am afraid I lost myself in this place. I know there is a recall portal left around here by a friend, but I have fought too many battles, and I fear I will not make it. Would you help me?]], + actor = { + name = _t"%s, the lost sun paladin", + type = "humanoid", subtype = "human", female=true, image = "player/higher_female.png", + display = "@", color=colors.GOLD, + desc = _t[[She looks tired and wounded.]], + autolevel = "warriormage", + ai = "escort_quest", ai_state = { talent_in=4, }, + stats = { str=18, dex=7, mag=18, con=12 }, + + body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 }, + resolvers.equip{ {type="weapon", subtype="mace", autoreq=true} }, + resolvers.talents{ [Talents.T_CHANT_OF_FORTRESS]=1, }, + lite = 4, + rank = 2, + exp_worth = 1, + + max_life = 50, life_regen = 0, + life_rating = 12, + combat_armor = 3, combat_def = 3, + inc_damage = {all=-50}, + sunwall_query = true, + }, + }, + }, + defiler = { + chance = 70, + classes = {"Corruptor", "Reaver"}, + escort = { name="lost defiler", random="female", + text = _t[[Please help me! I am afraid I lost myself in this place. I know there is a recall portal left around here by a friend, but I have fought too many battles, and I fear I will not make it. Would you help me?]], + actor = { + name = _t"%s, the lost defiler", + type = "humanoid", subtype = "human", female=true, image = "player/higher_female.png", + display = "@", color=colors.YELLOW, + desc = _t[[She looks tired and wounded.]], + autolevel = "caster", + ai = "escort_quest", ai_state = { talent_in=4, }, + stats = { str=8, dex=7, mag=18, con=12 }, + + body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 }, + resolvers.equip{ {type="weapon", subtype="staff", autoreq=true} }, + resolvers.talents{ [Talents.T_CURSE_OF_IMPOTENCE]=1, }, + lite = 4, + rank = 2, + exp_worth = 1, + + max_life = 50, life_regen = 0, + life_rating = 11, + combat_armor = 3, combat_def = 3, + inc_damage = {all=-50}, + }, + }, + }, + temporal = { + chance = 30, + classes = {"Paradox Mage", "Temporal Warden"}, + escort = { name="temporal explorer", random="player", portal=_t"temporal portal", + text = _t[[Oh but you are ... are you ?! ME?! + So I was right, this is not my original time-thread! + Please help me! I am afraid I lost myself in this place. I know there is a temporal portal left around here by a friend, but I have fought too many battles, and I fear I will not make it. Would you help me? Would you help .. yourself?]], + actor = { + name = _t"%s, temporal explorer", + type = "humanoid", subtype = "human", female=true, image = "player/higher_female.png", + display = "@", color=colors.YELLOW, + desc = _t[[She looks tired and wounded. She is so similar to you and yet completely different. Weird.]], + autolevel = "caster", + ai = "escort_quest", ai_state = { talent_in=4, }, + stats = { str=8, dex=7, mag=18, con=12 }, + + body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 }, + resolvers.equip{ {type="weapon", subtype="staff", autoreq=true} }, + resolvers.talents{ [Talents.T_DUST_TO_DUST]=1, }, + lite = 4, + rank = 2, + exp_worth = 1, + + max_life = 50, life_regen = 0, + life_rating = 11, + combat_armor = 3, combat_def = 3, + inc_damage = {all=-50}, + }, + }, + }, + exotic = { + chance = 30, + classes = "any", + escort = { name="worried loremaster", random="female", + text = _t[[Please help me! I am afraid I lost myself in this place. I know there is a recall portal left around here by a friend, but I have fought too many battles, and I fear I will not make it. Would you help me?]], + actor = { + name = _t"%s, the worried loremaster", + type = "humanoid", subtype = "human", female=true, image = "player/thalore_female.png", + display = "@", color=colors.LIGHT_GREEN, + desc = _t[[She looks tired and wounded.]], + autolevel = "wildcaster", + ai = "escort_quest", ai_state = { talent_in=4, }, + stats = { str=8, dex=7, mag=18, con=12 }, + + body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 }, + resolvers.equip{ {type="weapon", subtype="staff", autoreq=true} }, + resolvers.talents{ [Talents.T_MIND_SEAR]=1, }, + lite = 4, + rank = 2, + exp_worth = 1, + antimagic_ok = true, + + max_life = 50, life_regen = 0, + life_rating = 10, + combat_armor = 3, combat_def = 3, + inc_damage = {all=-50}, + }, + }, + }, + } + local hd = {"EscortRewards:givers", possible_types=possible_types} + if self:triggerHook(hd) then possible_types = hd.possible_types end + return possible_types +end + +function _M:getGiver() + local possible_types = self:listGivers() + + game.state.escorts_seen = game.state.escorts_seen or {} + local escorts_seen = game.state.escorts_seen + local kind_id, kind + while true do + kind_id = rng.tableIndex(possible_types) + kind = possible_types[kind_id] + if not kind.unique or not escorts_seen[kind_id] then + if rng.percent(kind.chance) then break end + end + end + escorts_seen[kind_id] = (escorts_seen[kind_id] or 0) + 1 + + return kind_id, kind +end + +function _M:listRewards() + local reward_types = { + warrior = { + types = { + ["technique/conditioning"] = 1.0, + }, + talents = { + [Talents.T_VITALITY] = 1, + [Talents.T_UNFLINCHING_RESOLVE] = 1, + [Talents.T_EXOTIC_WEAPONS_MASTERY] = 1, + }, + stats = { + [Stats.STAT_STR] = 5, + [Stats.STAT_CON] = 5, + }, + }, + divination = { + types = { + ["spell/divination"] = 1.0, + }, + talents = { + [Talents.T_ARCANE_EYE] = 1, + [Talents.T_PREMONITION] = 1, + [Talents.T_VISION] = 1, + }, + stats = { + [Stats.STAT_MAG] = 5, + [Stats.STAT_WIL] = 5, + }, + antimagic = { + types = { + ["wild-gift/call"] = 1.0, + }, + saves = { mental = 12 }, + talents = { + [Talents.T_NATURE_TOUCH] = 1, + [Talents.T_EARTH_S_EYES] = 1, + }, + stats = { + [Stats.STAT_CUN] = 5, + [Stats.STAT_WIL] = 5, + }, + }, + }, + alchemy = { + types = { + ["spell/staff-combat"] = 1.0, + ["spell/stone-alchemy"] = 1.0, + }, + talents = { + [Talents.T_CHANNEL_STAFF] = 1, + [Talents.T_STAFF_MASTERY] = 1, + [Talents.T_STONE_TOUCH] = 1, + }, + stats = { + [Stats.STAT_MAG] = 5, + [Stats.STAT_DEX] = 5, + }, + antimagic = { + types = { + ["wild-gift/mindstar-mastery"] = 1.0, + }, + talents = { + [Talents.T_PSIBLADES] = 1, + [Talents.T_THORN_GRAB] = 1, + }, + saves = { spell = 12 }, + stats = { + [Stats.STAT_WIL] = 5, + [Stats.STAT_DEX] = 5, + }, + }, + }, + survival = { + types = { + ["cunning/survival"] = 1.0, + ["cunning/scoundrel"] = 1.0, + }, + talents = { + [Talents.T_HEIGHTENED_SENSES] = 1, + [Talents.T_TRACK] = 1, + [Talents.T_LACERATING_STRIKES] = 1, + [Talents.T_MISDIRECTION] = 1, + }, + stats = { + [Stats.STAT_DEX] = 5, + [Stats.STAT_CUN] = 5, + }, + }, + sun_paladin = { + types = { + ["celestial/chants"] = 1.0, + }, + talents = { + [Talents.T_CHANT_OF_FORTITUDE] = 1, + [Talents.T_CHANT_OF_FORTRESS] = 1, + }, + stats = { + [Stats.STAT_STR] = 5, + [Stats.STAT_MAG] = 5, + }, + antimagic = { + types = { + ["psionic/augmented-mobility"] = 1.0, + }, + talents = { + [Talents.T_SKATE] = 1, + [Talents.T_TELEKINETIC_LEAP] = 1, + }, + saves = { spell = 12, phys = 12 }, + stats = { + [Stats.STAT_CUN] = 5, + [Stats.STAT_WIL] = 5, + }, + }, + }, + defiler = { + types = { + ["corruption/curses"] = 1.0, + }, + talents = { + [Talents.T_CURSE_OF_DEFENSELESSNESS] = 1, + [Talents.T_CURSE_OF_IMPOTENCE] = 1, + [Talents.T_CURSE_OF_DEATH] = 1, + }, + stats = { + [Stats.STAT_CUN] = 5, + [Stats.STAT_MAG] = 5, + }, + antimagic = { + types = { + ["psionic/feedback"] = 1.0, + }, + talents = { + [Talents.T_RESONANCE_FIELD] = 1, + [Talents.T_CONVERSION] = 1, + }, + saves = { spell = 12, mental = 12 }, + stats = { + [Stats.STAT_CUN] = 5, + [Stats.STAT_WIL] = 5, + }, + }, + }, + temporal = { + types = { + ["chronomancy/chronomancy"] = 1.0, + }, + talents = { + [Talents.T_PRECOGNITION] = 1, + [Talents.T_FORESIGHT] = 1, + }, + stats = { + [Stats.STAT_MAG] = 5, + [Stats.STAT_WIL] = 5, + }, + antimagic = { + types = { + ["psionic/dreaming"] = 1.0, + }, + talents = { + [Talents.T_SLEEP] = 1, + [Talents.T_DREAM_WALK] = 1, + }, + saves = { spell = 12 }, + stats = { + [Stats.STAT_WIL] = 5, + [Stats.STAT_CUN] = 5, + }, + }, + }, + exotic = { + talents = { + [Talents.T_DISARM] = 1, + [Talents.T_SPIT_POISON] = 1, + [Talents.T_MIND_SEAR] = 1, + }, + stats = { + [Stats.STAT_STR] = 5, + [Stats.STAT_DEX] = 5, + [Stats.STAT_MAG] = 5, + [Stats.STAT_WIL] = 5, + [Stats.STAT_CUN] = 5, + [Stats.STAT_CON] = 5, + }, + }, + } + + local hd = {"EscortRewards:rewards", reward_types=reward_types} + if self:triggerHook(hd) then reward_types = hd.reward_types end + + return reward_types +end + +function _M:getReward(reward_type) + local rts = self:listRewards() + if rts[reward_type] then + return rts[reward_type] + else + game.log('[EscortRewards] ERROR: reward_type "'..tostring(reward_type)..'" not defined, using warrior') + return rts.warrior + end +end + +local saves_name = { mental=_t"mental", spell=_t"spell", phys=_t"physical"} +local saves_tooltips = { mental="MENTAL", spell="SPELL", phys="PHYS"} + +function _M:rewardChatAnwsers(who, reward, jump_to, on_chose) + local answers = {} + if reward.stats then + for i = 1, #who.stats_def do if reward.stats[i] then + local doit = function(npc, player) game.party:reward(_t"Select the party member to receive the reward:", function(player) + player.inc_stats[i] = (player.inc_stats[i] or 0) + reward.stats[i] + player:onStatChange(i, reward.stats[i]) + player.changed = true + on_chose(npc, player, "stat", i, reward.stats[i], ("improved %s by +%d"):tformat(npc.stats_def[i].name, reward.stats[i])) + end) end + answers[#answers+1] = {("[Improve %s by +%d]"):tformat(who.stats_def[i].name, reward.stats[i]), + jump=jump_to or "done", + action=doit, + on_select=function(npc, player) + game.tooltip_x, game.tooltip_y = 1, 1 + local TooltipsData = require("mod.class.interface.TooltipsData") + game:tooltipDisplayAtMap(game.w, game.h, TooltipsData["TOOLTIP_"..npc.stats_def[i].short_name:upper()]) + end, + } + end end + end + if reward.saves then + for save, v in pairs(reward.saves) do + local doit = function(npc, player) game.party:reward(_t"Select the party member to receive the reward:", function(player) + player:attr("combat_"..save.."resist", v) + player.changed = true + on_chose(npc, player, "save", save, v, ("improved %s save by +%d"):tformat(saves_name[save], v)) + end) end + answers[#answers+1] = {("[Improve %s save by +%d]"):tformat(saves_name[save], v), + jump=jump_to or "done", + action=doit, + on_select=function(npc, player) + game.tooltip_x, game.tooltip_y = 1, 1 + local TooltipsData = require("mod.class.interface.TooltipsData") + game:tooltipDisplayAtMap(game.w, game.h, TooltipsData["TOOLTIP_"..saves_tooltips[save]:upper().."_SAVE"]) + end, + } + end + end + if reward.talents then + for tid, level in pairs(reward.talents) do + local t = who:getTalentFromId(tid) + level = math.min(t.points - who:getTalentLevelRaw(tid), level) + if level > 0 then + local doit = function(npc, player) game.party:reward(_t"Select the party member to receive the reward:", function(player) + if player:knowTalentType(t.type[1]) == nil then player:setTalentTypeMastery(t.type[1], 1.0) end + player:learnTalent(tid, true, level, {no_unlearn=true}) + if t.hide then player.__show_special_talents = player.__show_special_talents or {} player.__show_special_talents[tid] = true end + on_chose(npc, player, "talent", tid, level, ("%s talent %s (+%d level(s))"):tformat(player:knowTalent(tid) and _t"improved" or _t"learnt", t.name, level)) + end) end + answers[#answers+1] = { + ("[%s talent %s (+%d level(s))]"):tformat(who:knowTalent(tid) and _t"Improve" or _t"Learn", t.name, level), + jump=jump_to or "done", + action=doit, + on_select=function(npc, player) + game.tooltip_x, game.tooltip_y = 1, 1 + local mastery = nil + if player:knowTalentType(t.type[1]) == nil then mastery = 1.0 end + game:tooltipDisplayAtMap(game.w, game.h, ("#GOLD#%s#LAST#\n%s"):tformat(t.name,tostring(player:getTalentFullDescription(t, 1, nil, mastery)))) + end, + } + end + end + end + if reward.types then + for tt, mastery in pairs(reward.types) do if who:knowTalentType(tt) == nil then + local tt_def = who:getTalentTypeFrom(tt) + local cat = tt_def.type:gsub("/.*", "") + local doit = function(npc, player) game.party:reward(_t"Select the party member to receive the reward:", function(player) + if player:knowTalentType(tt) == nil then player:setTalentTypeMastery(tt, mastery - 1 + player:getTalentTypeMastery(tt)) end + player:learnTalentType(tt, false) + on_chose(npc, player, "talent_type", tt, mastery, ("gained talent category %s (at mastery %0.2f)"):tformat(_t(cat):capitalize().." / "..tt_def.name:capitalize(), mastery)) + end) end + answers[#answers+1] = {("[Allow training of talent category %s (at mastery %0.2f)]"):tformat(_t(cat):capitalize().." / "..tt_def.name:capitalize(), mastery), + jump=jump_to or "done", + action=doit, + on_select=function(npc, player) + game.tooltip_x, game.tooltip_y = 1, 1 + game:tooltipDisplayAtMap(game.w, game.h, ("#GOLD#%s / %s#LAST#\n%s"):tformat(_t(cat):capitalize(), tt_def.name:capitalize(), tt_def.description)) + end, + } + end end + end + if reward.special then + for _, data in ipairs(reward.special) do + answers[#answers+1] = {data.desc, + jump=jump_to or "done", + action=function(npc, player) + return data.action(npc, player, on_chose) + end, + on_select=function(npc, player) + game.tooltip_x, game.tooltip_y = 1, 1 + game:tooltipDisplayAtMap(game.w, game.h, data.tooltip) + end, + } + end + end + return answers +end diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua index 1ba13723018157c058f5d89b3ced1e974ef4aa81..6a954f690805e07a94a9708ea986dfd7ee9dd3bd 100644 --- a/game/modules/tome/class/Player.lua +++ b/game/modules/tome/class/Player.lua @@ -125,7 +125,7 @@ function _M:onBirth(birther) local zones = {} for i, zd in ipairs(def) do for j = zd[2], zd[3] do zones[#zones+1] = {zd[1], j} end end self.random_escort_levels = {} - for i = 1, 9 do + for i = 1, world.random_escort_possibilities_max or 9 do local z = rng.tableRemove(zones) print("Random escort on", z[1], z[2]) self.random_escort_levels[z[1]] = self.random_escort_levels[z[1]] or {} @@ -167,7 +167,11 @@ function _M:onEnterLevel(zone, level) -- Fire random escort quest if self.random_escort_levels and self.random_escort_levels[escort_zone_name] and self.random_escort_levels[escort_zone_name][level.level - escort_zone_offset] then - self:grantQuest("escort-duty") + if self:triggerHook{"Player:onEnterLevel:generateEscort", zone=zone, level=level} then + -- nothing + elseif game:isCampaign("Maj'Eyal") then + self:grantQuest("escort-duty") + end end -- Cancel effects diff --git a/game/modules/tome/data/chats/escort-quest.lua b/game/modules/tome/data/chats/escort-quest.lua index 88e4b37652ddc99142ac5981bebfef31a58d2306..e7924ad42c34b7a7fa3df7db5b04f2fde10dbc28 100644 --- a/game/modules/tome/data/chats/escort-quest.lua +++ b/game/modules/tome/data/chats/escort-quest.lua @@ -19,312 +19,22 @@ local Talents = require("engine.interface.ActorTalents") local Stats = require("engine.interface.ActorStats") +local EscortRewards = require("mod.class.EscortRewards") -local reward_types = { - warrior = { - types = { - ["technique/conditioning"] = 1.0, - }, - talents = { - [Talents.T_VITALITY] = 1, - [Talents.T_UNFLINCHING_RESOLVE] = 1, - [Talents.T_EXOTIC_WEAPONS_MASTERY] = 1, - }, - stats = { - [Stats.STAT_STR] = 5, - [Stats.STAT_CON] = 5, - }, - }, - divination = { - types = { - ["spell/divination"] = 1.0, - }, - talents = { - [Talents.T_ARCANE_EYE] = 1, - [Talents.T_PREMONITION] = 1, - [Talents.T_VISION] = 1, - }, - stats = { - [Stats.STAT_MAG] = 5, - [Stats.STAT_WIL] = 5, - }, - antimagic = { - types = { - ["wild-gift/call"] = 1.0, - }, - saves = { mental = 12 }, - talents = { - [Talents.T_NATURE_TOUCH] = 1, - [Talents.T_EARTH_S_EYES] = 1, - }, - stats = { - [Stats.STAT_CUN] = 5, - [Stats.STAT_WIL] = 5, - }, - }, - }, - alchemy = { - types = { - ["spell/staff-combat"] = 1.0, - ["spell/stone-alchemy"] = 1.0, - }, - talents = { - [Talents.T_CHANNEL_STAFF] = 1, - [Talents.T_STAFF_MASTERY] = 1, - [Talents.T_STONE_TOUCH] = 1, - }, - stats = { - [Stats.STAT_MAG] = 5, - [Stats.STAT_DEX] = 5, - }, - antimagic = { - types = { - ["wild-gift/mindstar-mastery"] = 1.0, - }, - talents = { - [Talents.T_PSIBLADES] = 1, - [Talents.T_THORN_GRAB] = 1, - }, - saves = { spell = 12 }, - stats = { - [Stats.STAT_WIL] = 5, - [Stats.STAT_DEX] = 5, - }, - }, - }, - survival = { - types = { - ["cunning/survival"] = 1.0, - ["cunning/scoundrel"] = 1.0, - }, - talents = { - [Talents.T_HEIGHTENED_SENSES] = 1, - [Talents.T_TRACK] = 1, - [Talents.T_LACERATING_STRIKES] = 1, - [Talents.T_MISDIRECTION] = 1, - }, - stats = { - [Stats.STAT_DEX] = 5, - [Stats.STAT_CUN] = 5, - }, - }, - sun_paladin = { - types = { - ["celestial/chants"] = 1.0, - }, - talents = { - [Talents.T_CHANT_OF_FORTITUDE] = 1, - [Talents.T_CHANT_OF_FORTRESS] = 1, - }, - stats = { - [Stats.STAT_STR] = 5, - [Stats.STAT_MAG] = 5, - }, - antimagic = { - types = { - ["psionic/augmented-mobility"] = 1.0, - }, - talents = { - [Talents.T_SKATE] = 1, - [Talents.T_TELEKINETIC_LEAP] = 1, - }, - saves = { spell = 12, phys = 12 }, - stats = { - [Stats.STAT_CUN] = 5, - [Stats.STAT_WIL] = 5, - }, - }, - }, - defiler = { - types = { - ["corruption/curses"] = 1.0, - }, - talents = { - [Talents.T_CURSE_OF_DEFENSELESSNESS] = 1, - [Talents.T_CURSE_OF_IMPOTENCE] = 1, - [Talents.T_CURSE_OF_DEATH] = 1, - }, - stats = { - [Stats.STAT_CUN] = 5, - [Stats.STAT_MAG] = 5, - }, - antimagic = { - types = { - ["psionic/feedback"] = 1.0, - }, - talents = { - [Talents.T_RESONANCE_FIELD] = 1, - [Talents.T_CONVERSION] = 1, - }, - saves = { spell = 12, mental = 12 }, - stats = { - [Stats.STAT_CUN] = 5, - [Stats.STAT_WIL] = 5, - }, - }, - }, - temporal = { - types = { - ["chronomancy/chronomancy"] = 1.0, - }, - talents = { - [Talents.T_PRECOGNITION] = 1, - [Talents.T_FORESIGHT] = 1, - }, - stats = { - [Stats.STAT_MAG] = 5, - [Stats.STAT_WIL] = 5, - }, - antimagic = { - types = { - ["psionic/dreaming"] = 1.0, - }, - talents = { - [Talents.T_SLEEP] = 1, - [Talents.T_DREAM_WALK] = 1, - }, - saves = { spell = 12 }, - stats = { - [Stats.STAT_WIL] = 5, - [Stats.STAT_CUN] = 5, - }, - }, - }, - exotic = { - talents = { - [Talents.T_DISARM] = 1, --- [Talents.T_WATER_JET] = 1, - [Talents.T_SPIT_POISON] = 1, - [Talents.T_MIND_SEAR] = 1, - }, - stats = { - [Stats.STAT_STR] = 5, - [Stats.STAT_DEX] = 5, - [Stats.STAT_MAG] = 5, - [Stats.STAT_WIL] = 5, - [Stats.STAT_CUN] = 5, - [Stats.STAT_CON] = 5, - }, - }, -} - -local hd = {"Quest:escort:reward", reward_types=reward_types} -if require("engine.class"):triggerHook(hd) then reward_types = hd.reward_types end - -local reward = reward_types[npc.reward_type] -if not reward then reward = reward_types.warrior end +local reward = EscortRewards:getReward(npc.reward_type) local quest = game.player:hasQuest(npc.quest_id) if quest.to_zigur and reward.antimagic then reward = reward.antimagic reward.is_antimagic = true end game.player:registerEscorts(quest.to_zigur and "zigur" or "saved") -local saves_name = { mental=_t"mental", spell=_t"spell", phys=_t"physical"} -local saves_tooltips = { mental="MENTAL", spell="SPELL", phys="PHYS"} - -local function generate_rewards() - local answers = {} - if reward.stats then - for i = 1, #npc.stats_def do if reward.stats[i] then - local doit = function(npc, player) game.party:reward(_t"Select the party member to receive the reward:", function(player) - player.inc_stats[i] = (player.inc_stats[i] or 0) + reward.stats[i] - player:onStatChange(i, reward.stats[i]) - player.changed = true - player:hasQuest(npc.quest_id).reward_message = ("improved %s by +%d"):tformat(npc.stats_def[i].name, reward.stats[i]) - end) end - answers[#answers+1] = {("[Improve %s by +%d]"):tformat(npc.stats_def[i].name, reward.stats[i]), - jump="done", - action=doit, - on_select=function(npc, player) - game.tooltip_x, game.tooltip_y = 1, 1 - local TooltipsData = require("mod.class.interface.TooltipsData") - game:tooltipDisplayAtMap(game.w, game.h, TooltipsData["TOOLTIP_"..npc.stats_def[i].short_name:upper()]) - end, - } - end end - end - if reward.saves then - for save, v in pairs(reward.saves) do - local doit = function(npc, player) game.party:reward(_t"Select the party member to receive the reward:", function(player) - player:attr("combat_"..save.."resist", v) - player.changed = true - player:hasQuest(npc.quest_id).reward_message = ("improved %s save by +%d"):tformat(saves_name[save], v) - end) end - answers[#answers+1] = {("[Improve %s save by +%d]"):tformat(saves_name[save], v), - jump="done", - action=doit, - on_select=function(npc, player) - game.tooltip_x, game.tooltip_y = 1, 1 - local TooltipsData = require("mod.class.interface.TooltipsData") - game:tooltipDisplayAtMap(game.w, game.h, TooltipsData["TOOLTIP_"..saves_tooltips[save]:upper().."_SAVE"]) - end, - } - end - end - if reward.talents then - for tid, level in pairs(reward.talents) do - local t = npc:getTalentFromId(tid) - level = math.min(t.points - game.player:getTalentLevelRaw(tid), level) - if level > 0 then - local doit = function(npc, player) game.party:reward(_t"Select the party member to receive the reward:", function(player) - if game.player:knowTalentType(t.type[1]) == nil then player:setTalentTypeMastery(t.type[1], 1.0) end - player:learnTalent(tid, true, level, {no_unlearn=true}) - if t.hide then player.__show_special_talents = player.__show_special_talents or {} player.__show_special_talents[tid] = true end - player:hasQuest(npc.quest_id).reward_message = ("%s talent %s (+%d level(s))"):tformat(game.player:knowTalent(tid) and _t"improved" or _t"learnt", t.name, level) - end) end - answers[#answers+1] = { - ("[%s talent %s (+%d level(s))]"):tformat(game.player:knowTalent(tid) and _t"Improve" or _t"Learn", t.name, level), - jump="done", - action=doit, - on_select=function(npc, player) - game.tooltip_x, game.tooltip_y = 1, 1 - local mastery = nil - if player:knowTalentType(t.type[1]) == nil then mastery = 1.0 end - game:tooltipDisplayAtMap(game.w, game.h, ("#GOLD#%s#LAST#\n%s"):tformat(t.name,tostring(player:getTalentFullDescription(t, 1, nil, mastery)))) - end, - } - end - end - end - if reward.types then - for tt, mastery in pairs(reward.types) do if game.player:knowTalentType(tt) == nil then - local tt_def = npc:getTalentTypeFrom(tt) - local cat = tt_def.type:gsub("/.*", "") - local doit = function(npc, player) game.party:reward(_t"Select the party member to receive the reward:", function(player) - if player:knowTalentType(tt) == nil then player:setTalentTypeMastery(tt, mastery - 1 + player:getTalentTypeMastery(tt)) end - player:learnTalentType(tt, false) - player:hasQuest(npc.quest_id).reward_message = ("gained talent category %s (at mastery %0.2f)"):tformat(_t(cat):capitalize().." / "..tt_def.name:capitalize(), mastery) - end) end - answers[#answers+1] = {("[Allow training of talent category %s (at mastery %0.2f)]"):tformat(_t(cat):capitalize().." / "..tt_def.name:capitalize(), mastery), - jump="done", - action=doit, - on_select=function(npc, player) - game.tooltip_x, game.tooltip_y = 1, 1 - game:tooltipDisplayAtMap(game.w, game.h, ("#GOLD#%s / %s#LAST#\n%s"):tformat(_t(cat):capitalize(), tt_def.name:capitalize(), tt_def.description)) - end, - } - end end - end - if reward.special then - for _, data in ipairs(reward.special) do - answers[#answers+1] = {data.desc, - jump="done", - action=data.action, - on_select=function(npc, player) - game.tooltip_x, game.tooltip_y = 1, 1 - game:tooltipDisplayAtMap(game.w, game.h, data.tooltip) - end, - } - end - end - return answers -end - newChat{ id="welcome", text = reward.is_antimagic and _t[[At the last moment you invoke the power of nature. The portal fizzles and transports @npcname@ to Zigur. You can feel Nature thanking you.]] or _t[[Thank you, my friend. I do not think I would have survived without you. Please let me reward you:]], - answers = generate_rewards(), + answers = EscortRewards:rewardChatAnwsers(player, reward, "done", function(npc, player, what, k, v, log) + player:hasQuest(npc.quest_id).reward_message = log + end), } newChat{ id="done", diff --git a/game/modules/tome/data/quests/escort-duty.lua b/game/modules/tome/data/quests/escort-duty.lua index 222e9b07a34c1d267d65780f1e1af2021377b1d4..f2b452dde532c049d6dd60648e7e1b222e2fbd0e 100644 --- a/game/modules/tome/data/quests/escort-duty.lua +++ b/game/modules/tome/data/quests/escort-duty.lua @@ -17,6 +17,7 @@ -- Nicolas Casalini "DarkGod" -- darkgod@te4.org +local EscortRewards = require("mod.class.EscortRewards") local Talents = require("engine.interface.ActorTalents") local Stats = require("engine.interface.ActorStats") local NameGenerator = require("engine.NameGenerator") @@ -45,224 +46,6 @@ local name_rules = { }, } -local possible_types = { - { name="lost warrior", random="male", chance=70, - text = _t[[Please help me! I am afraid I lost myself in this place. I know there is a recall portal left around here by a friend, but I have fought too many battles, and I fear I will not make it. Would you help me?]], - actor = { - type = "humanoid", subtype = "human", image = "player/higher_male.png", - display = "@", color=colors.UMBER, - name = _t"%s, the lost warrior", - desc = _t[[He looks tired and wounded.]], - autolevel = "warrior", - ai = "escort_quest", ai_state = { talent_in=4, }, - stats = { str=18, dex=13, mag=5, con=15 }, - - body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 }, - resolvers.equip{ {type="weapon", subtype="greatsword", autoreq=true} }, - resolvers.talents{ [Talents.T_STUNNING_BLOW]=1, }, - lite = 4, - rank = 2, - exp_worth = 1, - antimagic_ok = true, - - max_life = 50, life_regen = 0, - life_rating = 12, - combat_armor = 3, combat_def = 3, - inc_damage = {all=-50}, - - reward_type = "warrior", - }, - }, - { name="injured seer", random="female", chance=70, - text = _t[[Please help me! I am afraid I lost myself in this place. I know there is a recall portal left around here by a friend, but I will not be able to continue the road alone. Would you help me?]], - actor = { - name = _t"%s, the injured seer", - type = "humanoid", subtype = "elf", female=true, image = "player/halfling_female.png", - display = "@", color=colors.LIGHT_BLUE, - desc = _t[[She looks tired and wounded.]], - autolevel = "caster", - ai = "escort_quest", ai_state = { talent_in=4, }, - stats = { str=8, dex=7, mag=18, con=12 }, - - body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 }, - resolvers.equip{ {type="weapon", subtype="staff", autoreq=true} }, - resolvers.talents{ [Talents.T_MANATHRUST]=1, }, - lite = 4, - rank = 2, - exp_worth = 1, - - max_life = 50, life_regen = 0, - life_rating = 11, - combat_armor = 3, combat_def = 3, - inc_damage = {all=-50}, - - reward_type = "divination", - }, - }, - { name="repented thief", random="male", chance=70, - text = _t[[Please help me! I am afraid I lost myself in this place. I know there is a recall portal left around here by a friend, but I have fought too many battles, and I fear I will not make it. Would you help me?]], - actor = { - name = _t"%s, the repented thief", - type = "humanoid", subtype = "halfling", image = "player/cornac_male.png", - display = "@", color=colors.BLUE, - desc = _t[[He looks tired and wounded.]], - autolevel = "rogue", - ai = "escort_quest", ai_state = { talent_in=4, }, - stats = { str=8, dex=7, mag=18, con=12 }, - - body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 }, - resolvers.equip{ {type="weapon", subtype="dagger", autoreq=true}, {type="weapon", subtype="dagger", autoreq=true} }, - resolvers.talents{ [Talents.T_DIRTY_FIGHTING]=1, }, - lite = 4, - rank = 2, - exp_worth = 1, - antimagic_ok = true, - - max_life = 50, life_regen = 0, - life_rating = 11, - combat_armor = 3, combat_def = 3, - inc_damage = {all=-50}, - - reward_type = "survival", - }, - }, - { name="lone alchemist", random="male", chance=70, - text = _t[[Please help me! I am afraid I lost myself in this place. I know there is a recall portal left around here by a friend, but I have fought too many battles, and I fear I will not make it. Would you help me?]], - actor = { - name = _t"%s, the lone alchemist", - type = "humanoid", subtype = "human", image = "player/shalore_male.png", - display = "@", color=colors.AQUAMARINE, - desc = _t[[He looks tired and wounded.]], - autolevel = "rogue", - ai = "escort_quest", ai_state = { talent_in=4, }, - stats = { str=8, dex=7, mag=18, con=12 }, - - body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 }, - resolvers.equip{ {type="weapon", subtype="staff", autoreq=true} }, - resolvers.talents{ [Talents.T_HEAT]=1, }, - lite = 4, - rank = 2, - exp_worth = 1, - - max_life = 50, life_regen = 0, - life_rating = 11, - combat_armor = 3, combat_def = 3, - inc_damage = {all=-50}, - - reward_type = "alchemy", - }, - }, - { name="lost sun paladin", random="female", chance=70, - text = _t[[Please help me! I am afraid I lost myself in this place. I know there is a recall portal left around here by a friend, but I have fought too many battles, and I fear I will not make it. Would you help me?]], - actor = { - name = _t"%s, the lost sun paladin", - type = "humanoid", subtype = "human", female=true, image = "player/higher_female.png", - display = "@", color=colors.GOLD, - desc = _t[[She looks tired and wounded.]], - autolevel = "warriormage", - ai = "escort_quest", ai_state = { talent_in=4, }, - stats = { str=18, dex=7, mag=18, con=12 }, - - body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 }, - resolvers.equip{ {type="weapon", subtype="mace", autoreq=true} }, - resolvers.talents{ [Talents.T_CHANT_OF_FORTRESS]=1, }, - lite = 4, - rank = 2, - exp_worth = 1, - - max_life = 50, life_regen = 0, - life_rating = 12, - combat_armor = 3, combat_def = 3, - inc_damage = {all=-50}, - - reward_type = "sun_paladin", - sunwall_query = true, - }, - }, - { name="lost defiler", random="female", chance=70, - text = _t[[Please help me! I am afraid I lost myself in this place. I know there is a recall portal left around here by a friend, but I have fought too many battles, and I fear I will not make it. Would you help me?]], - actor = { - name = _t"%s, the lost defiler", - type = "humanoid", subtype = "human", female=true, image = "player/higher_female.png", - display = "@", color=colors.YELLOW, - desc = _t[[She looks tired and wounded.]], - autolevel = "caster", - ai = "escort_quest", ai_state = { talent_in=4, }, - stats = { str=8, dex=7, mag=18, con=12 }, - - body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 }, - resolvers.equip{ {type="weapon", subtype="staff", autoreq=true} }, - resolvers.talents{ [Talents.T_CURSE_OF_IMPOTENCE]=1, }, - lite = 4, - rank = 2, - exp_worth = 1, - - max_life = 50, life_regen = 0, - life_rating = 11, - combat_armor = 3, combat_def = 3, - inc_damage = {all=-50}, - - reward_type = "defiler", - }, - }, - { name="temporal explorer", random="player", portal=_t"temporal portal", chance=30, - text = _t[[Oh but you are ... are you ?! ME?! -So I was right, this is not my original time-thread! -Please help me! I am afraid I lost myself in this place. I know there is a temporal portal left around here by a friend, but I have fought too many battles, and I fear I will not make it. Would you help me? Would you help .. yourself?]], - actor = { - name = _t"%s, temporal explorer", - type = "humanoid", subtype = "human", female=true, image = "player/higher_female.png", - display = "@", color=colors.YELLOW, - desc = _t[[She looks tired and wounded. She is so similar to you and yet completely different. Weird.]], - autolevel = "caster", - ai = "escort_quest", ai_state = { talent_in=4, }, - stats = { str=8, dex=7, mag=18, con=12 }, - - body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 }, - resolvers.equip{ {type="weapon", subtype="staff", autoreq=true} }, - resolvers.talents{ [Talents.T_DUST_TO_DUST]=1, }, - lite = 4, - rank = 2, - exp_worth = 1, - - max_life = 50, life_regen = 0, - life_rating = 11, - combat_armor = 3, combat_def = 3, - inc_damage = {all=-50}, - - reward_type = "temporal", - }, - }, - { name="worried loremaster", random="female", chance=30, - text = _t[[Please help me! I am afraid I lost myself in this place. I know there is a recall portal left around here by a friend, but I have fought too many battles, and I fear I will not make it. Would you help me?]], - actor = { - name = _t"%s, the worried loremaster", - type = "humanoid", subtype = "human", female=true, image = "player/thalore_female.png", - display = "@", color=colors.LIGHT_GREEN, - desc = _t[[She looks tired and wounded.]], - autolevel = "wildcaster", - ai = "escort_quest", ai_state = { talent_in=4, }, - stats = { str=8, dex=7, mag=18, con=12 }, - - body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 }, - resolvers.equip{ {type="weapon", subtype="staff", autoreq=true} }, - resolvers.talents{ [Talents.T_MIND_SEAR]=1, }, - lite = 4, - rank = 2, - exp_worth = 1, - antimagic_ok = true, - - max_life = 50, life_regen = 0, - life_rating = 10, - combat_armor = 3, combat_def = 3, - inc_damage = {all=-50}, - - reward_type = "exotic", - }, - }, -} -local possible_types_safe = table.clone(possible_types, true) - -------------------------------------------------------------------------------- -- Quest code -------------------------------------------------------------------------------- @@ -338,24 +121,8 @@ on_grant = function(self, who) self.on_grant = nil - local hd = {"Quest:escort:assign", possible_types=possible_types} - if self:triggerHook(hd) then possible_types = hd.possible_types end - - game.state.escorts_seen = game.state.escorts_seen or {} - local escorts_seen = game.state.escorts_seen - while true do - self.kind = rng.table(possible_types) - if not self.kind then - -- If some bad addon borked us, revert to base list - possible_types = possible_types_safe - else - if not self.kind.unique or not escorts_seen[self.kind.name] then - if rng.percent(self.kind.chance) then break end - end - end - end - - escorts_seen[self.kind.name] = (escorts_seen[self.kind.name] or 0) + 1 + self.kind_id, self.kind = EscortRewards:getGiver() + self.kind = self.kind.escort if self.kind.random == "player" then self.kind.actor.name = self.kind.actor.name:format(game.player.name) @@ -368,6 +135,7 @@ on_grant = function(self, who) self.kind.actor.faction = who.faction self.kind.actor.summoner = who self.kind.actor.quest_id = self.id + self.kind.actor.reward_type = self.kind_id self.kind.actor.no_inventory_access = true self.kind.actor.escort_quest = true self.kind.actor.remove_from_party_on_death = true