Skip to content
Snippets Groups Projects
Commit 90cc7295 authored by dg's avatar dg
Browse files

All classes now have a cloneFull() method that will clone themselves and all...

All classes now have a cloneFull() method that will clone themselves and all their subobjects without duplication


git-svn-id: http://svn.net-core.org/repos/t-engine4@1111 51575b47-30f0-44d4-a5cc-537603b46e54
parent 09e4935a
No related branches found
No related tags found
No related merge requests found
......@@ -54,6 +54,7 @@ function _M:loaded()
self.mouse:setCurrent()
self.__threads = self.__threads or {}
self.__coroutines = self.__coroutines or {}
end
--- Defines the default fields to be saved by the savefile code
......@@ -113,6 +114,25 @@ end
--- This is the "main game loop", do something here
function _M:tick()
local stop = {}
local id, co = next(self.__coroutines)
while id do
local ok, err = coroutine.resume(co)
if not ok then
print(debug.traceback(co))
print("[COROUTINE] error", err)
end
if coroutine.status(co) == "dead" then
stop[#stop+1] = id
end
id, co = next(self.__coroutines, id)
end
if #stop > 0 then
for i = 1, #stop do
self.__coroutines[stop[i]] = nil
print("[COROUTINE] dead", stop[i])
end
end
end
--- Called when a zone leaves a level
......@@ -213,6 +233,35 @@ end
function _M:saveGame()
end
--- Add a coroutine to the pool
-- Coroutines registered will be run each game tick
function _M:registerCoroutine(id, co)
print("[COROUTINE] registering", id, co)
self.__coroutines[id] = co
end
--- Get the coroutine corresponding to the id
function _M:getCoroutine(id)
return self.__coroutines[id]
end
--- Ask a registered coroutine to cancel
-- The coroutine must accept a "cancel" action
function _M:cancelCoroutine(id)
local co = self.__coroutines[id]
if not co then return end
local ok, err = coroutine.resume(co, "cancel")
if not ok then
print(debug.traceback(co))
print("[COROUTINE] error", err)
end
if coroutine.status(co) == "dead" then
self.__coroutines[id] = nil
else
error("Told coroutine "..id.." to cancel, but it is not dead!")
end
end
--- Save a thread into the thread pool
-- Threads will be auto join'ed when the module exits or when it can<br/>
-- ALL THREADS registered *MUST* return true when they exit
......
......@@ -63,7 +63,7 @@ function _M:tick()
for i = 1, #arr do
e = arr[i]
if e and e.act and e.energy then
-- print("<ENERGY", e.name, e.uid, "::", e.energy.value, game.paused, "::", e.player)
-- print("<ENERGY", e.name, e.uid, "::", e.energy.value, self.paused, "::", e.player)
if e.energy.value < self.energy_to_act then
e.energy.value = (e.energy.value or 0) + self.energy_per_tick * (e.energy.mod or 1)
end
......@@ -71,7 +71,7 @@ function _M:tick()
e.energy.used = false
e:act(self)
end
-- print(">ENERGY", e.name, e.uid, "::", e.energy.value, game.paused, "::", e.player)
-- print(">ENERGY", e.name, e.uid, "::", e.energy.value, self.paused, "::", e.player)
end
end
end
......
......@@ -37,7 +37,7 @@ end
function _M:tick()
if self.paused then
-- Auto unpause if the player has no energy to act
if game:getPlayer() and not game:getPlayer():enoughEnergy() then game.paused = false end
if self:getPlayer() and not self:getPlayer():enoughEnergy() then self.paused = false end
-- If we are paused do not get energy, but still process frames if needed
engine.Game.tick(self)
......
......@@ -92,25 +92,36 @@ function _M:saveObject(obj, zip)
local tbl = table.remove(self.process)
self.tables[tbl] = self:getFileName(tbl)
zip:add(self:getFileName(tbl), tbl:save())
-- If run from a coroutine, we pause every object
if coroutine.running() then
local coret = coroutine.yield()
if coret and type(coret) == "string" and coret == "cancel" then
print("[SAVE] abording")
break
end
end
end
return self.tables[obj]
end
--- Save the given world
function _M:saveWorld(world)
function _M:saveWorld(world, no_dialog)
collectgarbage("collect")
fs.mkdir(self.save_dir)
local popup = Dialog:simplePopup("Saving world", "Please wait while saving the world...")
popup.__showup = nil
local popup
if not no_dialog then
popup = Dialog:simplePopup("Saving world", "Please wait while saving the world...")
popup.__showup = nil
end
core.display.forceRedraw()
local zip = fs.zipOpen(self.save_dir.."world.teaw")
self:saveObject(world, zip)
zip:close()
game:unregisterDialog(popup)
if not no_dialog then game:unregisterDialog(popup) end
end
--- Save the given birth descriptors, used for quick start
......@@ -142,13 +153,16 @@ function _M:loadQuickBirth()
end
--- Save the given game
function _M:saveGame(game)
function _M:saveGame(game, no_dialog)
collectgarbage("collect")
fs.mkdir(self.save_dir)
local popup = Dialog:simplePopup("Saving game", "Please wait while saving the game...")
popup.__showup = nil
local popup
if not no_dialog then
popup = Dialog:simplePopup("Saving game", "Please wait while saving the game...")
popup.__showup = nil
end
core.display.forceRedraw()
local zip = fs.zipOpen(self.save_dir.."game.teag")
......@@ -162,37 +176,43 @@ function _M:saveGame(game)
f:write(("description = %q\n"):format(desc.description))
f:close()
game:unregisterDialog(popup)
if not no_dialog then game:unregisterDialog(popup) end
end
--- Save a zone
function _M:saveZone(zone)
function _M:saveZone(zone, no_dialog)
fs.mkdir(self.save_dir)
local popup = Dialog:simplePopup("Saving zone", "Please wait while saving the zone...")
popup.__showup = nil
local popup
if not no_dialog then
popup = Dialog:simplePopup("Saving zone", "Please wait while saving the zone...")
popup.__showup = nil
end
core.display.forceRedraw()
local zip = fs.zipOpen(self.save_dir..("zone-%s.teaz"):format(zone.short_name))
self:saveObject(zone, zip)
zip:close()
game:unregisterDialog(popup)
if not no_dialog then game:unregisterDialog(popup) end
end
--- Save a level
function _M:saveLevel(level)
function _M:saveLevel(level, no_dialog)
fs.mkdir(self.save_dir)
local popup = Dialog:simplePopup("Saving level", "Please wait while saving the level...")
popup.__showup = nil
local popup
if not no_dialog then
popup = Dialog:simplePopup("Saving level", "Please wait while saving the level...")
popup.__showup = nil
end
core.display.forceRedraw()
local zip = fs.zipOpen(self.save_dir..("level-%s-%d.teal"):format(level.data.short_name, level.level))
self:saveObject(level, zip)
zip:close()
game:unregisterDialog(popup)
if not no_dialog then game:unregisterDialog(popup) end
end
local function resolveSelf(o, base, allow_object)
......
......@@ -108,6 +108,34 @@ function _M:clone(t)
return n
end
local function clonerecursfull(clonetable, d)
local n = {}
clonetable[d] = n
for k, e in pairs(d) do
local nk, ne = k, e
if clonetable[k] then nk = clonetable[k]
elseif type(k) == "table" then nk = clonerecursfull(clonetable, k)
end
if clonetable[e] then ne = clonetable[e]
elseif type(e) == "table" then ne = clonerecursfull(clonetable, e)
end
n[nk] = ne
end
setmetatable(n, getmetatable(d))
return n
end
--- Clones the object, all subobjects without cloning twice a subobject
function _M:cloneFull()
local clonetable = {}
local n = clonerecursfull(clonetable, self)
return n
end
--- Replaces the object with an other, by copying (not deeply)
function _M:replaceWith(t)
-- Delete fields
......
......@@ -74,14 +74,14 @@ function _M:project(t, x, y, damtype, dam, particles)
-- Ok if we are at the end reset lx and ly for the next code
if not lx and not ly then lx, ly = x, y end
if typ.ball then
if typ.ball and typ.ball > 0 then
core.fov.calc_circle(lx, ly, typ.ball, function(_, px, py)
-- Deal damage: ball
addGrid(px, py)
if not typ.no_restrict and game.level.map:checkEntity(px, py, Map.TERRAIN, "block_move") then return true end
end, function()end, nil)
addGrid(lx, ly)
elseif typ.cone then
elseif typ.cone and typ.cone > 0 then
core.fov.calc_beam(lx, ly, typ.cone, initial_dir, typ.cone_angle, function(_, px, py)
-- Deal damage: cone
addGrid(px, py)
......@@ -247,14 +247,14 @@ function _M:projectDoStop(typ, tg, damtype, dam, particles, lx, ly, tmp)
grids[x][y] = true
end
if typ.ball then
if typ.ball and typ.ball > 0 then
core.fov.calc_circle(lx, ly, typ.ball, function(_, px, py)
-- Deal damage: ball
addGrid(px, py)
if not typ.no_restrict and game.level.map:checkEntity(px, py, Map.TERRAIN, "block_move") then return true end
end, function()end, nil)
addGrid(lx, ly)
elseif typ.cone then
elseif typ.cone and typ.cone > 0 then
local initial_dir = lx and util.getDir(lx, ly, x, y) or 5
core.fov.calc_beam(lx, ly, typ.cone, initial_dir, typ.cone_angle, function(_, px, py)
-- Deal damage: cone
......
......@@ -376,7 +376,7 @@ function _M:tick()
-- (since display is on a set FPS while tick() ticks as much as possible
-- engine.GameEnergyBased.tick(self)
end
if game.paused then return true end
if self.paused and not self.saving then return true end
end
--- Called every game turns
......
......@@ -109,6 +109,8 @@ function _M:display()
self.surface:drawColorStringBlended(self.font, ("#904010#Vim: #ffffff#%d/%d"):format(player:getVim(), player.max_vim), 0, h, 255, 255, 255) h = h + self.font_h
end
if game.saving then h = h + self.font_h self.surface:drawColorStringBlended(self.font, "#YELLOW#Saving...", 0, h, 255, 255, 255) h = h + self.font_h end
h = h + self.font_h
for tid, act in pairs(player.sustain_talents) do
if act then self.surface:drawColorStringBlended(self.font, ("#LIGHT_GREEN#%s"):format(player:getTalentFromId(tid).name), 0, h, 255, 255, 255) h = h + self.font_h end
......
......@@ -33,9 +33,9 @@ function _M:run()
end
--- Requests the world to save
function _M:saveWorld()
function _M:saveWorld(no_dialog)
local save = Savefile.new("")
save:saveWorld(self)
save:saveWorld(self, no_dialog)
save:close()
game.log("Saved world.")
end
......
-- 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
newTalent{
name = "Virulent Disease",
type = {"corruption/plague", 1},
require = corrs_req1,
points = 5,
vim = 8,
cooldown = 3,
range = function(self, t) return 5 + math.floor(self:getTalentLevel(t) * 1.3) end,
action = function(self, t)
local tg = {type="bolt", range=self:getTalentRange(t)}
local x, y = self:getTarget(tg)
if not x or not y then return nil end
local diseases = {{self.EFF_WEAKNESS_DISEASE, "str"}, {self.EFF_ROTTING_DISEASE,"con"}, {self.EFF_DECREPITUDE_DISEASE,"dex"}}
local disease = rng.table(diseases)
-- Try to rot !
self:project(tg, x, y, function(px, py)
local target = game.level.map(px, py, engine.Map.ACTOR)
if not target then return end
if target:checkHit(self:combatSpellpower(), target:combatSpellResist(), 0, 95, 12 - self:getTalentLevel(t)) and target:canBe("disease") then
target:setEffect(disease[1], 6, {src=self, dam=self:combatTalentSpellDamage(t, 5, 45), [disease[2]]=self:combatTalentSpellDamage(t, 5, 25)})
else
game.logSeen(target, "%s resists the disease!", target.name:capitalize())
end
game.level.map:particleEmitter(px, py, 1, "slime")
end)
game:playSoundNear(self, "talents/slime")
return true
end,
info = function(self, t)
return ([[Fires a bolt of pure filth, diseasing your target with a random disease doing %0.2f blight damage per turns for 6 turns and reducing one of its physical stats (strength, constitution, dexterity) by %d.
The effect will increase with your Magic stat.]]):
format(self:combatTalentSpellDamage(t, 5, 45), self:combatTalentSpellDamage(t, 5, 25))
end,
}
newTalent{
name = "Cyst Burst",
type = {"corruption/plague", 2},
require = corrs_req2,
points = 5,
vim = 18,
cooldown = 9,
range = 15,
action = function(self, t)
local tg = {type="bolt", range=self:getTalentRange(t)}
local x, y = self:getTarget(tg)
if not x or not y then return nil end
local dam = self:combatTalentSpellDamage(t, 15, 85)
local diseases = {}
-- Try to rot !
local source = nil
self:project(tg, x, y, function(px, py)
local target = game.level.map(px, py, engine.Map.ACTOR)
if not target then return end
for eff_id, p in pairs(target.tmp) do
local e = target.tempeffect_def[eff_id]
if e.type == "disease" then
diseases[#diseases+1] = {id=eff_id, params=p}
end
end
if #diseases > 0 then
DamageType:get(DamageType.BLIGHT).projector(self, px, py, DamageType.BLIGHT, dam * #diseases)
game.level.map:particleEmitter(px, py, 1, "slime")
end
source = target
end)
if #diseases > 0 then
self:project({type="ball", radius=1, range=self:getTalentRange(t)}, x, y, function(px, py)
local target = game.level.map(px, py, engine.Map.ACTOR)
if not target or target == source or target == self then return end
local disease = rng.table(diseases)
target:setEffect(disease.id, 6, {src=self, dam=disease.params.dam, str=disease.params.str, dex=disease.params.dex, con=disease.params.con})
game.level.map:particleEmitter(px, py, 1, "slime")
end)
end
game:playSoundNear(self, "talents/slime")
return true
end,
info = function(self, t)
return ([[Make your target's diseases burst, doing %0.2f blight damage for each diseases it is infected with.
This will also spread the diseases to any nearby foes in a radius of 1.
The damage will increase with your Magic stat.]]):
format(self:combatTalentSpellDamage(t, 15, 85))
end,
}
newTalent{
name = "Catalepsy",
type = {"corruption/plague", 3},
require = corrs_req3,
points = 5,
vim = 35,
cooldown = 15,
range = 10,
action = function(self, t)
local tg = {type="ball", range=self:getTalentRange(t), radius=2}
local x, y = self:getTarget(tg)
if not x or not y then return nil end
local dur = math.floor(2 + self:getTalentLevel(t) / 2)
local source = nil
self:project(tg, x, y, function(px, py)
local target = game.level.map(px, py, engine.Map.ACTOR)
if not target then return end
-- List all diseas
local diseases = {}
for eff_id, p in pairs(target.tmp) do
local e = target.tempeffect_def[eff_id]
if e.type == "disease" then
diseases[#diseases+1] = {id=eff_id, params=p}
end
end
-- Make them EXPLODE !!!
for i, d in ipairs(diseases) do
target:removeEffect(d.id)
DamageType:get(DamageType.BLIGHT).projector(self, px, py, DamageType.BLIGHT, d.params.dam * d.params.dur)
end
if #diseases > 0 and target:checkHit(self:combatSpellpower(), target:combatSpellResist(), 0, 95, 8) and target:canBe("stun") then
target:setEffect(target.EFF_STUNNED, dur, {})
else
game.logSeen(target, "%s resists the stun!", target.name:capitalize())
end
game.level.map:particleEmitter(px, py, 1, "slime")
end)
game:playSoundNear(self, "talents/slime")
return true
end,
info = function(self, t)
return ([[All your foes infected with a disease enter a catalepsy, stunning them for %d turns and dealing all the diseases remaining damage instantly.]]):
format(math.floor(2 + self:getTalentLevel(t) / 2))
end,
}
newTalent{
name = "Epidemic",
type = {"corruption/plague", 4},
require = corrs_req4,
points = 5,
vim = 20,
cooldown = 13,
range = 10,
do_spread = function(self, t, carrier)
-- List all diseas
local diseases = {}
for eff_id, p in pairs(carrier.tmp) do
local e = carrier.tempeffect_def[eff_id]
if e.type == "disease" then
diseases[#diseases+1] = {id=eff_id, params=p}
end
end
self:project({type="ball", radius=2}, carrier.x, carrier.y, function(px, py)
local target = game.level.map(px, py, engine.Map.ACTOR)
if not target or target == carrier or target == self then return end
local disease = rng.table(diseases)
target:setEffect(disease.id, 6, {src=self, dam=disease.params.dam, str=disease.params.str, dex=disease.params.dex, con=disease.params.con})
game.level.map:particleEmitter(px, py, 1, "slime")
end)
end,
action = function(self, t)
local tg = {type="bolt", range=self:getTalentRange(t)}
local x, y = self:getTarget(tg)
if not x or not y then return nil end
-- Try to rot !
self:project(tg, x, y, function(px, py)
local target = game.level.map(px, py, engine.Map.ACTOR)
if not target then return end
if target:checkHit(self:combatSpellpower(), target:combatSpellResist(), 0, 95, 12 - self:getTalentLevel(t)) and target:canBe("disease") then
target:setEffect(self.EFF_EPIDEMIC, 6, {src=self, dam=self:combatTalentSpellDamage(t, 15, 50)})
else
game.logSeen(target, "%s resists the disease!", target.name:capitalize())
end
game.level.map:particleEmitter(px, py, 1, "slime")
end)
game:playSoundNear(self, "talents/slime")
return true
end,
info = function(self, t)
return ([[Infects the target with a very contagious disease doing %0.2f damage per turn for 6 turns.
If any blight damage from non-diseases hit the target the epidemic will activate and spread diseases to nearby targets.]]):
format(self:combatTalentSpellDamage(t, 15, 50))
end,
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment