diff --git a/game/modules/example/class/Actor.lua b/game/modules/example/class/Actor.lua new file mode 100644 index 0000000000000000000000000000000000000000..c8f26a7aeb6ad15ed8b52e65bd36f284a2715783 --- /dev/null +++ b/game/modules/example/class/Actor.lua @@ -0,0 +1,273 @@ +-- ToME - Tales of Middle-Earth +-- 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.Actor" +require "engine.Autolevel" +require "engine.interface.ActorLife" +require "engine.interface.ActorProject" +require "engine.interface.ActorLevel" +require "engine.interface.ActorStats" +require "engine.interface.ActorTalents" +require "engine.interface.ActorResource" +require "engine.interface.ActorFOV" +require "mod.class.interface.Combat" +local Map = require "engine.Map" + +module(..., package.seeall, class.inherit( + -- a ToME actor is a complex beast it uses may inetrfaces + engine.Actor, + engine.interface.ActorLife, + engine.interface.ActorProject, + engine.interface.ActorLevel, + engine.interface.ActorStats, + engine.interface.ActorTalents, + engine.interface.ActorResource, + engine.interface.ActorQuest, + engine.interface.ActorFOV, + mod.class.interface.Combat +)) + +function _M:init(t, no_default) + -- Define some basic combat stats + self.combat_armor = 0 + + -- Default regen + t.power_regen = t.power_regen or 1 + t.life_regen = t.life_regen or 0.25 -- Life regen real slow + + -- Default melee barehanded damage + self.combat = { dam=1, dammod={str=1} } + + engine.Actor.init(self, t, no_default) + engine.interface.ActorLife.init(self, t) + engine.interface.ActorProject.init(self, t) + engine.interface.ActorTalents.init(self, t) + engine.interface.ActorResource.init(self, t) + engine.interface.ActorStats.init(self, t) + engine.interface.ActorLevel.init(self, t) + engine.interface.ActorFOV.init(self, t) +end + +function _M:act() + if not engine.Actor.act(self) then return end + + self.changed = true + + -- Cooldown talents + self:cooldownTalents() + -- Regen resources + self:regenLife() + self:regenResources() + + -- Still enough energy to act ? + if self.energy.value < game.energy_to_act then return false end + + return true +end + +function _M:move(x, y, force) + local moved = false + if force or self:enoughEnergy() then + moved = engine.Actor.move(self, x, y, force) + if not force and moved and not self.did_energy then self:useEnergy() end + end + self.did_energy = nil + return moved +end + +function _M:tooltip() + return ([[%s +#00ffff#Level: %d +#ff0000#HP: %d (%d%%) +Stats: %d / %d / %d +%s]]):format( + self.name, + self.level, + self.life, self.life * 100 / self.max_life, + self:getStr(), + self:getDex(), + self:getCon() + ) +end + +function _M:die(src) + engine.interface.ActorLife.die(self, src) + + -- Gives the killer some exp for the kill + if src and src.gainExp then + src:gainExp(self:worthExp(src)) + end + + return true +end + +function _M:levelup() + self.max_life = self.max_life + 5 + + self:incMaxEnergy(3) + + -- Healp up on new level + self.life = self.max_life + self.power = self.max_power +end + +--- Notifies a change of stat value +function _M:onStatChange(stat, v) + if stat == self.STAT_CON then + self.max_life = self.max_life + 2 + end +end + +function _M:attack(target) + self:bumpInto(target) +end + + +--- Called before a talent is used +-- Check the actor can cast it +-- @param ab the talent (not the id, the table) +-- @return true to continue, false to stop +function _M:preUseTalent(ab, silent) + if not self:enoughEnergy() then print("fail energy") return false end + + if ab.mode == "sustained" then + if ab.sustain_power and self.max_power < ab.sustain_power and not self:isTalentActive(ab.id) then + game.logPlayer(self, "You do not have enough power to activate %s.", ab.name) + return false + end + else + if ab.power and self:getMana() < ab.power * (100 + self.fatigue) / 100 then + game.logPlayer(self, "You do not have enough power to cast %s.", ab.name) + return false + end + end + + if not silent then + -- Allow for silent talents + if ab.message ~= nil then + if ab.message then + game.logSeen(self, "%s", self:useTalentMessage(ab)) + end + elseif ab.mode == "sustained" and not self:isTalentActive(ab.id) then + game.logSeen(self, "%s activates %s.", self.name:capitalize(), ab.name) + elseif ab.mode == "sustained" and self:isTalentActive(ab.id) then + game.logSeen(self, "%s deactivates %s.", self.name:capitalize(), ab.name) + else + game.logSeen(self, "%s uses %s.", self.name:capitalize(), ab.name) + end + end + return true +end + +--- Called before a talent is used +-- Check if it must use a turn, mana, stamina, ... +-- @param ab the talent (not the id, the table) +-- @param ret the return of the talent action +-- @return true to continue, false to stop +function _M:postUseTalent(ab, ret) + if not ret then return end + + self:useEnergy() + + if ab.mode == "sustained" then + if not self:isTalentActive(ab.id) then + if ab.sustain_power then + self.max_power = self.max_power - ab.sustain_power + end + else + if ab.sustain_power then + self.max_power = self.max_power + ab.sustain_power + end + end + else + if ab.power then + self:incPower(-ab.power) + end + end + + return true +end + +--- Return the full description of a talent +-- You may overload it to add more data (like power usage, ...) +function _M:getTalentFullDescription(t) + local d = {} + + if t.mode == "passive" then d[#d+1] = "#6fff83#Use mode: #00FF00#Passive" + elseif t.mode == "sustained" then d[#d+1] = "#6fff83#Use mode: #00FF00#Sustained" + else d[#d+1] = "#6fff83#Use mode: #00FF00#Activable" + end + + if t.power or t.sustain_power then d[#d+1] = "#6fff83#Mana cost: #7fffd4#"..(t.power or t.sustain_power) end + if self:getTalentRange(t) > 1 then d[#d+1] = "#6fff83#Range: #FFFFFF#"..self:getTalentRange(t) + else d[#d+1] = "#6fff83#Range: #FFFFFF#melee/personal" + end + if t.cooldown then d[#d+1] = "#6fff83#Cooldown: #FFFFFF#"..t.cooldown end + + return table.concat(d, "\n").."\n#6fff83#Description: #FFFFFF#"..t.info(self, t) +end + +--- How much experience is this actor worth +-- @param target to whom is the exp rewarded +-- @return the experience rewarded +function _M:worthExp(target) + if not target.level or self.level < target.level - 3 then return 0 end + + local mult = 2 + if self.unique then mult = 6 + elseif self.egoed then mult = 3 end + return self.level * mult * self.exp_worth +end + +--- Can the actor see the target actor +-- This does not check LOS or such, only the actual ability to see it.<br/> +-- Check for telepathy, invisibility, stealth, ... +function _M:canSee(actor, def, def_pct) + if not actor then return false, 0 end + + -- Check for stealth. Checks against the target cunning and level + if actor:attr("stealth") and actor ~= self then + local def = self.level / 2 + self:getCun(25) + local hit, chance = self:checkHit(def, actor:attr("stealth") + (actor:attr("inc_stealth") or 0), 0, 100) + if not hit then + return false, chance + end + end + + if def ~= nil then + return def, def_pct + else + return true, 100 + end +end + +--- Can the target be applied some effects +-- @param what a string describing what is being tried +function _M:canBe(what) + if what == "poison" and rng.percent(100 * (self:attr("poison_immune") or 0)) then return false end + if what == "cut" and rng.percent(100 * (self:attr("cut_immune") or 0)) then return false end + if what == "confusion" and rng.percent(100 * (self:attr("confusion_immune") or 0)) then return false end + if what == "blind" and rng.percent(100 * (self:attr("blind_immune") or 0)) then return false end + if what == "stun" and rng.percent(100 * (self:attr("stun_immune") or 0)) then return false end + if what == "fear" and rng.percent(100 * (self:attr("fear_immune") or 0)) then return false end + if what == "knockback" and rng.percent(100 * (self:attr("knockback_immune") or 0)) then return false end + if what == "instakill" and rng.percent(100 * (self:attr("instakill_immune") or 0)) then return false end + return true +end diff --git a/game/modules/example/class/Game.lua b/game/modules/example/class/Game.lua new file mode 100644 index 0000000000000000000000000000000000000000..16c976e3de96c1af0132dee3b00866102d9672e7 --- /dev/null +++ b/game/modules/example/class/Game.lua @@ -0,0 +1,512 @@ +-- ToME - Tales of Middle-Earth +-- 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.GameTurnBased" +require "engine.KeyBind" +local Savefile = require "engine.Savefile" +local DamageType = require "engine.DamageType" +local Zone = require "engine.Zone" +local Map = require "engine.Map" +local Target = require "engine.Target" +local Level = require "engine.Level" +local Birther = require "engine.Birther" + +local Grid = require "mod.class.Grid" +local Actor = require "mod.class.Actor" +local Player = require "mod.class.Player" +local NPC = require "mod.class.NPC" + +local LogDisplay = require "engine.LogDisplay" +local LogFlasher = require "engine.LogFlasher" +local DebugConsole = require "engine.DebugConsole" +local FlyingText = require "engine.FlyingText" +local Tooltip = require "engine.Tooltip" + +local QuitDialog = require "mod.dialogs.Quit" + +module(..., package.seeall, class.inherit(engine.GameTurnBased, engine.interface.GameMusic, engine.interface.GameSound)) + +function _M:init() + engine.GameTurnBased.init(self, engine.KeyBind.new(), 1000, 100) + + -- Pause at birth + self.paused = true + + -- Same init as when loaded from a savefile + self:loaded() +end + +function _M:run() + self.flash = LogFlasher.new(0, 0, self.w, 20, nil, nil, nil, {255,255,255}, {0,0,0}) + self.logdisplay = LogDisplay.new(0, self.h * 0.8, self.w * 0.5, self.h * 0.2, nil, nil, nil, {255,255,255}, {30,30,30}) + self.tooltip = Tooltip.new(nil, nil, {255,255,255}, {30,30,30}) + self.flyers = FlyingText.new() + self:setFlyingText(self.flyers) + + self.log = function(style, ...) if type(style) == "number" then self.logdisplay(...) self.flash(style, ...) else self.logdisplay(style, ...) self.flash(self.flash.NEUTRAL, style, ...) end end + self.logSeen = function(e, style, ...) if e and self.level.map.seens(e.x, e.y) then self.log(style, ...) end end + self.logPlayer = function(e, style, ...) if e == self.player then self.log(style, ...) end end + + self.log(self.flash.GOOD, "Welcome to #00FF00#the template module!") + + -- Setup inputs + self:setupCommands() + self:setupMouse() + + -- Starting from here we create a new game + if not self.player then self:newGame() end + + -- Setup the targetting system + self.target = Target.new(Map, self.player) + self.target.target.entity = self.player + self.old_tmx, self.old_tmy = 0, 0 + self.target_style = "lock" + + -- Ok everything is good to go, activate the game in the engine! + self:setCurrent() + + if self.level then self:setupDisplayMode() end +end + +function _M:newGame() + self.player = Player.new{name=self.player_name, game_ender=true} + Map:setViewerActor(self.player) + self:setupDisplayMode() + + local birth = Birther.new(self.player, {"base", "role" }, function() + self:changeLevel(1, "dungeon") + print("[PLAYER BIRTH] resolve...") + self.player:resolve() + self.player:resolve(nil, true) + self.player.energy.value = self.energy_to_act + self.paused = true + print("[PLAYER BIRTH] resolved!") + end) + self:registerDialog(birth) +end + +function _M:loaded() + engine.GameTurnBased.loaded(self) + Zone:setup{npc_class="mod.class.NPC", grid_class="mod.class.Grid", } + Map:setViewerActor(self.player) + Map:setViewPort(200, 20, self.w - 200, math.floor(self.h * 0.80) - 20, 32, 32, nil, 20, true) + self.key = engine.KeyBind.new() +end + +function _M:onResolutionChange() + engine.Game.onResolutionChange(self) + print("[RESOLUTION] changed to ", self.w, self.h) + self:setupDisplayMode() + self.flash:resize(0, 0, self.w, 20) + self.logdisplay:resize(0, self.h * 0.8, self.w * 0.5, self.h * 0.2) +end + +function _M:setupDisplayMode() + print("[DISPLAY MODE] 32x32 ASCII/background") + Map:setViewPort(200, 20, self.w - 200, math.floor(self.h * 0.80) - 20, 32, 32, nil, 22, true, true) + Map:resetTiles() + Map.tiles.use_images = false + self:setupMiniMap() +end + +function _M:setupMiniMap() + if self.level and self.level.map then self.level.map._map:setupMiniMapGridSize(4) end +end + +function _M:save() + return class.save(self, self:defaultSavedFields{}, true) +end + +function _M:getSaveDescription() + return { + name = self.player.name, + description = ([[Exploring level %d of %s.]]):format(self.level.level, self.zone.name), + } +end + +function _M:leaveLevel(level, lev, old_lev) + if level:hasEntity(self.player) then + level.exited = level.exited or {} + if lev > old_lev then + level.exited.down = {x=self.player.x, y=self.player.y} + else + level.exited.up = {x=self.player.x, y=self.player.y} + end + level.last_turn = game.turn + level:removeEntity(self.player) + end +end + +function _M:changeLevel(lev, zone) + local old_lev = (self.level and not zone) and self.level.level or -1000 + if zone then + if self.zone then + self.zone:leaveLevel(false, lev, old_lev) + self.zone:leave() + end + if type(zone) == "string" then + self.zone = Zone.new(zone) + else + self.zone = zone + end + end + self.zone:getLevel(self, lev, old_lev) + + self:setupMiniMap() +end + +function _M:getPlayer() + return self.player +end + +function _M:tick() + if self.level then + if self.target.target.entity and not self.level:hasEntity(self.target.target.entity) then self.target.target.entity = false end + + engine.GameTurnBased.tick(self) + -- Fun stuff: this can make the game realtime, although callit it in display() will make it work better + -- (since display is on a set FPS while tick() ticks as much as possible + -- engine.GameEnergyBased.tick(self) + end + -- When paused (waiting for player input) we return true: this means we wont be called again until an event wakes us + if game.paused then return true end +end + +--- Called every game turns +-- Does nothing, you can override it +function _M:onTurn() + -- The following happens only every 10 game turns (once for every turn of 1 mod speed actors) + if self.turn % 10 ~= 0 then return end + + -- Process overlay effects + self.level.map:processEffects() +end + +function _M:display() + -- We display the player's interface + self.flash:display():toScreen(self.flash.display_x, self.flash.display_y) + self.logdisplay:display():toScreen(self.logdisplay.display_x, self.logdisplay.display_y) + if self.player then self.player.changed = false end + + -- Now the map, if any + if self.level and self.level.map and self.level.map.finished then + -- Display the map and compute FOV for the player if needed + if self.level.map.changed then + self.player:playerFOV() + end + + self.level.map:display() + + -- Display the targetting system if active + self.target:display() + + -- Display a tooltip if available + if self.tooltip_x then + local mx, my = self.tooltip_x , self.tooltip_y + local tmx, tmy = self.level.map:getMouseTile(mx, my) + self.tooltip:displayAtMap(tmx, tmy, mx, my) + end + + -- Move target around + if self.old_tmx ~= tmx or self.old_tmy ~= tmy then + self.target.target.x, self.target.target.y = tmx, tmy + end + self.old_tmx, self.old_tmy = tmx, tmy + + -- And the minimap + self.level.map:minimapDisplay(self.w - 200, 20, util.bound(self.player.x - 25, 0, self.level.map.w - 50), util.bound(self.player.y - 25, 0, self.level.map.h - 50), 50, 50, 0.6) + end + + engine.GameTurnBased.display(self) +end + +--- Targeting mode +-- Now before this is an hard piece of code. You probably wont need to change it much.<br/> +-- This uses a coroutine to allow a talent to request a target without interruption, yet while preserving the realtime-ness of the engine +function _M:targetMode(v, msg, co, typ) + local old = self.target_mode + self.target_mode = v + + if not v then + Map:setViewerFaction(self.always_target and "players" or nil) + if msg then self.log(type(msg) == "string" and msg or "Tactical display disabled. Press shift+'t' or right mouse click to enable.") end + self.level.map.changed = true + self.target:setActive(false) + + if tostring(old) == "exclusive" then + self.key = self.normal_key + self.key:setCurrent() + if self.target_co then + local co = self.target_co + self.target_co = nil + local ok, err = coroutine.resume(co, self.target.target.x, self.target.target.y, self.target.target.entity) + if not ok and err then print(debug.traceback(co)) error(err) end + end + end + else + Map:setViewerFaction("players") + if msg then self.log(type(msg) == "string" and msg or "Tactical display enabled. Press shift+'t' to disable.") end + self.level.map.changed = true + self.target:setActive(true, typ) + self.target_style = "lock" + + -- Exclusive mode means we disable the current key handler and use a specific one + -- that only allows targetting and resumes talent coroutine when done + if tostring(v) == "exclusive" then + self.target_co = co + self.key = self.targetmode_key + self.key:setCurrent() + + if self.target.target.entity and self.level.map.seens(self.target.target.entity.x, self.target.target.entity.y) and self.player ~= self.target.target.entity then + else + self.target:scan(5, nil, self.player.x, self.player.y) + end + end + self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) + end +end + +--- Setup the keybinds +function _M:setupCommands() + -- One key handler for targeting + self.targetmode_key = engine.KeyBind.new() + self.targetmode_key:addCommands{ _SPACE=function() self:targetMode(false, false) end, } + self.targetmode_key:addBinds + { + TACTICAL_DISPLAY = function() self:targetMode(false, false) end, + ACCEPT = function() + self:targetMode(false, false) + self.tooltip_x, self.tooltip_y = nil, nil + end, + EXIT = function() + self.target.target.entity = nil + self.target.target.x = nil + self.target.target.y = nil + self:targetMode(false, false) + self.tooltip_x, self.tooltip_y = nil, nil + end, + -- Targeting movement + RUN_LEFT = function() self.target:freemove(4) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end, + RUN_RIGHT = function() self.target:freemove(6) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end, + RUN_UP = function() self.target:freemove(8) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end, + RUN_DOWN = function() self.target:freemove(2) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end, + RUN_LEFT_DOWN = function() self.target:freemove(1) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end, + RUN_RIGHT_DOWN = function() self.target:freemove(3) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end, + RUN_LEFT_UP = function() self.target:freemove(7) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end, + RUN_RIGHT_UP = function() self.target:freemove(9) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end, + + MOVE_LEFT = function() if self.target_style == "lock" then self.target:scan(4) else self.target:freemove(4) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end, + MOVE_RIGHT = function() if self.target_style == "lock" then self.target:scan(6) else self.target:freemove(6) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end, + MOVE_UP = function() if self.target_style == "lock" then self.target:scan(8) else self.target:freemove(8) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end, + MOVE_DOWN = function() if self.target_style == "lock" then self.target:scan(2) else self.target:freemove(2) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end, + MOVE_LEFT_DOWN = function() if self.target_style == "lock" then self.target:scan(1) else self.target:freemove(1) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end, + MOVE_RIGHT_DOWN = function() if self.target_style == "lock" then self.target:scan(3) else self.target:freemove(3) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end, + MOVE_LEFT_UP = function() if self.target_style == "lock" then self.target:scan(7) else self.target:freemove(7) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end, + MOVE_RIGHT_UP = function() if self.target_style == "lock" then self.target:scan(9) else self.target:freemove(9) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end, + } + + self.normal_key = self.key + + -- One key handled for normal function + self.key:addBinds + { + -- Movements + MOVE_LEFT = function() self.player:moveDir(4) end, + MOVE_RIGHT = function() self.player:moveDir(6) end, + MOVE_UP = function() self.player:moveDir(8) end, + MOVE_DOWN = function() self.player:moveDir(2) end, + MOVE_LEFT_UP = function() self.player:moveDir(7) end, + MOVE_LEFT_DOWN = function() self.player:moveDir(1) end, + MOVE_RIGHT_UP = function() self.player:moveDir(9) end, + MOVE_RIGHT_DOWN = function() self.player:moveDir(3) end, + MOVE_STAY = function() self.player:useEnergy() end, + + RUN_LEFT = function() self.player:runInit(4) end, + RUN_RIGHT = function() self.player:runInit(6) end, + RUN_UP = function() self.player:runInit(8) end, + RUN_DOWN = function() self.player:runInit(2) end, + RUN_LEFT_UP = function() self.player:runInit(7) end, + RUN_LEFT_DOWN = function() self.player:runInit(1) end, + RUN_RIGHT_UP = function() self.player:runInit(9) end, + RUN_RIGHT_DOWN = function() self.player:runInit(3) end, + + -- Hotkeys + HOTKEY_1 = function() self.player:activateHotkey(1) end, + HOTKEY_2 = function() self.player:activateHotkey(2) end, + HOTKEY_3 = function() self.player:activateHotkey(3) end, + HOTKEY_4 = function() self.player:activateHotkey(4) end, + HOTKEY_5 = function() self.player:activateHotkey(5) end, + HOTKEY_6 = function() self.player:activateHotkey(6) end, + HOTKEY_7 = function() self.player:activateHotkey(7) end, + HOTKEY_8 = function() self.player:activateHotkey(8) end, + HOTKEY_9 = function() self.player:activateHotkey(9) end, + HOTKEY_10 = function() self.player:activateHotkey(10) end, + HOTKEY_11 = function() self.player:activateHotkey(11) end, + HOTKEY_12 = function() self.player:activateHotkey(12) end, + HOTKEY_SECOND_1 = function() self.player:activateHotkey(13) end, + HOTKEY_SECOND_2 = function() self.player:activateHotkey(14) end, + HOTKEY_SECOND_3 = function() self.player:activateHotkey(15) end, + HOTKEY_SECOND_4 = function() self.player:activateHotkey(16) end, + HOTKEY_SECOND_5 = function() self.player:activateHotkey(17) end, + HOTKEY_SECOND_6 = function() self.player:activateHotkey(18) end, + HOTKEY_SECOND_7 = function() self.player:activateHotkey(19) end, + HOTKEY_SECOND_8 = function() self.player:activateHotkey(20) end, + HOTKEY_SECOND_9 = function() self.player:activateHotkey(21) end, + HOTKEY_SECOND_10 = function() self.player:activateHotkey(22) end, + HOTKEY_SECOND_11 = function() self.player:activateHotkey(23) end, + HOTKEY_SECOND_12 = function() self.player:activateHotkey(24) end, + HOTKEY_THIRD_1 = function() self.player:activateHotkey(25) end, + HOTKEY_THIRD_2 = function() self.player:activateHotkey(26) end, + HOTKEY_THIRD_3 = function() self.player:activateHotkey(27) end, + HOTKEY_THIRD_4 = function() self.player:activateHotkey(28) end, + HOTKEY_THIRD_5 = function() self.player:activateHotkey(29) end, + HOTKEY_THIRD_6 = function() self.player:activateHotkey(30) end, + HOTKEY_THIRD_7 = function() self.player:activateHotkey(31) end, + HOTKEY_THIRD_8 = function() self.player:activateHotkey(31) end, + HOTKEY_THIRD_9 = function() self.player:activateHotkey(33) end, + HOTKEY_THIRD_10 = function() self.player:activateHotkey(34) end, + HOTKEY_THIRD_11 = function() self.player:activateHotkey(35) end, + HOTKEY_THIRD_12 = function() self.player:activateHotkey(36) end, + HOTKEY_PREV_PAGE = function() self.player:prevHotkeyPage() end, + HOTKEY_NEXT_PAGE = function() self.player:nextHotkeyPage() end, + + -- Actions + CHANGE_LEVEL = function() + local e = self.level.map(self.player.x, self.player.y, Map.TERRAIN) + if self.player:enoughEnergy() and e.change_level then + self:changeLevel(e.change_zone and e.change_level or self.level.level + e.change_level, e.change_zone) + else + self.log("There is no way out of this level here.") + end + end, + + REST = function() + self.player:restInit() + end, + + USE_TALENTS = function() + self.player:useTalents() + end, + + SAVE_GAME = function() + self:saveGame() + end, + + SHOW_CHARACTER_SHEET = function() + self:registerDialog(require("mod.dialogs.CharacterSheet").new(self.player)) + end, + + -- Exit the game + QUIT_GAME = function() + self:onQuit() + end, + + EXIT = function() + local menu menu = require("engine.dialogs.GameMenu").new{ + "resume", + "keybinds", + "resolution", + "save", + "quit" + } + self:registerDialog(menu) + end, + + TACTICAL_DISPLAY = function() + if Map.view_faction then + self.always_target = nil + Map:setViewerFaction(nil) + else + self.always_target = true + Map:setViewerFaction("players") + end + end, + + LOOK_AROUND = function() + self.flash:empty(true) + self.flash(self.flash.GOOD, "Looking around... (direction keys to select interresting things, shift+direction keys to move freely)") + local co = coroutine.create(function() self.player:getTarget{type="hit", no_restrict=true, range=2000} end) + local ok, err = coroutine.resume(co) + if not ok and err then print(debug.traceback(co)) error(err) end + end, + } + self.key:setCurrent() +end + +function _M:setupMouse() + -- Those 2 locals will be "absorbed" into the mosue event handler function, this is a closure + local derivx, derivy = 0, 0 + + self.mouse:registerZone(Map.display_x, Map.display_y, Map.viewport.width, Map.viewport.height, function(button, mx, my, xrel, yrel) + -- Move tooltip + self.tooltip_x, self.tooltip_y = mx, my + local tmx, tmy = self.level.map:getMouseTile(mx, my) + + -- Target stuff + if button == "right" then + self.player:mouseMove(tmx, tmy) + + -- Move map around + elseif button == "left" and xrel and yrel then + derivx = derivx + xrel + derivy = derivy + yrel + game.level.map.changed = true + if derivx >= game.level.map.tile_w then + game.level.map.mx = game.level.map.mx - 1 + derivx = derivx - game.level.map.tile_w + elseif derivx <= -game.level.map.tile_w then + game.level.map.mx = game.level.map.mx + 1 + derivx = derivx + game.level.map.tile_w + end + if derivy >= game.level.map.tile_h then + game.level.map.my = game.level.map.my - 1 + derivy = derivy - game.level.map.tile_h + elseif derivy <= -game.level.map.tile_h then + game.level.map.my = game.level.map.my + 1 + derivy = derivy + game.level.map.tile_h + end + game.level.map._map:setScroll(game.level.map.mx, game.level.map.my) + end + end) + -- Scroll message log + self.mouse:registerZone(self.logdisplay.display_x, self.logdisplay.display_y, self.w, self.h, function(button) + if button == "wheelup" then self.logdisplay:scrollUp(1) end + if button == "wheeldown" then self.logdisplay:scrollUp(-1) end + end, {button=true}) + self.mouse:setCurrent() +end + +--- Ask if we realy want to close, if so, save the game first +function _M:onQuit() + self.player:restStop() + + if not self.quit_dialog then + self.quit_dialog = QuitDialog.new() + self:registerDialog(self.quit_dialog) + end +end + +--- Requests the game to save +function _M:saveGame() + local save = Savefile.new(self.save_name) + save:saveGame(self) + save:close() + self.log("Saved game.") +end diff --git a/game/modules/example/class/Grid.lua b/game/modules/example/class/Grid.lua new file mode 100644 index 0000000000000000000000000000000000000000..f6df667897e586d0233680e33dcbeee9a9c34af2 --- /dev/null +++ b/game/modules/example/class/Grid.lua @@ -0,0 +1,68 @@ +-- ToME - Tales of Middle-Earth +-- 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.Grid" +local DamageType = require "engine.DamageType" + +module(..., package.seeall, class.inherit(engine.Grid)) + +function _M:init(t, no_default) + engine.Grid.init(self, t, no_default) +end + +function _M:block_move(x, y, e, act, couldpass) + -- Open doors + if self.door_opened and act then + game.level.map(x, y, engine.Map.TERRAIN, game.zone.grid_list.DOOR_OPEN) + return true + elseif self.door_opened and not couldpass then + return true + end + + -- Pass walls + if e and self.can_pass and e.can_pass then + for what, check in pairs(e.can_pass) do + if self.can_pass[what] and self.can_pass[what] <= check then return false end + end + end + + return self.does_block_move +end + +function _M:on_move(x, y, who, forced) + if forced then return end + if who.move_project and next(who.move_project) then + for typ, dam in pairs(who.move_project) do + DamageType:get(typ).projector(who, x, y, typ, dam) + end + end +end + +function _M:tooltip() + if self.show_tooltip then + local name = ((self.show_tooltip == true) and self.name or self.show_tooltip) + if self.desc then + return name.."\n"..self.desc + else + return name + end + end +end + diff --git a/game/modules/example/class/NPC.lua b/game/modules/example/class/NPC.lua new file mode 100644 index 0000000000000000000000000000000000000000..a5b78b82a5c81cf1ebb6add99f8966c30584169c --- /dev/null +++ b/game/modules/example/class/NPC.lua @@ -0,0 +1,70 @@ +-- ToME - Tales of Middle-Earth +-- 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 ActorAI = require "engine.interface.ActorAI" +local Faction = require "engine.Faction" +require "mod.class.Actor" + +module(..., package.seeall, class.inherit(mod.class.Actor, engine.interface.ActorAI)) + +function _M:init(t, no_default) + mod.class.Actor.init(self, t, no_default) + ActorAI.init(self, t) +end + +function _M:act() + -- Do basic actor stuff + if not mod.class.Actor.act(self) then return end + + -- Compute FOV, if needed + self:computeFOV(self.sight or 20) + + -- Let the AI think .... beware of Shub ! + -- If AI did nothing, use energy anyway + self:doAI() + if not self.energy.used then self:useEnergy() end +end + +--- Called by ActorLife interface +-- We use it to pass aggression values to the AIs +function _M:onTakeHit(value, src) + if not self.ai_target.actor and src.targetable then + self.ai_target.actor = src + end + + return mod.class.Actor.onTakeHit(self, value, src) +end + +function _M:tooltip() + local factcolor, factstate = "#ANTIQUE_WHITE#", "neutral" + if self:reactionToward(game.player) < 0 then factcolor, factstate = "#LIGHT_RED#", "hostile" + elseif self:reactionToward(game.player) > 0 then factcolor, factstate = "#LIGHT_GREEN#", "friendly" + end + + local str = mod.class.Actor.tooltip(self) + return str..([[ + +Faction: %s%s (%s) +Target: %s +UID: %d]]):format( + factcolor, Faction.factions[self.faction].name, factstate, + self.ai_target.actor and self.ai_target.actor.name or "none", + self.uid) +end diff --git a/game/modules/example/class/Player.lua b/game/modules/example/class/Player.lua new file mode 100644 index 0000000000000000000000000000000000000000..d19816ab55611e402754504445b70210311fd04c --- /dev/null +++ b/game/modules/example/class/Player.lua @@ -0,0 +1,240 @@ +-- ToME - Tales of Middle-Earth +-- 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 "mod.class.Actor" +require "engine.interface.PlayerRest" +require "engine.interface.PlayerRun" +require "engine.interface.PlayerHotkeys" +local Map = require "engine.Map" +local Dialog = require "engine.Dialog" +local ActorTalents = require "engine.interface.ActorTalents" +local DeathDialog = require "mod.dialogs.DeathDialog" +local Astar = require"engine.Astar" +local DirectPath = require"engine.DirectPath" + +--- Defines the player for ToME +-- It is a normal actor, with some redefined methods to handle user interaction.<br/> +-- It is also able to run and rest and use hotkeys +module(..., package.seeall, class.inherit( + mod.class.Actor, + engine.interface.PlayerRest, + engine.interface.PlayerRun, + engine.interface.PlayerHotkeys +)) + +function _M:init(t, no_default) + t.display=t.display or '@' + t.color_r=t.color_r or 230 + t.color_g=t.color_g or 230 + t.color_b=t.color_b or 230 + + t.player = true + t.type = t.type or "humanoid" + t.subtype = t.subtype or "player" + t.faction = t.faction or "players" + + t.lite = t.lite or 0 + + mod.class.Actor.init(self, t, no_default) + engine.interface.PlayerHotkeys.init(self, t) + + self.descriptor = {} +end + +function _M:move(x, y, force) + local moved = mod.class.Actor.move(self, x, y, force) + if moved then + game.level.map:moveViewSurround(self.x, self.y, 8, 8) + end + return moved +end + +function _M:act() + if not mod.class.Actor.act(self) then return end + + -- Clean log flasher + game.flash:empty() + + -- Resting ? Running ? Otherwise pause + if not self:restStep() and not self:runStep() and self.player then + game.paused = true + end +end + +function _M:playerFOV() + -- Clean FOV before computing it + game.level.map:cleanFOV() + -- Compute both the normal and the lite FOV, using cache + self:computeFOV(self.sight or 20, "block_sight", function(x, y, dx, dy, sqdist) + game.level.map:apply(x, y, math.max((20 - math.sqrt(sqdist)) / 14, 0.6)) + end, true, false, true) + self:computeFOV(self.lite, "block_sight", function(x, y, dx, dy, sqdist) game.level.map:applyLite(x, y) end, true, true, true) +end + +--- Called before taking a hit, overload mod.class.Actor:onTakeHit() to stop resting and running +function _M:onTakeHit(value, src) + self:runStop("taken damage") + self:restStop("taken damage") + local ret = mod.class.Actor.onTakeHit(self, value, src) + if self.life < self.max_life * 0.3 then + local sx, sy = game.level.map:getTileToScreen(self.x, self.y) + game.flyers:add(sx, sy, 30, (rng.range(0,2)-1) * 0.5, 2, "LOW HEALTH!", {255,0,0}, true) + end + return ret +end + +function _M:die(src) + if self.game_ender then + engine.interface.ActorLife.die(self, src) + game.paused = true + self.energy.value = game.energy_to_act + game:registerDialog(DeathDialog.new(self)) + else + mod.class.Actor.die(self, src) + end +end + +function _M:setName(name) + self.name = name + game.save_name = name +end + +--- Notify the player of available cooldowns +function _M:onTalentCooledDown(tid) + local t = self:getTalentFromId(tid) + + local x, y = game.level.map:getTileToScreen(self.x, self.y) + game.flyers:add(x, y, 30, -0.3, -3.5, ("%s available"):format(t.name:capitalize()), {0,255,00}) + game.log("#00ff00#Talent %s is ready to use.", t.name) +end + +function _M:levelup() + mod.class.Actor.levelup(self) + + local x, y = game.level.map:getTileToScreen(self.x, self.y) + game.flyers:add(x, y, 80, 0.5, -2, "LEVEL UP!", {0,255,255}) + game.log("#00ffff#Welcome to level %d.", self.level) +end + +--- Tries to get a target from the user +-- *WARNING* If used inside a coroutine it will yield and resume it later when a target is found. +-- This is usualy just what you want so dont think too much about it :) +function _M:getTarget(typ) + if coroutine.running() then + local msg + if type(typ) == "string" then msg, typ = typ, nil + elseif type(typ) == "table" then + if typ.default_target then game.target.target.entity = typ.default_target end + msg = typ.msg + end + game:targetMode("exclusive", msg, coroutine.running(), typ) + if typ.nolock then + game.target_style = "free" + end + return coroutine.yield() + end + return game.target.target.x, game.target.target.y, game.target.target.entity +end + +--- Sets the current target +function _M:setTarget(target) + game.target.target.entity = target + game.target.target.x = target.x + game.target.target.y = target.y +end + +local function spotHostiles(self) + local seen = false + -- Check for visible monsters, only see LOS actors, so telepathy wont prevent resting + core.fov.calc_circle(self.x, self.y, 20, function(_, x, y) return game.level.map:opaque(x, y) end, function(_, x, y) + local actor = game.level.map(x, y, game.level.map.ACTOR) + if actor and self:reactionToward(actor) < 0 and self:canSee(actor) and game.level.map.seens(x, y) then seen = true end + end, nil) + return seen +end + +--- Can we continue resting ? +-- We can rest if no hostiles are in sight, and if we need life/mana/stamina (and their regen rates allows them to fully regen) +function _M:restCheck() + if spotHostiles(self) then return false, "hostile spotted" end + + -- Check ressources, make sure they CAN go up, otherwise we will never stop + if self:getPower() < self:getMaxPower() and self.power_regen > 0 then return true end + if self.life < self.max_life and self.life_regen> 0 then return true end + + return false, "all resources and life at maximun" +end + +--- Can we continue running? +-- We can run if no hostiles are in sight, and if we no interresting terrains are next to us +function _M:runCheck() + if spotHostiles(self) then return false, "hostile spotted" end + + -- Notice any noticable terrain + local noticed = false + self:runScan(function(x, y) + -- Only notice interresting terrains + local grid = game.level.map(x, y, Map.TERRAIN) + if grid and grid.notice then noticed = "interesting terrain" end + end) + if noticed then return false, noticed end + + self:playerFOV() + + return engine.interface.PlayerRun.runCheck(self) +end + + +--- Runs to the clicked mouse spot +-- if no monsters in sight it will try to make an A* path, if it fails it will do a direct path +-- if there are monsters in sight it will move one stop in the direct path direction +function _M:mouseMove(tmx, tmy) + if config.settings.tome.cheat and game.key.status[game.key._LSHIFT] and game.key.status[game.key._LCTRL] then + game.log("[CHEAT] teleport to %dx%d", tmx, tmy) + self:move(tmx, tmy, true) + else + -- If hostiles, attack! + if spotHostiles(self) or math.floor(core.fov.distance(self.x, self.y, tmx, tmy)) == 1 then + local l = line.new(self.x, self.y, tmx, tmy) + local nx, ny = l() + self:move(nx or self.x, ny or self.y) + return + end + + local a = Astar.new(game.level.map, self) + local path = a:calc(self.x, self.y, tmx, tmy, true) + -- No Astar path ? jsut be dumb and try direct line + if not path then + local d = DirectPath.new(game.level.map, self) + path = d:calc(self.x, self.y, tmx, tmy, true) + end + + if path then + -- Should we just try to move in the direction, aka: attack! + if path[1] and game.level.map:checkAllEntities(path[1].x, path[1].y, "block_move", self) then self:move(path[1].x, path[1].y) return end + + -- Insert the player coords, running needs to find the player + table.insert(path, 1, {x=self.x, y=self.y}) + + -- Move along the projected A* path + self:runFollow(path) + end + end +end diff --git a/game/modules/example/class/interface/Combat.lua b/game/modules/example/class/interface/Combat.lua new file mode 100644 index 0000000000000000000000000000000000000000..947bb6444e18a282d2ae100d417008d5a1b77525 --- /dev/null +++ b/game/modules/example/class/interface/Combat.lua @@ -0,0 +1,65 @@ +-- ToME - Tales of Middle-Earth +-- 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 DamageType = require "engine.DamageType" +local Map = require "engine.Map" +local Chat = require "engine.Chat" +local Target = require "engine.Target" +local Talents = require "engine.interface.ActorTalents" + +--- Interface to add ToME combat system +module(..., package.seeall, class.make) + +--- Checks what to do with the target +-- Talk ? attack ? displace ? +function _M:bumpInto(target) + local reaction = self:reactionToward(target) + if reaction < 0 then + return self:attackTarget(target) + elseif reaction >= 0 then + if self.move_others then + -- Displace + game.level.map:remove(self.x, self.y, Map.ACTOR) + game.level.map:remove(target.x, target.y, Map.ACTOR) + game.level.map(self.x, self.y, Map.ACTOR, target) + game.level.map(target.x, target.y, Map.ACTOR, self) + self.x, self.y, target.x, target.y = target.x, target.y, self.x, self.y + end + end +end + +--- Makes the death happen! +function _M:attackTarget(target, damtype, mult, noenergy) + local speed, hit = nil, false + + if self.combat then + local s, h = self:attackTargetWith(target, self.combat, damtype, mult) + speed = math.max(speed or 0, s) + hit = hit or h + end + + -- We use up our own energy + if speed and not noenergy then + self:useEnergy(game.energy_to_act * speed) + self.did_energy = true + end + + return hit +end diff --git a/game/modules/example/data/birth/descriptors.lua b/game/modules/example/data/birth/descriptors.lua new file mode 100644 index 0000000000000000000000000000000000000000..8191fb93369a208dec4ee732b4b6c50d458a8d2a --- /dev/null +++ b/game/modules/example/data/birth/descriptors.lua @@ -0,0 +1,48 @@ +-- ToME - Tales of Middle-Earth +-- 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 + +newBirthDescriptor{ + type = "base", + name = "base", + desc = { + }, + experience = 1.0, + + copy = { + max_level = 10, + }, +} + +newBirthDescriptor{ + type = "role", + name = "Destroyer", + desc = + { + "Boom!", + }, +} + +newBirthDescriptor{ + type = "role", + name = "Acid-maniac", + desc = + { + "Zshhhhhhhh!", + }, +} diff --git a/game/modules/example/data/damage_types.lua b/game/modules/example/data/damage_types.lua new file mode 100644 index 0000000000000000000000000000000000000000..9854ec9176e4c4e76d1e462edb6655b9911f3574 --- /dev/null +++ b/game/modules/example/data/damage_types.lua @@ -0,0 +1,53 @@ +-- ToME - Tales of Middle-Earth +-- 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 + +-- The basic stuff used to damage a grid +setDefaultProjector(function(src, x, y, type, dam) + local target = game.level.map(x, y, Map.ACTOR) + if target then + local flash = game.flash.NEUTRAL + if target == game.player then flash = game.flash.BAD end + if src == game.player then flash = game.flash.GOOD end + + game.logSeen(target, flash, "%s hits %s for %s%0.2f %s damage#LAST#.", src.name:capitalize(), target.name, DamageType:get(type).text_color or "#aaaaaa#", dam, DamageType:get(type).name) + local sx, sy = game.level.map:getTileToScreen(x, y) + if target:takeHit(dam, src) then + if src == game.player or target == game.player then + game.flyers:add(sx, sy, 30, (rng.range(0,2)-1) * 0.5, -3, "Kill!", {255,0,255}) + end + else + if src == game.player then + game.flyers:add(sx, sy, 30, (rng.range(0,2)-1) * 0.5, -3, tostring(-math.ceil(dam)), {0,255,0}) + elseif target == game.player then + game.flyers:add(sx, sy, 30, (rng.range(0,2)-1) * 0.5, -3, tostring(-math.ceil(dam)), {255,0,0}) + end + end + return dam + end + return 0 +end) + +newDamageType{ + name = "physical", type = "PHYSICAL", +} + +-- Acid detroys potions +newDamageType{ + name = "acid", type = "ACID", text_color = "#GREEN#", +} diff --git a/game/modules/example/data/general/grids/basic.lua b/game/modules/example/data/general/grids/basic.lua new file mode 100644 index 0000000000000000000000000000000000000000..6d14ec4987bcf837299356f63b8c6a22595e75eb --- /dev/null +++ b/game/modules/example/data/general/grids/basic.lua @@ -0,0 +1,99 @@ +-- ToME - Tales of Middle-Earth +-- 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 + +newEntity{ + define_as = "UP_WILDERNESS", + name = "exit to the wilds", + display = '<', color_r=255, color_g=0, color_b=255, + always_remember = true, + notice = true, + change_level = 1, + change_zone = "wilderness", +} + +newEntity{ + define_as = "UP", + name = "previous level", + display = '<', color_r=255, color_g=255, color_b=0, + notice = true, + always_remember = true, + change_level = -1, +} + +newEntity{ + define_as = "DOWN", + name = "next level", + display = '>', color_r=255, color_g=255, color_b=0, + notice = true, + always_remember = true, + change_level = 1, +} + +newEntity{ + define_as = "FLOOR", + name = "floor", image = "terrain/marble_floor.png", + display = '.', color_r=255, color_g=255, color_b=255, back_color=colors.DARK_GREY, +} + +newEntity{ + define_as = "WALL", + name = "wall", image = "terrain/granite_wall1.png", + display = '#', color_r=255, color_g=255, color_b=255, back_color=colors.GREY, + always_remember = true, + does_block_move = true, + can_pass = {pass_wall=1}, + block_sight = true, + air_level = -20, + dig = "FLOOR", +} + +newEntity{ + define_as = "DOOR", + name = "door", image = "terrain/granite_door1.png", + display = '+', color_r=238, color_g=154, color_b=77, back_color=colors.DARK_UMBER, + notice = true, + always_remember = true, + block_sight = true, + door_opened = "DOOR_OPEN", + dig = "DOOR_OPEN", +} + +newEntity{ + define_as = "DOOR_OPEN", + name = "open door", image = "terrain/granite_door1_open.png", + display = "'", color_r=238, color_g=154, color_b=77, back_color=colors.DARK_GREY, + always_remember = true, + door_closed = "DOOR", +} + +newEntity{ + define_as = "OLD_FLOOR", + name = "floor", image = "terrain/maze_floor.png", + display = '.', color_r=255, color_g=255, color_b=255, back_color=colors.DARK_GREY, +} + +newEntity{ + define_as = "OLD_WALL", + name = "wall", image = "terrain/granite_wall_lichen.png", back_color=colors.GREY, + display = '#', color_r=255, color_g=255, color_b=255, + always_remember = true, + does_block_move = true, + block_sight = true, + air_level = -20, +} diff --git a/game/modules/example/data/general/npcs/kobold.lua b/game/modules/example/data/general/npcs/kobold.lua new file mode 100644 index 0000000000000000000000000000000000000000..e890bb8fdd6a5dda82192fd77ad36c3b0f946bd4 --- /dev/null +++ b/game/modules/example/data/general/npcs/kobold.lua @@ -0,0 +1,115 @@ +-- ToME - Tales of Middle-Earth +-- 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 + +local Talents = require("engine.interface.ActorTalents") + +newEntity{ --rodent base + define_as = "BASE_NPC_RODENT", + type = "vermin", subtype = "rodent", + display = "r", color=colors.WHITE, + can_multiply = 2, + body = { INVEN = 10 }, + + autolevel = "warrior", + ai = "dumb_talented_simple", ai_state = { talent_in=3, }, + energy = { mod=1 }, + stats = { str=8, dex=15, mag=3, con=5 }, + combat_armor = 1, combat_def = 1, + rank = 1, + size_category = 1, +} + +newEntity{ base = "BASE_NPC_RODENT", + name = "giant white mouse", color=colors.WHITE, + level_range = {1, 3}, exp_worth = 1, + rarity = 4, + max_life = resolvers.rngavg(5,9), + combat = { dam=5, atk=15, apr=10 }, +} + +newEntity{ base = "BASE_NPC_RODENT", + name = "giant brown mouse", color=colors.UMBER, + level_range = {1, 3}, exp_worth = 1, + rarity = 4, + max_life = resolvers.rngavg(5,9), + combat = { dam=5, atk=15, apr=10 }, +} + +newEntity{ base = "BASE_NPC_RODENT", + name = "giant white rat", color=colors.WHITE, + level_range = {1, 4}, exp_worth = 1, + rarity = 5, + max_life = resolvers.rngavg(15,20), + combat = { dam=7, atk=15, apr=10 }, +} + +newEntity{ base = "BASE_NPC_RODENT", + name = "giant brown rat", color=colors.UMBER, + level_range = {1, 4}, exp_worth = 1, + rarity = 5, + max_life = resolvers.rngavg(15,20), + combat = { dam=7, atk=15, apr=10 }, +} + +newEntity{ base = "BASE_NPC_RODENT", + name = "giant rabbit", color=colors.UMBER, + desc = [[Kill the wabbit, kill the wabbit, kill the wabbbbbiiiiiit.]], + level_range = {1, 4}, exp_worth = 1, + rarity = 6, + max_life = resolvers.rngavg(20,30), + combat = { dam=8, atk=16, apr=10 }, +} + +newEntity{ base = "BASE_NPC_RODENT", + name = "giant crystal rat", color=colors.PINK, + desc = [[Instead of fur this rat has crystals growing on its back which provide extra protection.]], + level_range = {1, 5}, exp_worth = 1, + rarity = 6, + max_life = resolvers.rngavg(35,50), + combat = { dam=7, atk=15, apr=10 }, + combat_armor = 4, combat_def = 2, +} + +newEntity{ base = "BASE_NPC_RODENT", + name = "cute little bunny", color=colors.SALMON, + desc = [[It looks at you with cute little eyes before jumping at you with razor sharp teeth.]], + level_range = {1, 15}, exp_worth = 3, + rarity = 200, + max_life = resolvers.rngavg(15,20), + combat = { dam=50, atk=15, apr=10 }, + combat_armor = 1, combat_def = 20, +} + +newEntity{ base = "BASE_NPC_RODENT", + name = "giant grey mouse", color=colors.SLATE, + level_range = {1, 3}, exp_worth = 1, + rarity = 6, + max_life = resolvers.rngavg(5,9), + combat = { dam=5, atk=15, apr=10 }, + resolvers.talents{ [Talents.T_CRAWL_POISON]=1 }, +} + +newEntity{ base = "BASE_NPC_RODENT", + name = "giant grey rat", color=colors.SLATE, + level_range = {1, 4}, exp_worth = 1, + rarity = 7, + max_life = resolvers.rngavg(15,20), + combat = { dam=7, atk=15, apr=10 }, + resolvers.talents{ [Talents.T_CRAWL_POISON]=1 }, +} diff --git a/game/modules/example/data/gfx/particles/acid.lua b/game/modules/example/data/gfx/particles/acid.lua new file mode 100644 index 0000000000000000000000000000000000000000..6a5cfb89e9ed7fbe6b4974fb8b46c09d6b96d090 --- /dev/null +++ b/game/modules/example/data/gfx/particles/acid.lua @@ -0,0 +1,38 @@ +-- ToME - Tales of Middle-Earth +-- 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 + +return { + base = 1000, + + angle = { 0, 360 }, anglev = { 2000, 4000 }, anglea = { 200, 600 }, + + life = { 5, 10 }, + size = { 3, 6 }, sizev = {0, 0}, sizea = {0, 0}, + + r = {0, 0}, rv = {0, 0}, ra = {0, 0}, + g = {80, 200}, gv = {0, 10}, ga = {0, 0}, + b = {0, 0}, bv = {0, 0}, ba = {0, 0}, + a = {255, 255}, av = {0, 0}, aa = {0, 0}, + +}, function(self) + self.nb = (self.nb or 0) + 1 + if self.nb < 4 then + self.ps:emit(100) + end +end diff --git a/game/modules/example/data/rooms/pilar.lua b/game/modules/example/data/rooms/pilar.lua new file mode 100644 index 0000000000000000000000000000000000000000..337f27805e2df9d456c181de2f8a668360a92ea4 --- /dev/null +++ b/game/modules/example/data/rooms/pilar.lua @@ -0,0 +1,26 @@ +-- ToME - Tales of Middle-Earth +-- 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 + +return { +[[#!!!!!!!!#]], +[[!........!]], +[[!...##...!]], +[[!........!]], +[[#!!!!!!!!#]], +} diff --git a/game/modules/example/data/rooms/simple.lua b/game/modules/example/data/rooms/simple.lua new file mode 100644 index 0000000000000000000000000000000000000000..bcaf29ac196c8593c9c500c8c0f851024679549b --- /dev/null +++ b/game/modules/example/data/rooms/simple.lua @@ -0,0 +1,37 @@ +-- ToME - Tales of Middle-Earth +-- 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 + +return function(gen, id) + local w = rng.range(5, 12) + local h = rng.range(5, 12) + return { name="simple"..w.."x"..h, w=w, h=h, generator = function(self, x, y, is_lit) + for i = 1, self.w do + for j = 1, self.h do + if i == 1 or i == self.w or j == 1 or j == self.h then + gen.map.room_map[i-1+x][j-1+y].can_open = true + gen.map(i-1+x, j-1+y, Map.TERRAIN, gen.grid_list[gen:resolve('#')]) + else + gen.map.room_map[i-1+x][j-1+y].room = id + gen.map(i-1+x, j-1+y, Map.TERRAIN, gen.grid_list[gen:resolve('.')]) + end + if is_lit then gen.map.lites(i-1+x, j-1+y, true) end + end + end + end} +end diff --git a/game/modules/example/data/talents.lua b/game/modules/example/data/talents.lua new file mode 100644 index 0000000000000000000000000000000000000000..da57bd68b205d77cc81be4d184b9a2d487f148f8 --- /dev/null +++ b/game/modules/example/data/talents.lua @@ -0,0 +1,34 @@ +-- ToME - Tales of Middle-Earth +-- 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 + +newTalentType{ type="role/combat", name = "combat", description = "Combat techniques" } + +newTalent{ + name = "Kick", + type = {"role/combat", 1}, + points = 1, + cooldown = 6, + power = 2, + action = function(self, t) + return true + end, + info = function(self, t) + return "Kick!" + end, +} diff --git a/game/modules/example/data/zones/dungeon/grids.lua b/game/modules/example/data/zones/dungeon/grids.lua new file mode 100644 index 0000000000000000000000000000000000000000..14dc047df0c83b59498fb0f62c673df2fbc8ca70 --- /dev/null +++ b/game/modules/example/data/zones/dungeon/grids.lua @@ -0,0 +1,20 @@ +-- ToME - Tales of Middle-Earth +-- 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 + +load("/data/general/grids/basic.lua") diff --git a/game/modules/example/data/zones/dungeon/npcs.lua b/game/modules/example/data/zones/dungeon/npcs.lua new file mode 100644 index 0000000000000000000000000000000000000000..5e16d33fe1228c493e5dced7fa841113439e3137 --- /dev/null +++ b/game/modules/example/data/zones/dungeon/npcs.lua @@ -0,0 +1,20 @@ +-- ToME - Tales of Middle-Earth +-- 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 + +--load("/data/general/npcs/rodent.lua") diff --git a/game/modules/example/data/zones/dungeon/objects.lua b/game/modules/example/data/zones/dungeon/objects.lua new file mode 100644 index 0000000000000000000000000000000000000000..cad1c5dde4ec112098297ecc1d9d7be26841d4f6 --- /dev/null +++ b/game/modules/example/data/zones/dungeon/objects.lua @@ -0,0 +1,18 @@ +-- ToME - Tales of Middle-Earth +-- 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 diff --git a/game/modules/example/data/zones/dungeon/traps.lua b/game/modules/example/data/zones/dungeon/traps.lua new file mode 100644 index 0000000000000000000000000000000000000000..cad1c5dde4ec112098297ecc1d9d7be26841d4f6 --- /dev/null +++ b/game/modules/example/data/zones/dungeon/traps.lua @@ -0,0 +1,18 @@ +-- ToME - Tales of Middle-Earth +-- 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 diff --git a/game/modules/example/data/zones/dungeon/zone.lua b/game/modules/example/data/zones/dungeon/zone.lua new file mode 100644 index 0000000000000000000000000000000000000000..d3b7a486ac662b17b140f6eba8b1422db88bcc90 --- /dev/null +++ b/game/modules/example/data/zones/dungeon/zone.lua @@ -0,0 +1,48 @@ +-- ToME - Tales of Middle-Earth +-- 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 + +return { + name = "Old ruins", + level_range = {1, 1}, + max_level = 10, + decay = {300, 800}, + width = 50, height = 50, + persistant = "zone", + generator = { + map = { + class = "engine.generator.map.Roomer", + nb_rooms = 10, + rooms = {"simple", "pilar"}, + lite_room_chance = 100, + ['.'] = "FLOOR", + ['#'] = "WALL", + up = "UP", + down = "DOWN", + door = "DOOR", + }, + actor = { + class = "engine.generator.actor.Random", + nb_npc = {20, 30}, +-- guardian = "SHADE_OF_ANGMAR", -- The gardian is set in the static map + }, + }, + levels = + { + }, +} diff --git a/game/modules/example/dialogs/CharacterSheet.lua b/game/modules/example/dialogs/CharacterSheet.lua new file mode 100644 index 0000000000000000000000000000000000000000..7ac799ccb31ec4d92f2a8194040c7182641f84b7 --- /dev/null +++ b/game/modules/example/dialogs/CharacterSheet.lua @@ -0,0 +1,279 @@ +-- ToME - Tales of Middle-Earth +-- 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" +local DamageType = require "engine.DamageType" +local Talents = require "engine.interface.ActorTalents" + +module(..., package.seeall, class.inherit(engine.Dialog)) + +function _M:init(actor) + self.actor = actor + engine.Dialog.init(self, "Character Sheet: "..self.actor.name.." (Press 'd' to save)", 800, 400, nil, nil, nil, core.display.newFont("/data/font/VeraMono.ttf", 12)) + + self:keyCommands({ + __TEXTINPUT = function(c) + if c == 'd' or c == 'D' then + self:dump() + end + end, + }, { + ACCEPT = "EXIT", + EXIT = function() + game:unregisterDialog(self) + end, + }) +end + +function _M:drawDialog(s) + local cur_exp, max_exp = game.player.exp, game.player:getExpChart(game.player.level+1) + + local h = 0 + local w = 0 + s:drawString(self.font, "Sex: "..game.player.descriptor.sex, w, h, 0, 200, 255) h = h + self.font_h + s:drawString(self.font, "Race: "..game.player.descriptor.subrace, w, h, 0, 200, 255) h = h + self.font_h + s:drawString(self.font, "Class: "..game.player.descriptor.subclass, w, h, 0, 200, 255) h = h + self.font_h + h = h + self.font_h + s:drawColorString(self.font, "Level: #00ff00#"..game.player.level, w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("Exp: #00ff00#%2d%%"):format(100 * cur_exp / max_exp), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("Gold: #00ff00#%0.2f"):format(game.player.money), w, h, 255, 255, 255) h = h + self.font_h + + h = h + self.font_h + + s:drawColorString(self.font, ("#c00000#Life: #00ff00#%d/%d"):format(game.player.life, game.player.max_life), w, h, 255, 255, 255) h = h + self.font_h + if game.player:knowTalent(game.player.T_STAMINA_POOL) then + s:drawColorString(self.font, ("#ffcc80#Stamina: #00ff00#%d/%d"):format(game.player:getStamina(), game.player.max_stamina), w, h, 255, 255, 255) h = h + self.font_h + end + if game.player:knowTalent(game.player.T_MANA_POOL) then + s:drawColorString(self.font, ("#7fffd4#Mana: #00ff00#%d/%d"):format(game.player:getMana(), game.player.max_mana), w, h, 255, 255, 255) h = h + self.font_h + end + if game.player:knowTalent(game.player.T_SOUL_POOL) then + s:drawColorString(self.font, ("#777777#Soul: #00ff00#%d/%d"):format(game.player:getSoul(), game.player.max_soul), w, h, 255, 255, 255) h = h + self.font_h + end + if game.player:knowTalent(game.player.T_EQUILIBRIUM_POOL) then + s:drawColorString(self.font, ("#00ff74#Equi: #00ff00#%d"):format(game.player:getEquilibrium()), w, h, 255, 255, 255) h = h + self.font_h + end + + h = h + self.font_h + s:drawColorString(self.font, ("STR: #00ff00#%3d"):format(game.player:getStr()), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("DEX: #00ff00#%3d"):format(game.player:getDex()), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("MAG: #00ff00#%3d"):format(game.player:getMag()), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("WIL: #00ff00#%3d"):format(game.player:getWil()), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("CUN: #00ff00#%3d"):format(game.player:getCun()), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("CON: #00ff00#%3d"):format(game.player:getCon()), w, h, 255, 255, 255) h = h + self.font_h + + h = 0 + w = 200 + -- All weapons in main hands + if self.actor:getInven(self.actor.INVEN_MAINHAND) then + for i, o in ipairs(self.actor:getInven(self.actor.INVEN_MAINHAND)) do + if o.combat then + s:drawColorString(self.font, ("Attack(Main Hand): #00ff00#%3d"):format(game.player:combatAttack(o.combat)), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("Damage(Main Hand): #00ff00#%3d"):format(game.player:combatDamage(o.combat)), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("APR (Main Hand): #00ff00#%3d"):format(game.player:combatAPR(o.combat)), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("Crit (Main Hand): #00ff00#%3d%%"):format(game.player:combatCrit(o.combat)), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("Speed (Main Hand): #00ff00#%0.2f"):format(game.player:combatSpeed(o.combat)), w, h, 255, 255, 255) h = h + self.font_h + end + end + end + h = h + self.font_h + -- All wpeaons in off hands + -- Offhand atatcks are with a damage penality, taht can be reduced by talents + if self.actor:getInven(self.actor.INVEN_OFFHAND) then + local offmult = (mult or 1) / 2 + if self.actor:knowTalent(Talents.T_DUAL_WEAPON_TRAINING) then + offmult = (mult or 1) / (2 - (self.actor:getTalentLevel(Talents.T_DUAL_WEAPON_TRAINING) / 6)) + end + for i, o in ipairs(self.actor:getInven(self.actor.INVEN_OFFHAND)) do + if o.combat then + s:drawColorString(self.font, ("Attack (Off Hand): #00ff00#%3d"):format(game.player:combatAttack(o.combat)), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("Damage (Off Hand): #00ff00#%3d"):format(game.player:combatDamage(o.combat) * offmult), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("APR (Off Hand): #00ff00#%3d"):format(game.player:combatAPR(o.combat)), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("Crit (Off Hand): #00ff00#%3d%%"):format(game.player:combatCrit(o.combat)), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("Speed (Off Hand): #00ff00#%0.2f"):format(game.player:combatSpeed(o.combat)), w, h, 255, 255, 255) h = h + self.font_h + end + end + end + h = h + self.font_h + s:drawColorString(self.font, ("Spellpower: #00ff00#%3d"):format(game.player:combatSpellpower()), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("Spell Crit: #00ff00#%3d%%"):format(game.player:combatSpellCrit()), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("Spell Speed: #00ff00#%3d"):format(game.player:combatSpellSpeed()), w, h, 255, 255, 255) h = h + self.font_h + + h = h + self.font_h + for i, t in ipairs(DamageType.dam_def) do + if self.actor.inc_damage[DamageType[t.type]] and self.actor.inc_damage[DamageType[t.type]] ~= 0 then + s:drawColorString(self.font, ("%s damage: #00ff00#%3d%%"):format(t.name:capitalize(), self.actor.inc_damage[DamageType[t.type]]), w, h, 255, 255, 255) h = h + self.font_h + end + end + + h = 0 + w = 400 + s:drawColorString(self.font, ("Fatigue: #00ff00#%3d%%"):format(game.player.fatigue), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("Armor: #00ff00#%3d"):format(game.player:combatArmor()), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("Defence: #00ff00#%3d"):format(game.player:combatDefense()), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("Ranged Defence: #00ff00#%3d"):format(game.player:combatDefenseRanged()), w, h, 255, 255, 255) h = h + self.font_h + + h = h + self.font_h + s:drawColorString(self.font, ("Physical Resist: #00ff00#%3d"):format(game.player:combatPhysicalResist()), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("Spell Resist: #00ff00#%3d"):format(game.player:combatSpellResist()), w, h, 255, 255, 255) h = h + self.font_h + s:drawColorString(self.font, ("Mental Resist: #00ff00#%3d"):format(game.player:combatMentalResist()), w, h, 255, 255, 255) h = h + self.font_h + + h = h + self.font_h + for i, t in ipairs(DamageType.dam_def) do + if self.actor.resists[DamageType[t.type]] and self.actor.resists[DamageType[t.type]] ~= 0 then + s:drawColorString(self.font, ("%s Resist: #00ff00#%3d%%"):format(t.name:capitalize(), self.actor.resists[DamageType[t.type]]), w, h, 255, 255, 255) h = h + self.font_h + end + end + + h = 0 + w = 600 + s:drawColorString(self.font, "#LIGHT_BLUE#Current effects:", w, h, 255, 255, 255) h = h + self.font_h + for tid, act in pairs(game.player.sustain_talents) do + if act then s:drawColorString(self.font, ("#LIGHT_GREEN#%s"):format(game.player:getTalentFromId(tid).name), w, h, 255, 255, 255) h = h + self.font_h end + end + for eff_id, p in pairs(game.player.tmp) do + local e = game.player.tempeffect_def[eff_id] + if e.status == "detrimental" then + s:drawColorString(self.font, ("#LIGHT_RED#%s"):format(e.desc), w, h, 255, 255, 255) h = h + self.font_h + else + s:drawColorString(self.font, ("#LIGHT_GREEN#%s"):format(e.desc), w, h, 255, 255, 255) h = h + self.font_h + end + end + + self.changed = false +end + +function _M:dump() + fs.mkdir("/character-dumps") + local file = "/character-dumps/"..(game.player.name:gsub("[^a-zA-Z0-9_-.]", "_")).."-"..os.date("%Y%m%d-%H%M%S")..".txt" + local fff = fs.open(file, "w") + local nl = function(s) fff:write(s or "") fff:write("\n") end + local nnl = function(s) fff:write(s or "") end + + nl("Sex: "..game.player.descriptor.sex) + nl("Race: "..game.player.descriptor.subrace) + nl("Class: "..game.player.descriptor.subclass) + nl("Level: "..game.player.level) + + nl() + local cur_exp, max_exp = game.player.exp, game.player:getExpChart(game.player.level+1) + nl(("Exp: %2d%%"):format(100 * cur_exp / max_exp)) + nl(("Gold: %0.2f"):format(game.player.money)) + + nl() + nl(("Life: %d/%d"):format(game.player.life, game.player.max_life)) + if game.player:knowTalent(game.player.T_STAMINA_POOL) then + nl(("Stamina: %d/%d"):format(game.player:getStamina(), game.player.max_stamina)) + end + if game.player:knowTalent(game.player.T_MANA_POOL) then + nl(("Mana: %d/%d"):format(game.player:getMana(), game.player.max_mana)) + end + if game.player:knowTalent(game.player.T_SOUL_POOL) then + nl(("Soul: %d/%d"):format(game.player:getSoul(), game.player.max_soul)) + end + if game.player:knowTalent(game.player.T_EQUILIBRIUM_POOL) then + nl(("Equi: %d"):format(game.player:getEquilibrium())) + end + + nl() + nl(("STR: %3d"):format(game.player:getStr())) + nl(("DEX: %3d"):format(game.player:getDex())) + nl(("MAG: %3d"):format(game.player:getMag())) + nl(("WIL: %3d"):format(game.player:getWil())) + nl(("CUN: %3d"):format(game.player:getCun())) + nl(("CON: %3d"):format(game.player:getCon())) + + -- All weapons in main hands + if self.actor:getInven(self.actor.INVEN_MAINHAND) then + for i, o in ipairs(self.actor:getInven(self.actor.INVEN_MAINHAND)) do + if o.combat then + nl() + nl(("Attack(Main Hand): %3d"):format(game.player:combatAttack(o.combat))) + nl(("Damage(Main Hand): %3d"):format(game.player:combatDamage(o.combat))) + nl(("APR (Main Hand): %3d"):format(game.player:combatAPR(o.combat))) + nl(("Crit (Main Hand): %3d%%"):format(game.player:combatCrit(o.combat))) + nl(("Speed (Main Hand): %0.2f"):format(game.player:combatSpeed(o.combat))) + end + end + end + + -- All wpeaons in off hands + -- Offhand atatcks are with a damage penality, taht can be reduced by talents + if self.actor:getInven(self.actor.INVEN_OFFHAND) then + local offmult = (mult or 1) / 2 + if self.actor:knowTalent(Talents.T_DUAL_WEAPON_TRAINING) then + offmult = (mult or 1) / (2 - (self.actor:getTalentLevel(Talents.T_DUAL_WEAPON_TRAINING) / 6)) + end + for i, o in ipairs(self.actor:getInven(self.actor.INVEN_OFFHAND)) do + if o.combat then + nl() + nl(("Attack (Off Hand): %3d"):format(game.player:combatAttack(o.combat))) + nl(("Damage (Off Hand): %3d"):format(game.player:combatDamage(o.combat) * offmult)) + nl(("APR (Off Hand): %3d"):format(game.player:combatAPR(o.combat))) + nl(("Crit (Off Hand): %3d%%"):format(game.player:combatCrit(o.combat))) + nl(("Speed (Off Hand): %0.2f"):format(game.player:combatSpeed(o.combat))) + end + end + end + + nl() + nl(("Spellpower: %3d"):format(game.player:combatSpellpower())) + nl(("Spell Crit: %3d%%"):format(game.player:combatSpellCrit())) + nl(("Spell Speed: %3d"):format(game.player:combatSpellSpeed())) + + nl() + for i, t in ipairs(DamageType.dam_def) do + if self.actor.inc_damage[DamageType[t.type]] and self.actor.inc_damage[DamageType[t.type]] ~= 0 then + nl(("%s damage: %3d%%"):format(t.name:capitalize(), self.actor.inc_damage[DamageType[t.type]])) + end + end + + nl() + nl(("Fatigue: %3d%%"):format(game.player.fatigue)) + nl(("Armor: %3d"):format(game.player:combatArmor())) + nl(("Defence: %3d"):format(game.player:combatDefense())) + nl(("Ranged Defence: %3d"):format(game.player:combatDefenseRanged())) + + nl() + nl(("Physical Resist: %3d"):format(game.player:combatPhysicalResist())) + nl(("Spell Resist: %3d"):format(game.player:combatSpellResist())) + nl(("Mental Resist: %3d"):format(game.player:combatMentalResist())) + + nl() + for i, t in ipairs(DamageType.dam_def) do + if self.actor.resists[DamageType[t.type]] and self.actor.resists[DamageType[t.type]] ~= 0 then + nl(("%s Resist: %3d%%"):format(t.name:capitalize(), self.actor.resists[DamageType[t.type]])) + end + end + + nl() + local most_kill, most_kill_max = "none", 0 + local total_kill = 0 + for name, nb in pairs(game.player.all_kills or {}) do + if nb > most_kill_max then most_kill_max = nb most_kill = name end + total_kill = total_kill + nb + end + nl(("Number of NPC killed: %s"):format(total_kill)) + nl(("Most killed NPC: %s (%d)"):format(most_kill, most_kill_max)) + + fff:close() + + Dialog:simplePopup("Character dump complete", "File: "..fs.getRealPath(file)) +end diff --git a/game/modules/example/dialogs/DeathDialog.lua b/game/modules/example/dialogs/DeathDialog.lua new file mode 100644 index 0000000000000000000000000000000000000000..6d1a7030b734e4701307ba7df4126737e892ed7f --- /dev/null +++ b/game/modules/example/dialogs/DeathDialog.lua @@ -0,0 +1,178 @@ +-- ToME - Tales of Middle-Earth +-- 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" +local Savefile = require "engine.Savefile" +local Map = require "engine.Map" + +module(..., package.seeall, class.inherit(engine.Dialog)) + +function _M:init(actor) + self.actor = actor + engine.Dialog.init(self, "Death!", 500, 300) + + self:generateList() + + self.sel = 1 + self.scroll = 1 + self.max = math.floor((self.ih - 45) / self.font_h) - 1 + + self:keyCommands(nil, { + MOVE_UP = function() self.sel = util.boundWrap(self.sel - 1, 1, #self.list) self.scroll = util.scroll(self.sel, self.scroll, self.max) self.changed = true end, + MOVE_DOWN = function() self.sel = util.boundWrap(self.sel + 1, 1, #self.list) self.scroll = util.scroll(self.sel, self.scroll, self.max) self.changed = true end, + ACCEPT = function() self:use() end, + }) + self:mouseZones{ + { x=2, y=10 + self.font:lineSkip()*6, w=350, h=self.font_h*self.max, fct=function(button, x, y, xrel, yrel, tx, ty) + self.changed = true + self.sel = util.bound(self.scroll + math.floor(ty / self.font_h), 1, #self.list) + if button == "left" then self:use() + end + self.changed = true + end }, + } +end + +--- Clean the actor from debuffs/buffs +function _M:cleanActor() + local effs = {} + + -- Go through all spell effects + for eff_id, p in pairs(self.actor.tmp) do + + local e = self.actor.tempeffect_def[eff_id] + effs[#effs+1] = {"effect", eff_id} + end + + -- Go through all sustained spells + for tid, act in pairs(self.actor.sustain_talents) do + if act then + effs[#effs+1] = {"talent", tid} + end + end + + while #effs > 0 do + local eff = rng.tableRemove(effs) + + if eff[1] == "effect" then + self.actor:removeEffect(eff[2]) + else + local old = self.actor.energy.value + self.actor:useTalent(eff[2]) + -- Prevent using energy + self.actor.energy.value = old + end + end +end + +--- Restore ressources +function _M:restoreRessources() + self.actor.life = self.actor.max_life + self.actor.mana = self.actor.max_mana + self.actor.stamina = self.actor.max_stamina + self.actor.equilibrium = 0 + self.actor.air = self.actor.max_air + + self.actor.energy.value = game.energy_to_act +end + +--- Basic resurection +function _M:resurrectBasic() + self.actor.dead = false + self.actor.died = (self.actor.died or 0) + 1 + + local x, y = util.findFreeGrid(self.actor.x, self.actor.y, 20, true, {[Map.ACTOR]=true}) + if not x then x, y = self.actor.x, self.actor.y end + self.actor.x, self.actor.y = nil, nil + + self.actor:move(x, y, true) + game.level:addEntity(self.actor) + game:unregisterDialog(self) + game.level.map:redisplay() + + world:gainAchievement("UNSTOPPABLE", self.actor) +end + +function _M:use() + if not self.list[self.sel] then return end + local act = self.list[self.sel].action + + if act == "exit" then + local save = Savefile.new(game.save_name) + save:delete() + save:close() + util.showMainMenu() + elseif act == "dump" then + game:registerDialog(require("mod.dialogs.CharacterSheet").new(self.actor)) + elseif act == "cheat" then + game.logPlayer(self.actor, "#LIGHT_BLUE#You resurrect! CHEATER !") + + self:cleanActor() + self:restoreRessources() + self:resurrectBasic() + elseif act == "blood_life" then + self.actor.blood_life = false + game.logPlayer(self.actor, "#LIGHT_RED#The Blood of Life rushes through your dead body. You come back to life!") + + self:cleanActor() + self:restoreRessources() + self:resurrectBasic() + elseif act == "skeleton" then + self.actor:attr("re-assembled", 1) + game.logPlayer(self.actor, "#YELLOW#Your bones magically come back together. You are once more able to dish pain to your foes!") + + self:cleanActor() + self:restoreRessources() + self:resurrectBasic() + end +end + +function _M:generateList() + local list = {} + + if config.settings.tome.cheat then list[#list+1] = {name="Resurrect by cheating", action="cheat"} end + if self.actor:attr("blood_life") and not self.actor:attr("undead") then list[#list+1] = {name="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="Re-assemble your bones ad resurrect (Skeleton ability)", action="skeleton"} end + + list[#list+1] = {name="Character dump", action="dump"} + list[#list+1] = {name="Exit to main menu", action="exit"} + + self.list = list +end + +function _M:drawDialog(s) + local help = ([[You have #LIGHT_RED#died#LAST#! +Death in T.o.M.E. 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 again to survive in the wilds! +]]):splitLines(self.iw - 10, self.font) + + local h = 2 + local r, g, b + for i = 1, #help do + r, g, b = s:drawColorString(self.font, help[i], 5, h, r, g, b) + h = h + self.font:lineSkip() + end + h = h + self.font:lineSkip() + + self:drawWBorder(s, 2, h - 0.5 * self.font:lineSkip(), self.iw - 4) + + self:drawSelectionList(s, 2, h, self.font_h, self.list, self.sel, "name") + self.changed = false +end diff --git a/game/modules/example/dialogs/Quit.lua b/game/modules/example/dialogs/Quit.lua new file mode 100644 index 0000000000000000000000000000000000000000..84b0303f1cf3c372deeec5058afa944a4f98943f --- /dev/null +++ b/game/modules/example/dialogs/Quit.lua @@ -0,0 +1,45 @@ +-- ToME - Tales of Middle-Earth +-- 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" +local Savefile = require "engine.Savefile" + +module(..., package.seeall, class.inherit(engine.Dialog)) + +function _M:init() + engine.Dialog.init(self, "Realy exit ToME?", 300, 100) + self:keyCommands({ + __DEFAULT = function() + game:unregisterDialog(self) + game.quit_dialog = false + end, + }, { + ACCEPT = function() + local save = Savefile.new(game.save_name) + save:saveGame(game) + save:close() + util.showMainMenu() + end, + }) +end + +function _M:drawDialog(s, w, h) + s:drawColorStringCentered(self.font, "Press enter to quit, any other keys to stay", 2, 2, self.iw - 2, self.ih - 2) +end diff --git a/game/modules/example/init.lua b/game/modules/example/init.lua new file mode 100644 index 0000000000000000000000000000000000000000..a2c541f3a311fc936d5e81479efc1c766d009583 --- /dev/null +++ b/game/modules/example/init.lua @@ -0,0 +1,30 @@ +-- ToME - Tales of Middle-Earth +-- 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 + +name = "Example Module" +long_name = "Example Module for T-Engine4" +short_name = "example" +author = { "DarkGod", "darkgod@te4.org" } +homepage = "http://te4.org/modules:example" +version = {1,0,0} +engine = {1,0,0} +description = [[ +This is *NOT* a game, just an example/template to make your own using the T-Engine4. +]] +starter = "mod.load" diff --git a/game/modules/example/load.lua b/game/modules/example/load.lua new file mode 100644 index 0000000000000000000000000000000000000000..8d85cb226c71438c3dca6962d912887991d04575 --- /dev/null +++ b/game/modules/example/load.lua @@ -0,0 +1,53 @@ +-- ToME - Tales of Middle-Earth +-- 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 + +-- This file loads the game module, and loads data +local KeyBind = require "engine.KeyBind" +local DamageType = require "engine.DamageType" +local ActorStats = require "engine.interface.ActorStats" +local ActorResource = require "engine.interface.ActorResource" +local ActorTalents = require "engine.interface.ActorTalents" +local ActorAI = require "engine.interface.ActorAI" +local ActorLevel = require "engine.interface.ActorLevel" +local Birther = require "engine.Birther" + +-- Usefull keybinds +KeyBind:load("move,hotkeys,inventory,actions,debug") + +-- Damage types +DamageType:loadDefinition("/data/damage_types.lua") + +-- Talents +ActorTalents:loadDefinition("/data/talents.lua") + +-- Actor resources +ActorResource:defineResource("Power", "power", nil, "power_regen", "Power represent your ability to use special talents.") + +-- Actor stats +ActorStats:defineStat("Strength", "str", 10, 1, 100, "Strength defines your character's ability to apply physical force. It increases your melee damage, damage with heavy weapons, your chance to resist physical effects, and carrying capacity.") +ActorStats:defineStat("Dexterity", "dex", 10, 1, 100, "Dexterity defines your character's ability to be agile and alert. It increases your chance to hit, your ability to avoid attacks and your damage with light weapons.") +ActorStats:defineStat("Constitution", "con", 10, 1, 100, "Constitution defines your character's ability to withstand and resist damage. It increases your maximun life and physical resistance.") + +-- Actor AIs +ActorAI:loadDefinition("/engine/ai/") + +-- Birther descriptor +Birther:loadDefinition("/data/birth/descriptors.lua") + +return {require "mod.class.Game" }