From 30f12a5d8a96696a1e79a644d7ab3c4a312f53f7 Mon Sep 17 00:00:00 2001
From: dg <dg@51575b47-30f0-44d4-a5cc-537603b46e54>
Date: Sat, 3 Apr 2010 19:53:27 +0000
Subject: [PATCH] achievements!

git-svn-id: http://svn.net-core.org/repos/t-engine4@492 51575b47-30f0-44d4-a5cc-537603b46e54
---
 game/engine/Dialog.lua                      |  3 +-
 game/engine/dialogs/GameMenu.lua            |  5 ++
 game/engine/dialogs/ShowAchievements.lua    | 77 ++++++++++++++++++++
 game/engine/interface/WorldAchievements.lua | 80 +++++++++++++++++++++
 game/modules/tome/class/Actor.lua           | 11 +++
 game/modules/tome/class/Game.lua            |  1 +
 game/modules/tome/class/Player.lua          |  6 ++
 game/modules/tome/class/World.lua           | 10 ++-
 game/modules/tome/data/achievements/all.lua | 41 +++++++++++
 game/modules/tome/load.lua                  |  4 ++
 10 files changed, 236 insertions(+), 2 deletions(-)
 create mode 100644 game/engine/dialogs/ShowAchievements.lua
 create mode 100644 game/engine/interface/WorldAchievements.lua
 create mode 100644 game/modules/tome/data/achievements/all.lua

diff --git a/game/engine/Dialog.lua b/game/engine/Dialog.lua
index 49ef8d762d..b2b0b9af49 100644
--- a/game/engine/Dialog.lua
+++ b/game/engine/Dialog.lua
@@ -30,7 +30,8 @@ tiles = engine.Tiles.new(16, 16)
 function _M:simplePopup(title, text, fct, no_leave)
 	local font = core.display.newFont("/data/font/Vera.ttf", 12)
 	local w, h = font:size(text)
-	local d = new(title, w + 8, h + 25, nil, nil, nil, font)
+	local tw, th = font:size(title)
+	local d = new(title, math.max(w, tw) + 8, h + 25, nil, nil, nil, font)
 	if no_leave then
 		d:keyCommands{}
 	else
diff --git a/game/engine/dialogs/GameMenu.lua b/game/engine/dialogs/GameMenu.lua
index 51146eae69..fb08e1d3aa 100644
--- a/game/engine/dialogs/GameMenu.lua
+++ b/game/engine/dialogs/GameMenu.lua
@@ -71,6 +71,11 @@ function _M:generateList(actions)
 			local menu = require("engine.dialogs.DisplayResolution").new()
 			game:registerDialog(menu)
 		end },
+		achievements = { "Show Achievements", function()
+			game:unregisterDialog(self)
+			local menu = require("engine.dialogs.ShowAchievements").new()
+			game:registerDialog(menu)
+		end },
 		save = { "Save Game", function() game:saveGame() end },
 		quit = { "Save and Exit", function() game:onQuit() end },
 	}
