diff --git a/game/modules/tome/class/GameState.lua b/game/modules/tome/class/GameState.lua index 95f0e39d1bb74f27b3fca9388af346f3c35c4c7e..f40d3c7c413b429d13f7717fb82f48e65c3ca6d4 100644 --- a/game/modules/tome/class/GameState.lua +++ b/game/modules/tome/class/GameState.lua @@ -2492,7 +2492,9 @@ function _M:startEvents() if game.zone.events_by_level then lev = game.level.level if forbid[lev] then lev = nil - elseif e.level_range and (lev < (e.level_range[1] or 1) or lev > (e.level_range[2] or game.zone.max_level)) then lev = nil end + elseif e.level_range and (lev < (e.level_range[1] or 1) or lev > (e.level_range[2] or game.zone.max_level)) then lev = nil + elseif e.special and not e.special(lev) then lev = nil + end else local start, stop = 1, game.zone.max_level if e.level_range then start, stop = e.level_range[1] or start, e.level_range[2] or stop end @@ -2643,18 +2645,23 @@ function _M:infiniteDungeonChallenge(zone, lev, data, id_layout_name, id_grids_n end local challenge = rng.rarityTable(challenges) - data.id_challenge = challenge.id + data.id_challenge = challenge and challenge.id data.id_layout_name = id_layout_name data.id_grids_name = id_grids_name print("[INFINITE DUNGEON] Selected challenge", data.id_challenge) end +--- Create and grant an id-challenge quest based on I.D. level function _M:makeChallengeQuest(level, name, desc, data, alter_effect) + local qid = "id-challenge-"..level.level + local p = game:getPlayer(true) + if p:hasQuest(qid) then return end -- sanity check local q = { - id = "id-challenge-"..level.level, - name = "Infinite Dungeon Challenge: "..name.." (Level "..level.level..")", + id = qid, + name = "Infinite Dungeon Challenge (Level "..level.level.."): "..name, use_ui = "quest-idchallenge", challenge_desc = desc, + check_level = level, -- level the quest is granted for desc = function(self, who) local desc = {} desc[#desc+1] = self.challenge_desc @@ -2667,15 +2674,29 @@ function _M:makeChallengeQuest(level, name, desc, data, alter_effect) who:setQuestStatus(self.id, engine.Quest.DONE) self:check("on_challenge_success", who) game:getPlayer(true):removeEffect(who.EFF_ZONE_AURA_CHALLENGE, true, true) + self.check_level = nil elseif self:isFailed() then self:check("on_challenge_failed", who) game:getPlayer(true):removeEffect(who.EFF_ZONE_AURA_CHALLENGE, true, true) + self.check_level = nil end end, - on_exit_level = function(self, who) - self:check("on_exit_check", who) - if self.status ~= self.DONE then - who:setQuestStatus(self.id, self.FAILED) + on_exit_level = function(self, who) -- auto triggered when the "ZONE_AURA_CHALLENGE" is deactivated + if who.dead then -- death always triggers an exit check before the eidolon can resurrect the player + self:check("on_exit_check", who) + self.check_level = nil + else + -- wrap up the quest (on_exit_check) when moving to another ID level or any level allowing worldport + -- run after the level switch is complete so the destination can be determined + game:onTickEnd(function() + if self.check_level and self.check_level ~= game.level and (game.zone.infinite_dungeon or not game.zone.no_worldport) then + self:check("on_exit_check", who) + if not (self:isEnded() or self:isCompleted()) then -- force failure if not complete + who:setQuestStatus(self.id, self.FAILED) + end + self.check_level = nil + end + end) end end, on_challenge_success = function(self, who) @@ -2686,34 +2707,39 @@ function _M:makeChallengeQuest(level, name, desc, data, alter_effect) popup_text = {}, } table.merge(q, data) - local p = game:getPlayer(true) + + -- force wrapping up previous quest if needed + local eff = p:hasEffect(p.EFF_ZONE_AURA_CHALLENGE) + if eff then p:removeEffect(p.EFF_ZONE_AURA_CHALLENGE, true, true) end + p:grantQuest(q) game:onTickEnd(function() p:setEffect(p.EFF_ZONE_AURA_CHALLENGE, 1, {id_challenge_quest = q.id}) - local eff = p:hasEffect(p.EFF_ZONE_AURA_CHALLENGE) + eff = p:hasEffect(p.EFF_ZONE_AURA_CHALLENGE) if eff and alter_effect then alter_effect(p, eff) end end) return q end +--- Create a custom quest based on the id-challenge function _M:infiniteDungeonChallengeFinish(zone, level) local id_challenge = level.data.id_challenge if not id_challenge then return end - + local p = game:getPlayer(true) + if p:hasQuest("id-challenge-"..level.level) then return end -- sanity check + if id_challenge == "pacifist" then level.data.record_player_kills = 0 self:makeChallengeQuest(level, "Pacifist", "Leave the level (to the next level) without killing a single creature. You will get #{italic}#two#{normal}# rewards.", { on_exit_check = function(self, who) if not self.check_level then return end if self.check_level.data.record_player_kills == 0 then who:setQuestStatus(self.id, self.COMPLETED) end - self.check_level = nil end, forbid_rewards = {"randart", "generic_pt"}, rewards_nb = 2, on_kill_foe = function(self, who, target) who:setQuestStatus(self.id, self.FAILED) end, - check_level = level, }) elseif id_challenge == "exterminator" then local enemies_left = function(self, who) @@ -2732,7 +2758,7 @@ function _M:infiniteDungeonChallengeFinish(zone, level) end, on_grant = function(self, who) game:onTickEnd(function() - -- mark enemies when quest is awarded (to prevent summons and any newly spawned npcs from preventing completion) + -- mark enemies when quest is awarded (to prevent summons and any newly spawned npcs from preventing completion) for uid, e in pairs(self.check_level.entities) do if who:reactionToward(e) < 0 then e[self.id] = true @@ -2745,47 +2771,46 @@ function _M:infiniteDungeonChallengeFinish(zone, level) if not self.check_level then return end local nb = self:enemies_left(who) if nb == 0 then who:setQuestStatus(self.id, self.COMPLETED) end - self.check_level = nil end, on_kill_foe = function(self, who, target) if not self.check_level then return end local nb = self:enemies_left(who) if nb == 0 then who:setQuestStatus(self.id, self.COMPLETED) end end, - check_level = level, }) elseif id_challenge == "fast-exit" then local a = require("engine.Astar").new(level.map, game:getPlayer(true)) local path = a:calc(level.default_up.x, level.default_up.y, level.default_down.x, level.default_down.y) if path and #path > 5 then - local turns = #path * 3 - self:makeChallengeQuest(level, "Rush Hour ("..turns..")", "Leave the level in less than "..turns.." turns (exit is revealed on your map).", { + local turns = #path * 3 + 20 + self:makeChallengeQuest(level, "Rush Hour ("..turns..")", "Proceed directly to the next Infinite Dungeon level in less than "..turns.." turns (an exit is revealed on your map).", { turns_left = turns, dynamic_desc = function(self, desc) desc[#desc+1] = "Turns left: #LIGHT_GREEN#"..self.turns_left end, on_exit_check = function(self, who) - if self.turns_left >= 0 then who:setQuestStatus(self.id, self.COMPLETED) end + if self.turns_left >= 0 then + if self.check_level then self.check_level.turn_counter = nil end + who:setQuestStatus(self.id, self.COMPLETED) + end end, on_act_base = function(self, who) - if game.level.turn_counter then - game.level.turn_counter = game.level.turn_counter - 10 - game.player.changed = true - if game.level.turn_counter < 0 then - game.level.turn_counter = nil - end - end - self.turns_left = self.turns_left - 1 if self.turns_left < 0 then + if self.check_level then self.check_level.turn_counter = nil end who:setQuestStatus(self.id, self.FAILED) + elseif self.check_level then + self.check_level.turn_counter = self.turns_left * 10 + if self.check_level ~= game.level then + game.log("\n#ORCHID# Rush Hour: %s turns left!\n", self.turns_left) + end end end, }) self:locationRevealAround(level.default_down.x, level.default_down.y) level.turn_counter = turns * 10 level.max_turn_counter = turns * 10 - level.turn_counter_desc = "Find the exit! It is marked on your map." + level.turn_counter_desc = "Proceed to the next Infinite Dungeon level! An exit has been marked on your map." end elseif id_challenge == "dream-horror" then local m = zone:makeEntity(level, "actor", {name="dreaming horror", random_boss=true}, nil, true) @@ -2797,7 +2822,12 @@ function _M:infiniteDungeonChallengeFinish(zone, level) tries = tries + 1 end if tries < 100 then - local q = self:makeChallengeQuest(level, "Dream Hunter", "Wake up and kill the dreaming horror boss '"..m.name.."'.", {}) + local q = self:makeChallengeQuest(level, "Dream Hunter", "Wake up and kill the dreaming horror boss '"..m.name.."'.", { + on_exit_check = function(self, who) + if not self:isEnded() then who:setQuestStatus(self.id, self.FAILED) end + end + }) + m.id_challenge_quest = q.id m.on_die = function(self, who) who:setQuestStatus(self.id_challenge_quest, engine.Quest.COMPLETED) @@ -2885,8 +2915,8 @@ function _M:infiniteDungeonChallengeFinish(zone, level) end end elseif id_challenge == "near-sighted" then - Dialog:yesnoPopup("Challenge: #PURPLE#Near Sighted", "Finish the level with -7 sight range for a reward.", function(r) if not r then - self:makeChallengeQuest(level, "Near Sighted", "Finish the level with -7 sight range.", { + Dialog:yesnoPopup("Challenge: #PURPLE#Near Sighted", "Proceed to the next Infinite Dungeon level with -7 sight range for a reward.", function(r) if not r then + self:makeChallengeQuest(level, "Near Sighted", "Proceed to the next Infinite Dungeon level with -7 sight range.", { on_exit_check = function(self, who) who:setQuestStatus(self.id, self.COMPLETED) end, }, function(actor, eff) actor:effectTemporaryValue(eff, "sight", -7) @@ -2906,9 +2936,17 @@ function _M:infiniteDungeonChallengeFinish(zone, level) if self.turns_left <= 0 and not who.dead then who:setQuestStatus(self.id, self.COMPLETED) else who:setQuestStatus(self.id, self.FAILED) end end, on_act_base = function(self, who) - self.turns_left = self.turns_left - 1 + if self.check_level == game.level then self.turns_left = self.turns_left - 1 end if self.turns_left == 0 then game.bignews:say(60, "#LIGHT_GREEN#Multiplicity: You have survived so far. Exit for your reward!") + if game.level.turn_counter then game.level.turn_counter = nil end + end + if game.level.turn_counter then + game.level.turn_counter = game.level.turn_counter - 10 + game.player.changed = true + if game.level.turn_counter < 0 then + game.level.turn_counter = nil + end end end, }) @@ -2921,6 +2959,9 @@ function _M:infiniteDungeonChallengeFinish(zone, level) end end end) + level.turn_counter = turns * 10 + level.max_turn_counter = turns * 10 + level.turn_counter_desc = "Survive the multiplicative madness!." end end, "Refuse", "Accept", true) elseif id_challenge == "headhunter" then local mlist = {} -- add random elite "spawns of Urh'Rok" @@ -3004,13 +3045,17 @@ function _M:infiniteDungeonChallengeFinish(zone, level) self.id_challenge.quests[id_challenge] = (self.id_challenge.quests[id_challenge] or 0) + 1 end +--- Grant (random) awards for an id-challenge quest function _M:infiniteDungeonChallengeReward(quest, who) local rewards = { {name = "Random Artifact", id="randart", rarity=1, give=function(who) local tries = 100 + -- make sure randart is compatible with recipient + local random_filter = {properties={"randart_able"}, special=function(e) return self:checkPowers(who, e) end, + random_object={egos=rng.range(2,3), nb_powers_add=rng.range(10,30), forbid_power_source=self:attrPowers(who)}} while tries > 0 do - local o = game.zone:makeEntity(game.level, "object", {random_object={egos=rng.range(2,3), nb_powers_add=rng.range(10,30)}, properties={"randart_able"}}, nil, true) + local o = game.zone:makeEntity(game.level, "object", random_filter, nil, true) if o then if o.__transmo == nil and who:attr("has_transmo") then o.__transmo = true end o:identify(true) @@ -3018,6 +3063,7 @@ function _M:infiniteDungeonChallengeReward(quest, who) who:sortInven() return "Random Artifact: "..o:getName{do_color=true} end + tries = tries - 1 end -- Fallback who.unused_stats = who.unused_stats + 3 diff --git a/game/modules/tome/data/timed_effects/other.lua b/game/modules/tome/data/timed_effects/other.lua index 08f005d20d483c277223ae662e4e3529694063fc..96262ada3915e34a713e3dd75716cedd832c40ec 100644 --- a/game/modules/tome/data/timed_effects/other.lua +++ b/game/modules/tome/data/timed_effects/other.lua @@ -3036,7 +3036,7 @@ newEffect{ type = "other", subtype = { aura=true }, status = "neutral", - zone_wide_effect = true, + zone_wide_effect = false, parameters = {}, activate = function(self, eff) end, @@ -3052,6 +3052,10 @@ newEffect{ if not eff.id_challenge_quest or not self:hasQuest(eff.id_challenge_quest) then return end self:hasQuest(eff.id_challenge_quest):check("on_act_base", self) end, + callbackOnChangeLevel = function(self, eff) + local q = eff.id_challenge_quest and self:hasQuest(eff.id_challenge_quest) + if q then q:check("on_exit_level", self) end + end, } newEffect{