Skip to content
Snippets Groups Projects
DeathDialog.lua 12.8 KiB
Newer Older
dg's avatar
dg committed
-- ToME - Tales of Maj'Eyal
DarkGod's avatar
DarkGod committed
-- Copyright (C) 2009 - 2019 Nicolas Casalini
dg's avatar
dg committed
--
-- 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

dg's avatar
dg committed
require "engine.class"
local Shader = require "engine.Shader"
dg's avatar
dg committed
local Dialog = require "engine.ui.Dialog"
local Textzone = require "engine.ui.Textzone"
local Separator = require "engine.ui.Separator"
local List = require "engine.ui.List"
dg's avatar
dg committed
local Savefile = require "engine.Savefile"
local Map = require "engine.Map"

dg's avatar
dg committed
module(..., package.seeall, class.inherit(Dialog))
dg's avatar
dg committed

function _M:init(actor)
	self.actor = actor
	self.ui = "deathbox"
	Dialog.init(self, _t"You have #LIGHT_RED#died#LAST#!", 500, 600)
dg's avatar
dg committed

dg's avatar
dg committed
	actor:saveUUID()

dg's avatar
dg committed
	self:generateList()
dg's avatar
dg committed
	if self.dont_show then return end
	if not config.settings.cheat then game:onTickEnd(function() game:saveGame() end) end