diff --git a/game/engine/dialogs/ShowAchievements.lua b/game/engine/dialogs/ShowAchievements.lua
new file mode 100644
index 0000000000..d7892d791e
--- /dev/null
+++ b/game/engine/dialogs/ShowAchievements.lua
@@ -0,0 +1,77 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009, 2010 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"
+require "engine.Dialog"
+
+module(..., package.seeall, class.inherit(engine.Dialog))
+
+function _M:init(title)
+	engine.Dialog.init(self, title or "Achievements", game.w * 0.8, game.h * 0.8, nil, nil, nil, core.display.newFont("/data/font/VeraMono.ttf", 12))
+
+	self:generateList()
+
+	self.sel = 1
+	self.scroll = 1
+	self.max = math.floor((self.ih - 5) / self.font_h) - 1
+
+	self:keyCommands({},{
+		MOVE_UP = function() self.sel = util.boundWrap(self.sel - 1, 1, #self.list) self.scroll = util.scroll(self.sel, self.scroll, self.max) end,
+		MOVE_DOWN = function() self.sel = util.boundWrap(self.sel + 1, 1, #self.list) self.scroll = util.scroll(self.sel, self.scroll, self.max) end,
+		EXIT = function() game:unregisterDialog(self) end,
+	})
+	self:mouseZones{
+		{ x=2, y=5, w=350, h=self.font_h*self.max, fct=function(button, x, y, xrel, yrel, tx, ty)
+			self.sel = util.bound(self.scroll + math.floor(ty / self.font_h), 1, #self.list)
+		end },
+	}
+end
+
+function _M:generateList()
+	-- Makes up the list
+	local list = {}
+	local i = 0
+	for id, data in pairs(world.achieved) do
+		local a = world:getAchievementFromId(id)
+		list[#list+1] = { name=a.name,  desc=a.desc, when=data.when, who=data.who, order=a.order }
+		i = i + 1
+	end
+	table.sort(list, function(a, b) return a.order < b.order end)
+	self.list = list
+end
+
+function _M:drawDialog(s)
+	-- Description part
+	self:drawHBorder(s, self.iw / 2, 2, self.ih - 4)
+
+	local h = 2
+	if self.list[self.sel] then
+		local str = ("#GOLD#Achieved on:#LAST# %s\n#GOLD#Achieved by:#LAST# %s\n\n#GOLD#Description:#LAST# %s"):format(self.list[self.sel].when, self.list[self.sel].who, self.list[self.sel].desc)
+		lines = str:splitLines(self.iw / 2 - 10, self.font)
+	else
+		lines = {}
+	end
+	self:drawWBorder(s, self.iw / 2 + self.iw / 6, h - 0.5 * self.font:lineSkip(), self.iw / 6)
+	for i = 1, #lines do
+		s:drawColorString(self.font, lines[i], self.iw / 2 + 5, 2 + h)
+		h = h + self.font:lineSkip()
+	end
+
+	self:drawSelectionList(s, 2, 5, self.font_h, self.list, self.sel, "name", self.scroll, self.max)
+end
diff --git a/game/engine/interface/WorldAchievements.lua b/game/engine/interface/WorldAchievements.lua
new file mode 100644
index 0000000000..a43f052736
--- /dev/null
+++ b/game/engine/interface/WorldAchievements.lua
@@ -0,0 +1,80 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009, 2010 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 Dialog = require "engine.Dialog"
+
+--- Handles archievements in a world
+module(..., package.seeall, class.make)
+
+_M.achiev_defs = {}
+
+--- Loads achievements
+function _M:loadDefinition(dir)
+	for i, file in ipairs(fs.list(dir)) do
+		if file:find("%.lua$") then
+			local f, err = loadfile(dir.."/"..file)
+			if not f and err then error(err) end
+			setfenv(f, setmetatable({
+				newAchievement = function(t) self:newAchievement(t) end,
+			}, {__index=_G}))
+			f()
+		end
+	end
+end
+
+--- Make a new achivement with a name and desc
+function _M:newAchievement(t)
+	assert(t.name, "no achivement name")
+	assert(t.desc, "no achivement desc")
+
+	t.id = t.id or t.name
+	t.id = t.id:upper():gsub("[ ]", "_")
+	t.order = #self.achiev_defs+1
+
+	self.achiev_defs[t.id] = t
+	self.achiev_defs[#self.achiev_defs+1] = t
+	print("[ACHIEVEMENT] defined", t.order, t.id)
+end
+
+function _M:init()
+	self.achieved = {}
+end
+
+function _M:getAchievementFromId(id)
+	return self.achiev_defs[id]
+end
+
+--- Gain an achievement
+-- @param id the achivement to gain
+-- @param src who did it
+function _M:gainAchievement(id, src)
+	if not self.achiev_defs[id] then error("Unknown achievement "..id) return end
+	if self.achieved[id] then return end
+	self.achieved[id] = {turn=game.turn, who=self:achievementWho(src), when=os.date("%Y-%m-%d %H:%M:%S")}
+	game.log("#LIGHT_GREEN#New Achievement: %s!", self.achiev_defs[id].name)
+	Dialog:simplePopup("New Achievement: #LIGHT_GREEN#"..self.achiev_defs[id].name, self.achiev_defs[id].desc)
+end
+
+--- Format an achievement source
+-- By default just uses the actor's name, you can overload it to do more
+-- @param src the actor who did it
+function _M:achievementWho(src)
+	return src.name
+end
diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index 9586c3dfac..574fda17ed 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -320,6 +320,12 @@ function _M:onTakeHit(value, src)
 			value = 0
 		end
 	end
+
+	-- Achievements
+	if src and src:resolveSource().player and value >= 600 then
+		world:gainAchievement("SIZE_MATTERS", src:resolveSource())
+	end
+
 	return value
 end
 
@@ -356,6 +362,11 @@ function _M:die(src)
 		src:incStamina(src:getTalentLevel(src.T_UNENDING_FRENZY) * 2)
 	end
 
+	-- Achievements
+	if src and src:resolveSource().player and src:resolveSource().life == 1 then
+		world:gainAchievement("THAT_WAS_CLOSE", src:resolveSource())
+	end
+
 	return true
 end
 
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index d0b0157ec1..8cefd634bb 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -588,6 +588,7 @@ function _M:setupCommands()
 		EXIT = function()
 			local menu menu = require("engine.dialogs.GameMenu").new{
 				"resume",
+				"achievements",
 				"keybinds",
 				{"Graphic Mode", function() game:unregisterDialog(menu) game:registerDialog(require("mod.dialogs.GraphicMode").new()) end},
 				"resolution",
diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua
index 9d66cbd9ba..06a39f0e27 100644
--- a/game/modules/tome/class/Player.lua
+++ b/game/modules/tome/class/Player.lua
@@ -169,6 +169,12 @@ function _M:levelup()
 	if self.unused_stats > 0 then game.log("You have %d stat point(s) to spend. Press G to use them.", self.unused_stats) end
 	if self.unused_talents > 0 then game.log("You have %d talent point(s) to spend. Press G to use them.", self.unused_talents) end
 	if self.unused_talents_types > 0 then game.log("You have %d talent category point(s) to spend. Press G to use them.", self.unused_talents_types) end
+
+	if self.level == 10 then world:gainAchievement("LEVEL_10", self) end
+	if self.level == 20 then world:gainAchievement("LEVEL_20", self) end
+	if self.level == 30 then world:gainAchievement("LEVEL_30", self) end
+	if self.level == 40 then world:gainAchievement("LEVEL_40", self) end
+	if self.level == 50 then world:gainAchievement("LEVEL_50", self) end
 end
 
 --- Tries to get a target from the user
diff --git a/game/modules/tome/class/World.lua b/game/modules/tome/class/World.lua
index b50e4c70e4..a2d4addabe 100644
--- a/game/modules/tome/class/World.lua
+++ b/game/modules/tome/class/World.lua
@@ -19,12 +19,14 @@
 
 require "engine.class"
 require "engine.World"
+require "engine.interface.WorldAchievements"
 local Savefile = require "engine.Savefile"
 
-module(..., package.seeall, class.inherit(engine.World))
+module(..., package.seeall, class.inherit(engine.World, engine.interface.WorldAchievements))
 
 function _M:init()
 	engine.World.init(self)
+	engine.interface.WorldAchievements.init(self)
 end
 
 --- Requests the world to save
@@ -34,3 +36,9 @@ function _M:saveWorld()
 	save:close()
 	game.log("Saved world.")
 end
+
+--- Format an achievement source
+-- @param src the actor who did it
+function _M:achievementWho(src)
+	return src.name.." the "..game.player.descriptor.subrace.." "..game.player.descriptor.subclass
+end
diff --git a/game/modules/tome/data/achievements/all.lua b/game/modules/tome/data/achievements/all.lua
new file mode 100644
index 0000000000..a99ef2acca
--- /dev/null
+++ b/game/modules/tome/data/achievements/all.lua
@@ -0,0 +1,41 @@
+-------------- Levels
+newAchievement{
+	name = "Level 10",
+	desc = [[Got a character to level 10.]],
+}
+newAchievement{
+	name = "Level 20",
+	desc = [[Got a character to level 20.]],
+}
+newAchievement{
+	name = "Level 30",
+	desc = [[Got a character to level 30.]],
+}
+newAchievement{
+	name = "Level 40",
+	desc = [[Got a character to level 40.]],
+}
+newAchievement{
+	name = "Level 50",
+	desc = [[Got a character to level 50.]],
+}
+
+--------------- Main objectives
+newAchievement{
+	name = "Vampire crusher",
+	desc = [[Destroyed the Master in its lair of Tol Falas.]],
+}
+newAchievement{
+	name = "A dangerous secret",
+	desc = [[Found the mysterious staff and told Minas Tirith about it.]],
+}
+
+--------------- Misc
+newAchievement{
+	name = "That was close",
+	desc = [[Kill your target while having only 1 life left.]],
+}
+newAchievement{
+	name = "Size matters",
+	desc = [[Do over 600 damage in one attack]],
+}
diff --git a/game/modules/tome/load.lua b/game/modules/tome/load.lua
index a0b9bc197f..53bf1842f6 100644
--- a/game/modules/tome/load.lua
+++ b/game/modules/tome/load.lua
@@ -29,10 +29,14 @@ local ActorInventory = require "engine.interface.ActorInventory"
 local ActorLevel = require "engine.interface.ActorLevel"
 local Birther = require "engine.Birther"
 local Store = require "mod.class.Store"
+local WorldAchievements = require "engine.interface.WorldAchievements"
 
 config.settings.tome = config.settings.tome or {}
 config.settings.tome.allow_build = config.settings.tome.allow_build or {}
 
+-- Achievements
+WorldAchievements:loadDefinition("/data/achievements/")
+
 -- Usefull keybinds
 KeyBind:load("move,hotkeys,inventory,actions,debug")
 
-- 
GitLab