From fb252e18210757f5262e0c27be4ce0ddeba9551f Mon Sep 17 00:00:00 2001 From: DarkGod <darkgod@net-core.org> Date: Fri, 5 Jun 2020 15:35:03 +0200 Subject: [PATCH] Updated the way escorts are coded to make them easier to mod. To avoid stuff exploding too much in addons, the old hooks are disabled and now two new ones took their place "EscortRewards:givers" and "EscortRewards:rewards" --- game/modules/tome/class/EscortRewards.lua | 587 ++++++++++++++++++ game/modules/tome/class/Player.lua | 8 +- game/modules/tome/data/chats/escort-quest.lua | 300 +-------- game/modules/tome/data/quests/escort-duty.lua | 240 +------ 4 files changed, 602 insertions(+), 533 deletions(-) create mode 100644 game/modules/tome/class/EscortRewards.lua diff --git a/game/modules/tome/class/EscortRewards.lua b/game/modules/tome/class/EscortRewards.lua new file mode 100644 index 0000000000..84d9c95379 --- /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 1ba1372301..6a954f6908 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 88e4b37652..e7924ad42c 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 222e9b07a3..f2b452dde5 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 -- GitLab