dg's avatar
dg committed

	self:setTitleShadowShader(Shader.default.textoutline and Shader.default.textoutline.shad, 1.5)

	self.c_list = List.new{width=self.iw, nb_items=#self.list, list=self.list, fct=function(item) self:use(item) end, select=function(item) self.cur_item = item end}

	self.graphical_options = {
		blood_life = {
			available   = self:getUITexture("ui/active_blood_life.png"),
			unavailable = self:getUITexture("ui/inactive_blood_life.png"),
		},
		consume = {
			available   = self:getUITexture("ui/active_consume.png"),
			unavailable = self:getUITexture("ui/inactive_consume.png"),
		},
		skeleton = {
			available   = self:getUITexture("ui/active_skeleton.png"),
			unavailable = self:getUITexture("ui/inactive_skeleton.png"),
		},
	}

dg's avatar
dg committed

	if self.c_achv then
		self:loadUI{
			{left=0, top=0, ui=self.c_desc},
			{left=0, top=self.c_desc.h, ui=self.c_achv},
DarkGod's avatar
DarkGod committed
			{left=5, top=self.c_desc.h+self.c_achv.h, padding_h=10, ui=Separator.new{ui="deathbox", dir="vertical", size=self.iw - 10}},
			{left=0, bottom=0, ui=self.c_list},
		}
	else
		self:loadUI{
			{left=0, top=0, ui=self.c_desc},
DarkGod's avatar
DarkGod committed
			{left=5, top=self.c_desc.h, padding_h=10, ui=Separator.new{ui="deathbox", dir="vertical", size=self.iw - 10}},
			{left=0, bottom=0, ui=self.c_list},
		}
	end
dg's avatar
dg committed
	self:setFocus(self.c_list)
	self:setupUI(false, true)
dg's avatar
dg committed
end

function _M:setupDescription()
	self.c_desc = Textzone.new{width=self.iw, auto_height=true, text=_t[[Death in #{bold}#Tales of Maj'Eyal#{normal}# is usually permanent, but if you have a means of resurrection it will be proposed in the menu below.
You can dump your character data to a file to remember her/him forever, or you can exit and try once again to survive in the wilds!
]]}
	self.c_desc:setTextShadow(1)
	self.c_desc:setShadowShader(Shader.default.textoutline and Shader.default.textoutline.shad, 1.2)

	if #game.party.on_death_show_achieved > 0 then
		self.c_achv = Textzone.new{width=self.iw, scrollbar=true, height=100, text=("#LIGHT_GREEN#During your game you#WHITE#:\n* %s"):tformat(table.concat(game.party.on_death_show_achieved, "\n* "))}
	end
end

dg's avatar
dg committed
--- Clean the actor from debuffs/buffs
dg's avatar
dg committed
	local effs = {}
	-- Remove chronoworlds
	if game._chronoworlds then game._chronoworlds = nil end
dg's avatar
dg committed
	-- Go through all spell effects
	for eff_id, p in pairs(actor.tmp) do
		local e = actor.tempeffect_def[eff_id]
dg's avatar
dg committed
		effs[#effs+1] = {"effect", eff_id}
	end

	-- Go through all sustained spells
	for tid, act in pairs(actor.sustain_talents) do
dg's avatar
dg committed
		if act then
			effs[#effs+1] = {"talent", tid}
		end
	end

	while #effs > 0 do
		local eff = rng.tableRemove(effs)

		if eff[1] == "effect" then
			actor:removeEffect(eff[2], false, true)
dg's avatar
dg committed
		else
			actor:forceUseTalent(eff[2], {ignore_energy=true, no_equilibrium_fail=true, no_paradox_fail=true, save_cleanup=true})
dg's avatar
dg committed
		end
	end
end

dg's avatar
dg committed
--- Restore resources
dg's avatar
dg committed
function _M:restoreResources(actor)
	if actor.resetToFull then
		actor:resetToFull()
		actor.energy.value = game.energy_to_act
	end
dg's avatar
dg committed
end

dg's avatar
dg committed
--- Basic resurrection
DarkGod's avatar
DarkGod committed
function _M:resurrectBasic(actor, reason)
	actor.dead = false
	actor.died = (actor.died or 0) + 1
	-- Find the position of the last dead
	local last = game.party:findLastDeath()

	local x, y = util.findFreeGrid(last.x, last.y, 20, true, {[Map.ACTOR]=true})
	if not x then x, y = last.x, last.y end
	
	-- invulnerable while moving so we don't get killed twice
	local old_invuln = actor.invulnerable
	actor.invulnerable = 1
	actor.x, actor.y = nil, nil
dg's avatar
dg committed
	game:unregisterDialog(self)
	game.level.map:redisplay()
	actor.energy.value = game.energy_to_act
dg's avatar
dg committed

	-- apply cursed equipment
	if actor.hasTalent and actor.hasTalent(actor.T_DEFILING_TOUCH) then
dg's avatar
dg committed
		local t = actor:getTalentFromId(actor.T_DEFILING_TOUCH)
		t.updateCurses(actor, t, true)
dg's avatar
dg committed

DarkGod's avatar
DarkGod committed
	actor:checkTwoHandedPenalty()
DarkGod's avatar
DarkGod committed

	actor:fireTalentCheck("callbackOnResurrect", reason or "unknown")
end

--- Send the party to the Eidolon Plane
function _M:eidolonPlane()
--	self.actor:setEffect(self.actor.EFF_EIDOLON_PROTECT, 1, {})
dg's avatar
dg committed
		if not self.actor:attr("infinite_lifes") then
			self.actor:attr("easy_mode_lifes", -1)
Otowa Kotori's avatar
Otowa Kotori committed
			local nb = self.actor:attr("easy_mode_lifes") and self.actor:attr("easy_mode_lifes") or 0
			local style
			if(nb > 0) then style = ("#LIGHT_RED#You have %d life(s) left."):tformat(nb)
			else style = ("#LIGHT_RED#You have no more lives left."):tformat() end
			game.log(style)
dg's avatar
dg committed
		end
		local is_exploration = game.permadeath == game.PERMADEATH_INFINITE
		self:cleanActor(self.actor)
DarkGod's avatar
DarkGod committed
		self:resurrectBasic(self.actor, "eidolon_plane")
DarkGod's avatar
DarkGod committed
		for e, _ in pairs(game.party.members) do if e ~= self then
DarkGod's avatar
DarkGod committed
		end end
		for uid, e in pairs(game.level.entities) do
			if not is_exploration or game.party:hasMember(e) then
				self:restoreResources(e)
			end
		game.party:goToEidolon(self.actor)
dg's avatar
dg committed
		game.log("#LIGHT_RED#From the brink of death you seem to be yanked to another plane.")
		if not config.settings.cheat then game:onTickEnd(function() game:saveGame() end) end

		self.actor:checkTwoHandedPenalty()
dg's avatar
dg committed
end

dg's avatar
dg committed
function _M:use(item)
	if not item then return end
	local act = item.action
dg's avatar
dg committed

	if type(act) == "function" then
		act()
	elseif act == "exit" then
		if item.subaction == "none" then
			util.showMainMenu()
		elseif item.subaction == "restart" then
			local addons = {}
			for add, _ in pairs(game.__mod_info.addons) do addons[#addons+1] = "'"..add.."'" end
			util.showMainMenu(false, engine.version[4], engine.version[1].."."..engine.version[2].."."..engine.version[3], game.__mod_info.short_name, game.save_name, true, ("auto_quickbirth=%q set_addons={%s}"):format(game:getPlayer(true).name, table.concat(addons, ", ")))
		elseif item.subaction == "restart-new" then
			util.showMainMenu(false, engine.version[4], engine.version[1].."."..engine.version[2].."."..engine.version[3], game.__mod_info.short_name, game.save_name, true)
dg's avatar
dg committed
	elseif act == "dump" then
		game:registerDialog(require("mod.dialogs.CharacterSheet").new(self.actor))
Otowa Kotori's avatar
Otowa Kotori committed
		game:registerDialog(require("mod.dialogs.ShowChatLog").new(_t"Message Log", 0.6, game.uiset.logdisplay, profile.chat))
dg's avatar
dg committed
	elseif act == "cheat" then
dg's avatar
dg committed
		game.logPlayer(self.actor, "#LIGHT_BLUE#You resurrect! CHEATER!")
dg's avatar
dg committed

DarkGod's avatar
DarkGod committed
		self:resurrectBasic(self.actor, "cheat")
dg's avatar
dg committed
		self:restoreResources(self.actor)
dg's avatar
dg committed
		self.actor:check("on_resurrect", "cheat")
DarkGod's avatar
DarkGod committed
		self.actor:triggerHook{"Actor:resurrect", reason="cheat"}
dg's avatar
dg committed
	elseif act == "blood_life" then
dg's avatar
dg committed
		self.actor.blood_life = false
dg's avatar
dg committed
		game.logPlayer(self.actor, "#LIGHT_RED#The Blood of Life rushes through your dead body. You come back to life!")

DarkGod's avatar
DarkGod committed
		self:resurrectBasic(self.actor, "blood_life")
dg's avatar
dg committed
		self:restoreResources(self.actor)
		world:gainAchievement("UNSTOPPABLE", actor)
dg's avatar
dg committed
		self.actor:check("on_resurrect", "blood_life")
DarkGod's avatar
DarkGod committed
		self.actor:triggerHook{"Actor:resurrect", reason="blood_life"}
Eric Wykoff's avatar
Eric Wykoff committed
	elseif act == "threads" then
		game:chronoRestore("see_threads_base", true)
		game:onTickEnd(function()
			game._chronoworlds = nil
			game.player:removeEffect(game.player.EFF_SEE_THREADS)end
		)
Eric Wykoff's avatar
Eric Wykoff committed
		game:saveGame()
dg's avatar
dg committed
	elseif act == "skeleton" then
		self.actor:attr("re-assembled", 1)
dg's avatar
dg committed
		game.logPlayer(self.actor, "#YELLOW#Your bones magically knit back together. You are once more able to dish out pain to your foes!")
dg's avatar
dg committed

DarkGod's avatar
DarkGod committed
		self:resurrectBasic(self.actor, "skeleton")
dg's avatar
dg committed
		self:restoreResources(self.actor)
		world:gainAchievement("UNSTOPPABLE", actor)
dg's avatar
dg committed
		self.actor:check("on_resurrect", "skeleton")
DarkGod's avatar
DarkGod committed
		self.actor:triggerHook{"Actor:resurrect", reason="skeleton"}
dg's avatar
dg committed
	elseif act:find("^consume") then
dg's avatar
dg committed
		local inven, item, o = item.inven, item.item, item.object
dg's avatar
dg committed
		self.actor:removeObject(inven, item)
dg's avatar
dg committed
		game.logPlayer(self.actor, "#YELLOW#Your %s is consumed and disappears! You come back to life!", o:getName{do_colour=true})
dg's avatar
dg committed

		self:cleanActor(self.actor)
DarkGod's avatar
DarkGod committed
		self:resurrectBasic(self.actor, "consume", o)
dg's avatar
dg committed
		self:restoreResources(self.actor)
		world:gainAchievement("UNSTOPPABLE", actor)
dg's avatar
dg committed
		self.actor:check("on_resurrect", "consume", o)
		o:check("on_resurrect", self.actor)
DarkGod's avatar
DarkGod committed
		self.actor:triggerHook{"Actor:resurrect", reason="consume", object=o}
dg's avatar
dg committed
	end
end

function _M:generateList()
	local list = {}
dg's avatar
dg committed

	-- Pause the game
	game:onTickEnd(function()
		game.paused = true
		game.player.energy.value = game.energy_to_act
	end)

	if game.zone.is_eidolon_plane then
		game.logPlayer(self, "You managed to die on the eidolon plane! DIE!")
		game:onTickEnd(function() world:gainAchievement("EIDOLON_DEATH", self.actor) end)
		allow_res = false
	end
dg's avatar
dg committed

Otowa Kotori's avatar
Otowa Kotori committed
	if config.settings.cheat then list[#list+1] = {name=_t"Resurrect by cheating", action="cheat"} end
	if not self.actor.no_resurrect and allow_res then
Eric Wykoff's avatar
Eric Wykoff committed
		if self.actor:hasEffect(self.actor.EFF_SEE_THREADS) and game._chronoworlds then
			self:use{action="threads"}
			self.dont_show =true
			return
		end
		
		if self.actor:fireTalentCheck("callbackOnDeathbox", self, list) then return end

dg's avatar
dg committed
		if self.actor:attr("easy_mode_lifes") or self.actor:attr("infinite_lifes") then
			self:use{action="easy_mode"}
			self.dont_show = true
			return
		end
Otowa Kotori's avatar
Otowa Kotori committed
		if self.actor:attr("blood_life") and not self.actor:attr("undead") then list[#list+1] = {name=_t"Resurrect with the Blood of Life", action="blood_life"} end
		if self.actor:getTalentLevelRaw(self.actor.T_SKELETON_REASSEMBLE) >= 5 and not self.actor:attr("re-assembled") then list[#list+1] = {name=_t"Re-assemble your bones and resurrect (Skeleton ability)", action="skeleton"} end

		local consumenb = 1
		self.actor:inventoryApplyAll(function(inven, item, o)
			if o.one_shot_life_saving and (not o.slot or inven.worn) then
Otowa Kotori's avatar
Otowa Kotori committed
				list[#list+1] = {name=("Resurrect by consuming %s"):tformat(o:getName{do_colour=true}), action="consume"..consumenb, inven=inven, item=item, object=o, is_consume=true}
				consumenb = consumenb + 1
				self.possible_items.consume = true
dg's avatar
dg committed

Otowa Kotori's avatar
Otowa Kotori committed
	list[#list+1] = {name=(not profile.auth and _t"Message Log" or _t"Message/Chat log (allows to talk)"), action="log"}
	list[#list+1] = {name=_t"Character dump", action="dump"}
	list[#list+1] = {name=_t"Restart the same character", action="exit", subaction="restart"}
	list[#list+1] = {name=_t"Restart with a new character", action="exit", subaction="restart-new"}
	list[#list+1] = {name=_t"Exit to main menu", action="exit", subaction="none"}
dg's avatar
dg committed

	self.list = list
	for _, item in ipairs(list) do self.possible_items[item.action] = true end
end

function _M:innerDisplayBack(x, y, nb_keyframes, tx, ty)
	x = x + self.frame.ox1
	y = y + self.frame.oy1

	if self.possible_items.blood_life then
		local d = self.graphical_options.blood_life[self.cur_item and self.cur_item.action == "blood_life" and "available" or "unavailable"]
		d.t:toScreenFull(x + self.frame.w - d.w, y, d.w, d.h, d.tw, d.th, 1, 1, 1, 1)
	end
	if self.possible_items.consume then
		local d = self.graphical_options.consume[self.cur_item and self.cur_item.is_consume and "available" or "unavailable"]
		d.t:toScreenFull(x, y, d.w, d.h, d.tw, d.th, 1, 1, 1, 1)
	end
	if self.possible_items.skeleton then
		local d = self.graphical_options.skeleton[self.cur_item and (self.cur_item.action == "skeleton" or self.cur_item.action == "lichform") and "available" or "unavailable"]
		d.t:toScreenFull(x, y + self.frame.h - d.h, d.w, d.h, d.tw, d.th, 1, 1, 1, 1)
	end
dg's avatar
dg committed
end