[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Enigma-devel] New level: "The Tower"
From: |
Hubai Tamas |
Subject: |
[Enigma-devel] New level: "The Tower" |
Date: |
Fri, 06 Apr 2007 02:26:49 +0200 |
User-agent: |
Thunderbird 1.5.0.9 (X11/20060911) |
Hi,
I'm sending a new level attached [ht01_1.xml]. It's called "The Tower"
and is co-authored by Andrew (aka HuB34) and me, featuring a three
dimensional sokoban challenge. It's quite a difficult one, so don't
despair. And try the easy mode first ;).
Also, I'll try to share some of my experiences concerning Enigma's lua
interface, which this level uses quite intensively.
First off, I'm quite impressed with the multitude of options provided
and the level of scriptability. It's nice to see that quite complicated
concepts were rather easy to code. For some examples, check the level's
source.
[Bug: Trigger placed under an actor is initialised incorrectly]
The erroneous function is the Trigger::Trigger() constructor in
src/items.cc line 1353 (today's svn) which sets the actor count to 0
regardless of actors already present.
[Bug: Intermittent segfault in the lua garbage collector]
Sometimes when playing the level in hard difficulty, Enigma exits with a
segmentation fault (usually only after 15-20 minutes, which makes
debugging it quite frustrating). The problem seems to occur in both the
Linux and Windows build.
According to gdb, the SIGSEGV signal is received at line 181 of
lib-src/lua/lgc.c, in function traversetable(). Checking the relevant
variables revealed that h->sizearray was set to -1 upon function entry,
which seems incorrect. I was unable, however, to determine where this
bogus value is set (but it is neither of the assignments at ltable.c
line 268 and line 307, which I've checked).
I've attached a gdb backtrace [enigma.bt].
Below are some features I was missing. Note that I've worked around
these when creating the level, so don't add them just for me, only if
they are of value to others too.
[Feature request: Export actor functions to lua]
There are some functions in actor.cc that would be useful in lua
scripts. For example, 'get_actorinfo' to get high-resolution location
(currently only the integral grid coordinates are available) and speed,
'warp' to reliably move actors to a new position (vortex and wormhole
only works if the actor is at the center of a cell), 'on_respawn' and
'on_collision' handlers and similar functions. It might also be useful
to change the current velocity of an actor, to add a constant force for
only a single actor and to remove an actor (if I use 'shatter', it will
respawn).
[Feature request: NLS with variable arguments]
Sometimes you have a message like "You can respawn %d more times". As of
the current version, you need to know all possible values for %d and add
a string for each. It would be beneficial either to add such arguments
to document or to add a gettext()-like function to facilitate
constructing the message in the lua code. In the latter case, a
translate="false" option for documents would be nice, too (to avoid
double translation).
[Feature request: Arguments for trigger callback]
When I have multiple triggers with similar functionality, something like
action="callback", target="trigfn(27)" would be nice.
Thanks, happy testing and have a nice day
Thomas
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<el:level xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://enigma-game.org/schema/level/1 level.xsd"
xmlns:el="http://enigma-game.org/schema/level/1">
<el:protected>
<el:info el:type="level">
<el:identity el:title="The Tower" el:subtitle="Sokoban in three
dimensions" el:id="ht01"/>
<el:version el:score="1" el:release="1" el:revision="0"
el:status="released"/>
<el:author el:name="Thomas & Andrew Hubai" el:email="address@hidden"
el:homepage=""/>
<el:copyright>Copyright © 2006, 2007 Thomas & Andrew
Hubai</el:copyright>
<el:license el:type="GPL v2.0 or above" el:open="true"/>
<el:compatibility el:enigma="1.00">
</el:compatibility>
<el:modes el:easy="true" el:single="true" el:network="false"/>
<el:comments>
<el:credits el:showinfo="true" el:showstart="false">3d engine by
Thomas, sokoban challenge by Andrew</el:credits>
</el:comments>
<el:score el:easy="9:51" el:difficult="26:30"/>
<!-- authors' push count: 196 (in both difficulty modes) -->
</el:info>
<el:luamain><![CDATA[
-- -- -- -- -- -- -- -- -- -- -- --
-- puzzle description begins here
-- -- -- -- -- -- -- -- -- -- -- --
-- if you would like to change the puzzle, you only need
-- to edit this region (between the markers)
function puzzlesetup()
-- set up the puzzle space
--
-- char 'floor' stone
-- ---- ------- -----
-- . normal empty
-- a hole empty
-- t trigger empty
-- b normal wood
-- s normal block
-- A hole wood
-- B trigger wood
-- d normal door
puzzles={}
puzzles[1]={
"t.bbtstB..b",
"Bb.t.s..b.t",
"t.b.sts.b.t",
"sssstdtssss",
"t.btstst.B.",
"..b..stb.Bb",
"bB..ts....."
}
puzzles[2]={
".s.s..b.bs.",
".s..b.s..s.",
".s.s.ab..t.",
".s.basa.ds.",
".ss.sas.s..",
"b.tss.....s",
"...s...s..."
}
puzzles[3]={
".t.s..sts..",
".s..t.ss..s",
"..st..s..s.",
"dbtsbss.s..",
"a.s...s...t",
"ss..s.ss.s.",
"....s.Bt.t."
}
puzzles[4]={
".a...s.a.s.",
"s.sb..sbs..",
"..sasss.s.s",
"..st..d..B.",
"s.sssss.s.a",
"..B...sssss",
".s.ss.A...."
}
puzzles[5]={
".s..tsas...",
"..bb.sBssss",
".b.a.s.....",
".ss..ts.sss",
".s.ssssst.a",
".s........d",
".s...sss..."
}
puzzles[6]={
".s.s.s.sba.",
".btat.tst..",
".s.sasbssa.",
".btbtbtb.s.",
".s.s.s.d.s.",
".btbtbta.s.",
".b.b.b.b.sa"
}
-- set up the triggers
-- triggers[num]={x,y,z}
-- x,y are coordinates relative to the top left corner of the puzzle area
-- z is the floor number
-- note: the coordinates described here *must* correspond to trigger
tiles ("t", "B") in the puzzles[] array
triggers={}
t1=0
triggers[t1+1]={1,1,1}
triggers[t1+2]={1,2,1}
triggers[t1+3]={1,3,1}
triggers[t1+4]={1,5,1}
triggers[t1+5]={2,7,1}
triggers[t1+6]={4,2,1}
triggers[t1+7]={4,5,1}
triggers[t1+8]={5,1,1}
triggers[t1+9]={5,4,1}
triggers[t1+10]={5,7,1}
triggers[t1+11]={6,3,1}
triggers[t1+12]={6,5,1}
triggers[t1+13]={7,1,1}
triggers[t1+14]={7,4,1}
triggers[t1+15]={7,6,1}
triggers[t1+16]={8,1,1}
triggers[t1+17]={8,5,1}
triggers[t1+18]={10,5,1}
triggers[t1+19]={10,6,1}
triggers[t1+20]={11,2,1}
triggers[t1+21]={11,3,1}
t2=t1+21
triggers[t2+1]={3,6,2}
triggers[t2+2]={10,3,2}
t3=t2+2
triggers[t3+1]={2,1,3}
triggers[t3+2]={3,4,3}
triggers[t3+3]={4,3,3}
triggers[t3+4]={5,2,3}
triggers[t3+5]={7,7,3}
triggers[t3+6]={8,1,3}
triggers[t3+7]={8,7,3}
triggers[t3+8]={10,7,3}
triggers[t3+9]={11,5,3}
t4=t3+9
triggers[t4+1]={3,6,4}
triggers[t4+2]={4,4,4}
triggers[t4+3]={10,4,4}
t5=t4+3
triggers[t5+1]={5,1,5}
triggers[t5+2]={6,4,5}
triggers[t5+3]={7,2,5}
triggers[t5+4]={9,5,5}
t6=t5+4
triggers[t6+1]={3,2,6}
triggers[t6+2]={3,4,6}
triggers[t6+3]={3,6,6}
triggers[t6+4]={5,2,6}
triggers[t6+5]={5,4,6}
triggers[t6+6]={5,6,6}
triggers[t6+7]={7,2,6}
triggers[t6+8]={7,4,6}
triggers[t6+9]={7,6,6}
triggers[t6+10]={9,2,6}
tsum=t6+10
-- set up the doors
-- doors[num]={x,y,z,t}
-- t is the list of triggers required to open the door
-- (indices from the triggers[] array)
-- scd(f,n,t) -> staircase door number n (clockwise) on floor f
-- od(f,t) -> oxyd door on floor f
-- ed(t) -> extra door (visible on all floors)
-- note: the coordinates mentioned here *must* correspond to door stones
("d") in the puzzles[] array
doors={
{6,4,1,{}},
{9,4,2,{t5+2}},
{1,4,3,{t6+10}},
{7,4,4,{t4+3}},
{11,6,5,{t6+3}},
{8,5,6,{t6+1,t6+2,t6+4,t6+5,t6+7,t6+8}},
scd(1,1,{t1+9}),
scd(1,2,{t1+11}),
scd(1,3,{t1+14}),
scd(1,4,{t1+2}),
scd(1,5,{t1+16}),
scd(1,6,{t1+12}),
scd(2,1,{t2+1}),
scd(2,2,{t2+2}),
scd(2,3,{t1+19}),
scd(2,4,{t5+3}),
scd(2,5,{t1+5}),
scd(2,6,{t3+1}),
scd(3,1,{t3+3}),
scd(3,2,{t1+3}),
scd(3,3,{t3+7}),
scd(3,4,{t3+9}),
scd(3,5,{t1+20}),
scd(3,6,{t1+1}),
scd(4,1,{t3+1,t4+1}),
scd(4,2,{t3+5}),
scd(4,3,{t1+21}),
scd(4,4,{t6+7}),
scd(4,5,{t1+7}),
scd(4,6,{t1+10}),
scd(5,1,{t6+6,t6+8,t6+9}),
scd(5,2,{t1+6}),
scd(5,3,{t5+2}),
scd(5,4,{t3+8}),
scd(5,5,{t1+4}),
scd(5,6,{t6+1,t6+2,t6+4}),
scd(6,1,{t1+8}),
scd(6,2,{t1+17}),
scd(6,3,range(t6+1,t6+9)),
scd(6,4,{t3+1}),
scd(6,5,{t1+18}),
scd(6,6,{t6+5}),
od(1,range(t1+1,t2)),
od(2,range(1,tsum)),
od(3,{t3+2}),
od(4,{t4+2}),
od(5,{t5+1}),
od(6,{t6+10})
}
-- triggers needed to finish the game
completion = range(1,tsum) -- all
end
-- -- -- -- -- -- -- -- -- -- -- --
-- puzzle description ends here
-- -- -- -- -- -- -- -- -- -- -- --
-- offsets for the base map
ox=1
oy=0
-- offsets for the puzzle
px=ox+2
py=oy+2
-- starting floor
-- (if you die, you respawn here)
startfloor=1
floor=startfloor
-- shorthand for difficult mode
difficult = (options.Difficulty == 2)
-- permutation of oxyd doors wrt. floors
fharr={4,2,5,6,1,3}
-- relative positions of staircase doors to the top left
-- corner of the puzzle map (that is, (px,py))
-- (used in scd(), needs to be synced with drawbase())
scdarr={
{{3,0},{11,0},{12,5},{12,7},{9,8},{1,8}},
{{1,0},{9,0},{12,1},{12,3},{11,8},{3,8}},
{{3,0},{11,0},{12,7},{9,8},{1,8},{0,1}},
{{1,0},{9,0},{12,1},{11,8},{3,8},{0,7}}
}
-- returns the relative coordinates of staircase
-- doors on a given floor
function scd(f,p,ep)
local crp
if f==1 then
crp=scdarr[1][p]
elseif f==6 then
crp=scdarr[2][p]
elseif (f%2)==1 then
crp=scdarr[3][p]
else
crp=scdarr[4][p]
end
return {crp[1],crp[2],f,ep}
end
-- returns the relative coordinates of the oxyd
-- door on a given floor
function od(f,ep)
return {14,2*fharr[f]-3,f,ep}
end
-- helper function that returns {st, st+1, st+2, ..., sp}
function range(st,sp)
local t={}
for i=st,sp do
table.insert(t,i)
end
return t
end
-- set up the puzzles[], triggers[] and doors[] array
-- (all necessary functions are declared by now)
puzzlesetup()
-- characters used in puzzle[] are defined here
mapping={
{".", "normal", "empty"},
{"a", "hole", "empty"},
{"t", "trigger", "empty"},
{"b", "normal", "wood"},
{"s", "normal", "block"},
{"A", "hole", "wood"},
{"B", "trigger", "wood"},
{"d", "normal", "door"}
}
-- fill the pfloors[] and pstones[] arrays with initial
-- values from puzzles[]
function initpz()
local cfloor
local cstone
local fa
local sa
local fb
local sb
pfloors={}
pstones={}
for k=1,6 do
local map=puzzles[k]
fa={}
sa={}
for j=1,7 do
local line=map[j]
fb={}
sb={}
for i=1,11 do
local c=strsub(line,i,i)
cfloor="normal"
cstone="empty"
for m=1,#mapping do
if c==mapping[m][1] then
cfloor=mapping[m][2]
cstone=mapping[m][3]
end
end
if cfloor=="hole" then
if k==1 then
error("Level check error: hole on level 1 at
("..i..","..j..","..k..")")
else
local ustone=pstones[k-1][j][i]
if ustone=="empty" then
elseif ustone=="wood" then
elseif ustone=="elevator" then
else
error("Level check error: stone type '"..ustone.."'
under the hole at ("..i..","..j..","..k..")")
end
end
end
fb[i]=cfloor
sb[i]=cstone
end
fa[j]=fb
sa[j]=sb
end
pfloors[k]=fa
pstones[k]=sa
end
end
initpz()
-- remove everything except stones
-- this is to be called after the puzzle is completed
-- the purpose for this function (besides the effect)
-- is that the number of moves (pushes) necessary to
-- finish the level does not depend on the distribution
-- of the oxyds
puzzledone=0
function unpuzzle()
puzzledone=1
for k=1,6 do
for j=1,7 do
for i=1,11 do
pfloors[k][j][i] = "normal"
if pstones[k][j][i] ~= "block" then
pstones[k][j][i] = "empty"
end
end
end
end
end
-- add ghosts in difficult mode
ghosts=0
if difficult then
ghosts=1
end
specrespawn=false
--if difficult then
-- specrespawn=true -- use special respawn management
-- respawnsleft=27 -- player can respawn this many times
-- -- note: when increasing the number of respawns, don't forget to
-- -- update ovlarr in showrespawncounter()
--end
unlimitedrespawn=false
if difficult then
unlimitedrespawn=true -- player can respawn an unlimited number of times
end
-- -- -- -- -- -- -- -- --
-- BEGIN DEBUG SETTINGS
-- -- -- -- -- -- -- -- --
-- enable/disable staircase parity colouring (level debugging feature)
-- connected staircase cells are drawn with the same colour
-- (warning: this changes the friction in the staircase)
parity_colouring=false
-- enable/disable ghost cheat [please only use for debugging]
-- if enabled, ghosts are passable and don't kill
ghost_cheat=false
-- -- -- -- -- -- -- -- --
-- END DEBUG SETTINGS
-- -- -- -- -- -- -- -- --
-- with the 'virtual' 3d world already initialised,
-- start building the 'real' one-screen world
levelh = 13
levelw = 21
create_world(levelw, levelh)
-- only to avoid dummy tiles when debugging
fill_floor("fl-abyss")
-- count moves
enigma.ShowMoves = TRUE
-- add an actor to position the screen correctly (i.e. don't scroll/page the
screen, even at the entrance)
-- an ac-killerball is used here since that won't respawn on F3 (ac-whiteball
would make the player lose 2 lives)
set_actor("ac-killerball", 20.5, 6.5, {player=0, mouseforce=0})
-- add the marble
set_actor("ac-blackball", 0.5, 5.5, {player=0, name="player"})
-- define the main floor types used in the level
-- (standard floor types are redefined to change friction & mouseforce)
world.DefineSimpleFloor("fl-pbase", 5, 1, false, "fl-abyss")
display.DefineAlias("fl-pbase", "fl-red")
world.DefineSimpleFloor("fl-sbase", 3, 1, false, "fl-abyss")
display.DefineAlias("fl-sbase", "fl-gray")
-- define stones used for ghosts
if ghost_cheat then
world.DefineSimpleStone("st-ghost0", "stone", 1, 0)
world.DefineSimpleStone("st-ghost1", "stone", 1, 0)
world.DefineSimpleStone("st-ghost2", "stone", 1, 0)
world.DefineSimpleStone("st-ghost3", "stone", 1, 0)
display.DefineShadedModel("st-ghost0", "st-invisible", "st-bolder-fall1")
display.DefineShadedModel("st-ghost1", "st-invisible", "st-bolder-fall4")
display.DefineShadedModel("st-ghost2", "st-invisible", "st-bolder-fall7")
display.DefineShadedModel("st-ghost3", "st-invisible", "st-bolder-fall10")
else
world.DefineSimpleStone("st-ghost0", "stone", 0, 0)
world.DefineSimpleStone("st-ghost1", "stone", 0, 0)
world.DefineSimpleStone("st-ghost2", "stone", 0, 0)
world.DefineSimpleStone("st-ghost3", "stone", 0, 0)
display.DefineShadedModel("st-ghost0", "st-bolder-fall1",
"sh-round2-growing3")
display.DefineShadedModel("st-ghost1", "st-bolder-fall4",
"sh-round2-growing3")
display.DefineShadedModel("st-ghost2", "st-bolder-fall7",
"sh-round2-growing3")
display.DefineShadedModel("st-ghost3", "st-bolder-fall10",
"sh-round2-growing3")
end
-- in special respawn mode, a large, but fixed number of respawn possibilities
are allowed
-- we can't add that many 'it-extralife' items to the inventory, so we are
using a different
-- approach to show them, drawing them as an overlay to walls
-- [this feature is disabled at the moment]
if specrespawn then
display.DefineComposite("st-rock2-extra", "st-rock2", "it-extralife")
world.DefineSimpleStone("st-rock2-extra", "stone", 0, 0)
end
-- select the stone style to be used for walls
wallstone = "st-rock2"
wallstoneovl = "st-rock2-extra" -- the one with the extra life overlay
-- borders are drawn with the usual renderline method
function renderLine( line, pattern)
local drawblockers=0 -- avoid drawing blockers while drawblockers() is
undefined
bfloor="fl-sbase"
by=oy+line
for i=1, strlen(pattern) do
bx=ox+i-1
local c = strsub( pattern, i, i)
if c~=" " then
set_floor(bfloor,bx,by)
end
-- inner and outer boundary of the staircase
if c=="#" then
set_stone(wallstone,bx,by)
-- slopes in the staircase
elseif c==">" then
set_floor("fl-gradient",bx,by,{type=4})
elseif c=="<" then
set_floor("fl-gradient",bx,by,{type=3})
elseif c=="v" then
set_floor("fl-gradient",bx,by,{type=2})
elseif c=="^" then
set_floor("fl-gradient",bx,by,{type=1})
-- floor change markers (upwards)
elseif c==")" then
set_stone("st-oneway-e",bx,by)
elseif c=="(" then
set_stone("st-oneway-w",bx,by)
elseif c=="u" then
set_stone("st-oneway-s",bx,by)
elseif c=="`" then
set_stone("st-oneway-n",bx,by)
-- floor change markers (downwards)
elseif c=="}" then
set_stone("st-oneway_black-e",bx,by)
elseif c=="{" then
set_stone("st-oneway_black-w",bx,by)
elseif c=="w" then
set_stone("st-oneway_black-s",bx,by)
elseif c=="~" then
set_stone("st-oneway_black-n",bx,by)
-- staircase and oxyd doors
elseif c=="-" then
if ghosts==1 then
set_door("st-door_a",bx,by)
else
set_door("st-door-h",bx,by)
end
elseif c=="|" then
if ghosts==1 then
set_door("st-door_a",bx,by)
else
set_door("st-door-v",bx,by)
end
-- actual oxyds
elseif c=="o" then
oxyd(bx,by)
-- dummy sensors that make dropping items impossible
elseif c==":" then
set_item("it-sensor",bx,by)
-- sensors that trigger floor changes
elseif c=="S" then
set_item("it-sensor",bx,by,{action="callback",target="trigs"})
elseif c=="T" then
set_item("it-sensor",bx,by,{action="callback",target="trigt"})
elseif c=="U" then
set_item("it-sensor",bx,by,{action="callback",target="trigu"})
elseif c=="V" then
set_item("it-sensor",bx,by,{action="callback",target="trigv"})
-- staircase parity colouring
elseif c=="." then
if parity_colouring then
if (floor%4)<2 then
set_floor("fl-black",bx,by)
else
set_floor("fl-white",bx,by)
end
end
elseif c=="," then
if parity_colouring then
if (floor%4)<2 then
set_floor("fl-white",bx,by)
else
set_floor("fl-black",bx,by)
end
end
elseif c==";" then
if parity_colouring then
set_floor("fl-darkgray",bx,by)
end
-- safe places to avoid ghosts
elseif c=="%" then
if ghosts==1 then
set_stone("st-grate2",bx,by)
drawblockers=1
else
set_stone(wallstone,bx,by)
end
end
end
end
-- add dummy sensors everywhere so that you can't drop items
-- they will be overwritten with other items where necessary
for j=0,12 do
for i=0,19 do
set_item("it-sensor",ox+i,oy+j)
end
end
-- draw the invariant parts of the level
-- this includes oxyds and the sensors in the staircase
-- (oxyds are drawn only once so as to keep them open;
-- they would close if overwritten with another one)
--01234567890123456789
renderLine(00 , "####################")
renderLine(01 , "#::::::SSS::::::#:o#")
renderLine(02 , "#:#############:####")
renderLine(03 , "#:# #:#:o#")
renderLine(04 , "#:# #:####")
renderLine(05 , "#V# #T#:o#")
renderLine(06 , "#V# #T####")
renderLine(07 , "#V# #T#:o#")
renderLine(08 , "#:# #:####")
renderLine(09 , "#:# #:#:o#")
renderLine(10 , "#:#############:####")
renderLine(11 , "#::::::UUU::::::#:o#")
renderLine(12 , "####################")
--01234567890123456789
oxyd_shuffle()
-- add timer to move ghosts
function startghosts()
if ghosts==1 then
set_stone("st-timer",0,1,{loop=TRUE,interval=0.5,action="callback",target="ghosttick"})
end
end
-- starting position to roll in from the left
set_floor("fl-leaves", 0, 5) -- respawn position (to detect respawns)
set_floor("fl-gradient", 0, 6, {type=3, force=20}) -- roll-in position
set_floor("fl-leaves", 0, 7) -- intro position (to detect skip by F3)
set_stone(wallstone, 0, 5)
set_stone(wallstone, 0, 7)
-- draw the boundary with the staircase, depending on the
-- current floor (this function is called on every floor change)
function drawbase()
-- remove all stones except those near the oxyds
-- this is to ensure that cells marked as empty ('.') are really empty
for j=0,12 do
for i=0,16 do
enigma.KillStone(ox+i,oy+j)
end
end
-- reset the floor in the puzzle area to the default
-- note: this is obsolete, since loadmap() overwrites them anyway
for j=3,9 do
for i=3,13 do
set_floor("fl-leaves",ox+i,oy+j)
end
end
-- staircase layout depends on the parity of the floor number,
-- but we need a special layout for the topmost and bottommost floors
-- (no upwards/downwards stairs; doors/wallblocks repositioned)
if floor==1 then
--01234567890123456
renderLine(00 , "#################")
renderLine(01 , "#.....(<<<<<,,,,#")
renderLine(02 , "#.###-#######-%,#")
renderLine(03 , "#.# #,#")
renderLine(04 , "#.# #,#")
renderLine(05 , "#.# #,#")
renderLine(06 , "#.# #,#")
renderLine(07 , "#.# |,#")
renderLine(08 , "#.# ###")
renderLine(09 , "#.# |;#")
renderLine(10 , "#.%-#######-###;#")
renderLine(11 , "#....>>>>>);;;;;#")
renderLine(12 , "#################")
--01234567890123456
elseif floor==6 then
--01234567890123456
renderLine(00 , "#################")
renderLine(01 , "#....<<<<<};;;;;#")
renderLine(02 , "#.%-#######-###;#")
renderLine(03 , "#.# |;#")
renderLine(04 , "#.# ###")
renderLine(05 , "#.# |,#")
renderLine(06 , "#.# #,#")
renderLine(07 , "#.# #,#")
renderLine(08 , "#.# #,#")
renderLine(09 , "#.# #,#")
renderLine(10 , "#.###-#######-%,#")
renderLine(11 , "#.....{>>>>>,,,,#")
renderLine(12 , "#################")
--01234567890123456
elseif (floor%2)==1 then
--01234567890123456
renderLine(00 , "#################")
renderLine(01 , "#;;;;;(<<<<<,,,,#")
renderLine(02 , "#;###-#######-%,#")
renderLine(03 , "#;| #^#")
renderLine(04 , "#~# #^#")
renderLine(05 , "#v# #^#")
renderLine(06 , "#v# #^#")
renderLine(07 , "#v# #^#")
renderLine(08 , "#v# #w#")
renderLine(09 , "#v# |;#")
renderLine(10 , "#.%-#######-###;#")
renderLine(11 , "#....>>>>>);;;;;#")
renderLine(12 , "#################")
--01234567890123456
else
--01234567890123456
renderLine(00 , "#################")
renderLine(01 , "#....<<<<<};;;;;#")
renderLine(02 , "#.%-#######-###;#")
renderLine(03 , "#v# |;#")
renderLine(04 , "#v# #`#")
renderLine(05 , "#v# #^#")
renderLine(06 , "#v# #^#")
renderLine(07 , "#v# #^#")
renderLine(08 , "#u# #^#")
renderLine(09 , "#;| #^#")
renderLine(10 , "#;###-#######-%,#")
renderLine(11 , "#;;;;;{>>>>>,,,,#")
renderLine(12 , "#################")
--01234567890123456
end
-- add oxyd doors
set_door("st-door-v",ox+16,oy+2*fharr[floor]-1)
-- add a marker indicating the current floor
for i=1,6 do
enigma.KillStone(ox+17,oy+2*i-1)
end
set_stone("st-knight",ox+17,oy+2*(7-floor)-1)
-- add entrance
if floor==startfloor then
if entrancestate == 1 then
set_stone("st-door-v-open", ox+0, oy+6)
else
set_stone("st-door-v", ox+0, oy+6)
end
end
-- further safe places from ghosts
if ghosts==1 then
if floor==1 then
set_stone("st-grate2", ox+1, oy+4)
elseif floor==6 then
set_stone("st-grate2", ox+1, oy+8)
end
end
-- add blockers on horizontal staircases
if floor%2==1 then
enigma.KillItem(ox+4, oy+1)
enigma.KillItem(ox+12, oy+11)
drawblocker(4, 11)
drawblocker(12, 1)
else
enigma.KillItem(ox+4, oy+11)
enigma.KillItem(ox+12, oy+1)
drawblocker(4, 1)
drawblocker(12, 11)
end
-- display the number of remaining respawns
if specrespawn then
showrespawncounter()
end
end
entrancestate=0
function changeentrance(state)
local ent=enigma.GetStone(ox+0, oy+6)
if state == "open" or state == "fastopen" then
entrancestate=1
else
entrancestate=0
end
if floor == startfloor then
if ent ~= nil then
-- after completing the puzzles, the entrance
-- should remain open
if puzzledone == 1 then
state = "open"
end
if state == "fastopen" then
set_stone("st-door-v-open", ox+0, oy+6)
else
SendMessage(ent, state)
end
end
end
end
-- helper functions to get marble coordinates
function ppos()
local p=enigma.GetNamedObject("player")
return {enigma.GetPos(p)}
end
function xpos()
local s=ppos()
return s[1]
end
function ypos()
local s=ppos()
return s[2]
end
-- functions to handle floor switching using the staircase
-- (called by the appropriate sensors)
function trigs()
local x=xpos()
if x==ox+7 then
pou()
elseif x==ox+9 then
ped()
end
end
function trigt()
local y=ypos()
if y==oy+5 then
peu()
elseif y==oy+7 then
pod()
end
end
function trigu()
local x=xpos()
if x==ox+7 then
ped()
elseif x==ox+9 then
pou()
end
end
function trigv()
local y=ypos()
if y==oy+5 then
pod()
elseif y==oy+7 then
peu()
end
-- trigv is also called after leaving the entrance,
-- so we can close the entrance here
changeentrance("close")
end
-- page up if odd
function pou()
if (floor%2)==1 then
pup()
end
end
-- page down if odd
function pod()
if (floor%2)==1 then
pdown()
end
end
-- page up if even
function peu()
if (floor%2)==0 then
pup()
end
end
-- page down if even
function ped()
if (floor%2)==0 then
pdown()
end
end
function pup()
if floor==6 then else
changefloor(floor+1)
end
end
function pdown()
if floor==1 then else
changefloor(floor-1)
end
end
-- drawing the ghosts in the staircase
-- note: these are the small stones with arrows on them
-- (they don't look like ghosts anymore, but I've kept the terminology)
--blockers' state
bst={
{0,0,0,0,0,0}, -- staircase 0
{0,0,0,0,0,0} -- staircase 1
}
-- add "blockers" in the staircase, limiting the use of
-- horizontal stairs to walking between ghosts
-- change the state of a blocker, updating display if necessary
function changeblocker(gfloor, staircase)
local st=(bst[staircase+1][gfloor]+1)%4
if floor==gfloor then
local sx=4
local sy=11
if (gfloor%4)==2 or (gfloor%4)==3 then
sx=16-sx
end
if (gfloor%4)==3 or (gfloor%4)==0 then
sy=12-sy
end
if staircase==1 then
sx=16-sx
sy=12-sy
end
if st==1 then
-- open
local blocker=enigma.GetStone(ox+sx, oy+sy)
SendMessage(blocker, "open")
elseif st==0 then
-- close
enigma.KillItem(ox+sx, oy+sy)
set_stone("st-blocker-growing", ox+sx, oy+sy)
end
end
bst[staircase+1][gfloor]=st
end
-- draw the current state of a particular blocker
function drawblocker(dx, dy)
if ghosts==1 then
local staircase=0
if dy==1 then
staircase=1
end
if (floor%4)==3 or (floor%4)==0 then
staircase=1-staircase
end
if bst[staircase+1][floor]==0 then
-- closed
set_stone("st-blocker", ox+dx, oy+dy)
else
-- open
set_item("it-blocker", ox+dx, oy+dy)
end
else
set_item("it-sensor", ox+dx, oy+dy) -- dummy sensor
end
end
function addghost(pos, staircase, remove, active)
-- pos is a number from 0 to 161 that specifies the position of the ghost
-- it is counted modulo 162 and increased every time frame
-- staircase is either 0 or 1
-- remove is 0 if the ghost is to be placed, 1 if it is to be cleared
-- active is 1 when ghosts move as a result of a time phase (-> change
blockers)
-- and 0 if called because of changing the current floor (-> don't change
blockers)
pos=pos%162 -- just in case
local back=0
if pos>=81 then
back=1
pos=161-pos
end
local gfloor=0 -- current floor the ghost is on
local gmulti=0 -- when changing floors, there is a short interval when the
ghost is visible on
-- both the upper and lower floor; in this case, gmulti
should be set to 1 and
-- gfloor to the smaller of the two values
local gx=0
local gy=0 -- screen coordinates of the ghost (ox, oy will be added to
this)
local gdir=0 -- current direction (0=up, 1=right, 2=down, 3=left)
-- branching based on time phase, making the effect
-- of ghosts using the staircase
if pos<=6 then
gfloor=1
gx=1
gy=5+pos
gdir=2
elseif pos<=21 then
if pos<13 then
gfloor=1
elseif pos>15 then
gfloor=2
else
gfloor=1
gmulti=1
end
gx=1+(pos-7)
gy=11
gdir=1
elseif pos<=32 then
if pos<26 then
gfloor=2
elseif pos>28 then
gfloor=3
else
gfloor=2
gmulti=1
end
gx=15
gy=11-(pos-22)
gdir=0
elseif pos<=47 then
if pos<39 then
gfloor=3
elseif pos>41 then
gfloor=4
else
gfloor=3
gmulti=1
end
gx=15-(pos-33)
gy=1
gdir=3
elseif pos<=58 then
if pos<52 then
gfloor=4
elseif pos>54 then
gfloor=5
else
gfloor=4
gmulti=1
end
gx=1
gy=1+(pos-48)
gdir=2
elseif pos<=73 then
if pos<65 then
gfloor=5
elseif pos>67 then
gfloor=6
else
gfloor=5
gmulti=1
end
gx=1+(pos-59)
gy=11
gdir=1
else
gfloor=6
gx=15
gy=11-(pos-74)
gdir=0
end
if back==1 then
gdir=(gdir+2)%4 -- opposite direction
end
if staircase==1 then
gfloor=7-gfloor
if gmulti==1 then
gfloor=gfloor-1
end
-- reflect vertically
gy=12-gy
if gdir==0 or gdir==2 then
gdir=2-gdir
end
end
if remove==0 and active==1 then
if gy==1 or gy==11 then
if gx==3 or gx==5 or gx==11 or gx==13 then
changeblocker(gfloor, staircase)
end
end
end
if floor==gfloor or (gmulti==1 and floor==gfloor+1) then
if remove==0 then
set_stone("st-ghost"..gdir, ox+gx, oy+gy)
local pp=ppos()
if ox+gx==pp[1] and oy+gy==pp[2] then
if not ghost_cheat then
local player=enigma.GetNamedObject("player")
SendMessage(player, "shatter")
end
end
else
enigma.KillStone(ox+gx, oy+gy)
end
end
end
gphase=159 -- initial phase
function refreshghosts(active)
function addautoghost(pos, remove, active)
if remove==0 then
addghost(pos, 0, 0, active)
addghost(pos, 1, 0, active)
else
addghost((pos+161)%162, 0, 1, active)
addghost((pos+161)%162, 1, 1, active)
end
end
function addallghosts(pos, remove, active)
addautoghost(pos, remove, active)
addautoghost((pos+4)%162, remove, active)
addautoghost((pos+54)%162, remove, active)
addautoghost((pos+58)%162, remove, active)
addautoghost((pos+108)%162, remove, active)
addautoghost((pos+112)%162, remove, active)
end
if ghosts==1 then
addallghosts(gphase, 1, active)
addallghosts(gphase, 0, active)
end
end
-- move ghosts (should be called on timer ticks)
-- note: using a too short timer interval will make frequent indirect calls to
changeblocker(),
-- rendering the completion of opening/closing animations impossible
-- (this not only results in a visual glitch, but also opens a path that
should be kept closed)
function ghosttick()
gphase=(gphase+1)%162
refreshghosts(1)
end
-- after solving the challenge, doors should not be closed again and obstacles
should be removed
-- (this is to make the number of required pushes independent of the random
distribution of oxyds)
function puzzlecompleted()
ghosts=0
unpuzzle()
drawbase()
loadmap()
loadstones()
updatedoors()
refreshghosts(0)
changeentrance("open")
end
-- switch to a different floor, saving stones on the old one
-- (used many times through the code)
function changefloor(newfloor)
savestones()
floor=newfloor
drawbase()
loadmap()
loadstones()
updatedoors()
refreshghosts(0)
end
-- this is set to 1 after the maximal number of attempts has been exceeded
-- it changes all wooded blocks to st-death, making it impossible to
-- push them to the correct positions
puzzlefailed=0
-- commit the single stone at (i,j) from the real world to the virtual one
-- note that the string returned by enigma.GetKind might
-- be different from the one used in set_stone
-- (e.g. st-wood cf. st-wood1, st-wood2)
function csavestones(i,j)
local sn=enigma.GetStone(px+i,py+j)
if sn~=nil then
local st=enigma.GetKind(sn)
if st=="st-wood1" or st=="st-wood2" or st=="st-death" then
pstones[floor][j][i]="wood"
elseif st=="empty" then
pstones[floor][j][i]="empty"
-- otherwise don't change it
end
else
pstones[floor][j][i]="empty"
end
end
-- save all stones on the puzzle map to the current floor in pstones[]
function savestones()
for j=1,7 do
for i=1,11 do
csavestones(i,j)
end
end
end
-- draw stones from the pstones[] array to the puzzle area
function cloadstones(i,j)
local ttm=trigmode
trigmode=0
local ss=pstones[floor][j][i]
if ss=="empty" then
enigma.KillStone(px+i,py+j)
elseif ss=="wood" then
if puzzlefailed==0 then
set_stone("st-wood",px+i,py+j)
else
set_stone("st-death",px+i,py+j)
end
elseif ss=="block" then
set_stone(wallstone,px+i,py+j)
elseif ss=="door" then
set_door("st-door_a",px+i,py+j)
end
trigmode=ttm
end
function loadstones()
for j=1,7 do
for i=1,11 do
cloadstones(i,j)
end
end
-- to dump the virtual world to the standard output after every change,
-- uncomment the line below
--puzzledump()
end
-- all the floors are set to have the same background colour
-- (if you'd like some variety, you could uncomment the other version)
function floorbase(f)
return "fl-pbase"
--if f==1 then
-- return "fl-bluegray"
--elseif f==2 then
-- return "fl-red"
--elseif f==3 then
-- return "fl-sahara"
--elseif f==4 then
-- return "fl-black"
--elseif f==5 then
-- return "fl-tigris"
--elseif f==6 then
-- return "fl-leaves"
--end
end
-- add a trigger at (i,j) with visibility v that (indirectly) calls trigfn(i,j)
-- since we can't pass parameters to callback functions, we'll define
-- a callback function for every cell using a simple naming scheme
function addautotrig(i,j,v)
local od=1
local pp=ppos()
-- workaround for an enigma bug when adding a trigger under an actor
if i==pp[1]-px and j==pp[2]-py then
local tr=enigma.GetItem(px+i,py+j)
if tr~=nil then
if enigma.GetKind(tr)=="it-trigger" then
enigma.SetAttrib(tr, "invisible", v)
if v==FALSE then
local imname="img-fltemp"..strsub("abcdefghijk",i,i)..j
local cf=enigma.GetFloor(px+i,py+j)
local ct=enigma.GetKind(cf)
display.DefineComposite(imname,ct,"it-trigger1")
set_floor(imname,px+i,py+j)
end
fntrigger(i,j,floor) -- the trigmode check effectively cancels
this call
od=0
end
end
end
if od==1 then
enigma.KillItem(px+i,py+j)
set_item("it-trigger",px+i,py+j,{invisible=v,
action="callback",target="trig"..strsub("abcdefghijk",i,i)..j})
end
end
-- load the map tile at (i,j) from the virtual world array
function cloadmap(i,j)
-- shade depth: defines the number of gray shades added per floors of depth
-- (usually 1 or 2)
shadedepth=1
-- trigger mode is set to zero, so the addition of new triggers under
-- existing stones won't call the trigger function
local ttm=trigmode
trigmode=0
local fl=pfloors[floor][j][i]
if fl=="normal" then
-- add the standard floor and a dummy sensor
-- so that items can't be dropped there
set_floor(floorbase(floor),px+i,py+j)
set_item("it-sensor",px+i,py+j)
elseif fl=="trigger" then
-- add a visible trigger
set_floor(floorbase(floor),px+i,py+j)
addautotrig(i,j,FALSE)
elseif fl=="hole" then
if floor==1 then
error "Error: hole at level 1"
end
if pstones[floor-1][j][i]=="wood" then
if puzzlefailed==0 then
-- a wooden block on the floor underneath makes an stwood floor
-- (like walking on the top side of the wooden cube)
set_floor("fl-stwood",px+i,py+j)
set_item("it-sensor",px+i,py+j)
else
-- an st-death "from the top"
set_floor("fl-black",px+i,py+j)
set_item("it-death",px+i,py+j)
end
elseif pstones[floor-1][j][i]=="elevator" then
-- elevators aren't implemented (weren't needed for this level)
set_floor("fl-metal4",px+i,py+j)
set_item("it-sensor",px+i,py+j)
elseif pstones[floor-1][j][i]=="empty" then
-- iterate through the layers below while there is a hole in the
ground
-- and the layer is empty (no stone)
-- add shades of gray for each empty layer to visualise depth
local z=floor-1
while (z>1) and (pfloors[z][j][i]=="hole") and
(pstones[z-1][j][i]~="wood" and pstones[z-1][j][i]~="elevator") do
z=z-1
end
local lf=pfloors[z][j][i]
local ls=pstones[z][j][i]
-- pick a unique image name for the tile so that they won't conflict
local imname="img-fltemp"..strsub("abcdefghijk",i,i)..j
-- define friction & mouseforce
-- (they won't have much impact on you, since you start falling
when you reach there)
world.DefineSimpleFloor(imname,1,1,false,"fl-dunes")
-- set the floor image to the default floor type on the bottom
floor,
-- or stwood if there was a wooden block below
if lf=="hole" then
local us=pstones[z-1][j][i]
if us=="wood" then
if puzzlefailed==0 then
display.DefineAlias(imname, "fl-stwood")
else
display.DefineComposite(imname, "fl-black", "it-death")
end
elseif us=="elevator" then
display.DefineAlias(imname, "fl-metal4")
else
display.DefineAlias(imname, "fl-dummy") -- this shouldn't
occur
end
else
display.DefineAlias(imname, floorbase(z))
end
if lf=="trigger" then
-- add a trigger image
display.DefineComposite(imname,imname,"it-trigger")
end
while z~=floor do
-- shade it (st-disco-light is a homogeneous, low opacity gray
pixmap)
for s = 1,shadedepth do
display.DefineComposite(imname,imname,"st-disco-light")
end
z=z+1
end
-- apply the composited floor
set_floor(imname,px+i,py+j)
-- add an invisible trigger to notice the marble or a wooden block
falling down
addautotrig(i,j,TRUE)
else
error "Error: invalid stone type under a hole"
end
else
print("invalid floor at "..i..","..j)
enigma.KillItem(px+i,py+j)
end
if floor ~= 6 then
-- if there is a wooden block on the current cell with a hole above it,
we'll have to
-- add a trigger under it so that we can track the falling blocks when
the wooden
-- block at the bottom is pushed out
if pfloors[floor+1][j][i] == "hole" then
if pstones[floor][j][i] == "wood" then
if fl=="trigger" then
-- if there is already a trigger, don't make it invisible
addautotrig(i,j,FALSE)
else
addautotrig(i,j,TRUE)
end
end
end
end
trigmode=ttm
end
function loadmap()
for j=1,7 do
for i=1,11 do
cloadmap(i,j)
end
end
end
-- callback functions that call trigfn with the appropriate coordinates
-- (simulates passing arguments with a trigger callback)
function triga1() trigfn(1,1) end function triga2() trigfn(1,2) end function
triga3() trigfn(1,3) end
function triga4() trigfn(1,4) end function triga5() trigfn(1,5) end function
triga6() trigfn(1,6) end
function triga7() trigfn(1,7) end function trigb1() trigfn(2,1) end function
trigb2() trigfn(2,2) end
function trigb3() trigfn(2,3) end function trigb4() trigfn(2,4) end function
trigb5() trigfn(2,5) end
function trigb6() trigfn(2,6) end function trigb7() trigfn(2,7) end function
trigc1() trigfn(3,1) end
function trigc2() trigfn(3,2) end function trigc3() trigfn(3,3) end function
trigc4() trigfn(3,4) end
function trigc5() trigfn(3,5) end function trigc6() trigfn(3,6) end function
trigc7() trigfn(3,7) end
function trigd1() trigfn(4,1) end function trigd2() trigfn(4,2) end function
trigd3() trigfn(4,3) end
function trigd4() trigfn(4,4) end function trigd5() trigfn(4,5) end function
trigd6() trigfn(4,6) end
function trigd7() trigfn(4,7) end function trige1() trigfn(5,1) end function
trige2() trigfn(5,2) end
function trige3() trigfn(5,3) end function trige4() trigfn(5,4) end function
trige5() trigfn(5,5) end
function trige6() trigfn(5,6) end function trige7() trigfn(5,7) end function
trigf1() trigfn(6,1) end
function trigf2() trigfn(6,2) end function trigf3() trigfn(6,3) end function
trigf4() trigfn(6,4) end
function trigf5() trigfn(6,5) end function trigf6() trigfn(6,6) end function
trigf7() trigfn(6,7) end
function trigg1() trigfn(7,1) end function trigg2() trigfn(7,2) end function
trigg3() trigfn(7,3) end
function trigg4() trigfn(7,4) end function trigg5() trigfn(7,5) end function
trigg6() trigfn(7,6) end
function trigg7() trigfn(7,7) end function trigh1() trigfn(8,1) end function
trigh2() trigfn(8,2) end
function trigh3() trigfn(8,3) end function trigh4() trigfn(8,4) end function
trigh5() trigfn(8,5) end
function trigh6() trigfn(8,6) end function trigh7() trigfn(8,7) end function
trigi1() trigfn(9,1) end
function trigi2() trigfn(9,2) end function trigi3() trigfn(9,3) end function
trigi4() trigfn(9,4) end
function trigi5() trigfn(9,5) end function trigi6() trigfn(9,6) end function
trigi7() trigfn(9,7) end
function trigj1() trigfn(10,1) end function trigj2() trigfn(10,2) end function
trigj3() trigfn(10,3) end
function trigj4() trigfn(10,4) end function trigj5() trigfn(10,5) end function
trigj6() trigfn(10,6) end
function trigj7() trigfn(10,7) end function trigk1() trigfn(11,1) end function
trigk2() trigfn(11,2) end
function trigk3() trigfn(11,3) end function trigk4() trigfn(11,4) end function
trigk5() trigfn(11,5) end
function trigk6() trigfn(11,6) end function trigk7() trigfn(11,7) end
-- yes, it's an ugly hack, but I haven't found a workaround yet ;)
-- triggers start enabled
trigmode=1
function trigfn(x,y)
if trigmode==1 then
local cs=pstones[floor][y][x]
local cf=pfloors[floor][y][x]
if cf=="hole" then
-- an invisible trigger for checking a hole
local sn=enigma.GetStone(px+x,py+y)
if sn~=nil then
local st=enigma.GetKind(sn)
if st=="st-wood1" or st=="st-wood2" then
-- if a wooden block is pushed above the hole,
-- let it fall down
pzfall(x,y,floor)
end
end
local pp=ppos()
if (pp[1]==x+px) and (pp[2]==y+py) then
-- if the marble goes into the hole,
-- it shall fall too
acfall(x,y) -- warning: 'floor' changes here
end
elseif cf=="trigger" then
-- a real trigger
fntrigger(x,y,floor)
end
if cs=="wood" then
if floor~=6 then
local sn=enigma.GetStone(px+x,py+y)
if sn==nil then
if pfloors[floor+1][y][x]=="hole" then
if pstones[floor+1][y][x]=="wood" then
-- pushed out a wooden block from under another,
with a hole between
-- let the above blocks fall down to the current
floor
pzxfall(x,y,floor)
end
end
end
end
end
end
end
function pzfall(x,y,f)
-- (x,y,f) is a wooden block above a hole with empty space below
-- this function handles making it fall down
local z=f
csavestones(x,y)
pstones[z][y][x]="empty"
while (z>1) and (pfloors[z][y][x]=="hole") and
(pstones[z-1][y][x]=="empty") do
-- there's still space below, fall further
z=z-1
end
pstones[z][y][x]="wood"
if z~=f then
if pfloors[z][y][x]=="trigger" then
-- the block fell on a trigger, let's activate it
fntrigger(x,y,z)
end
-- reload the map so that changes appear
cloadmap(x,y)
cloadstones(x,y)
end
end
function pzxfall(x,y,f)
-- (x,y,f) is an empty cell with a hole and some wooden blocks above
-- make them fall down by moving the empty cell upwards as far as it can go
local z=f
csavestones(x,y)
pstones[z][y][x]="wood"
while (z<6) and (pfloors[z+1][y][x]=="hole") and
(pstones[z+1][y][x]=="wood") do
z=z+1
end
pstones[z][y][x]="empty"
if z~=f then
if pfloors[f][y][x]=="trigger" then
-- if the floor under the start cell contains a trigger,
-- we should call it
-- (if triggers are mapped to doors, this doesn't do much, since
-- pushing a block from under others makes the trigger pop up for
-- only a moment since the falling blocks press it again;
-- but for some other trigger actions, it might be necessary)
fntrigger(x,y,f)
end
cloadmap(x,y)
cloadstones(x,y)
end
end
lfz=0
function acfall(x,y)
-- the actor went over the hole at (x,y), let him fall down
local z=floor
while (z>1) and (pfloors[z][y][x]=="hole") and
(pstones[z-1][y][x]=="empty") do
z=z-1
end
if z==floor then
-- no fall at all, we shouldn't get here
return
end
lfz=z
-- play the shrinking ball animation
local pl=enigma.GetNamedObject("player")
SendMessage(pl,"fallvortex")
-- if the ball falls more than one floor, it shatters on impact
local tg="acendfall"
if z~=floor-1 then
tg="accontfall" --tg="acendfkill"
end
-- give the animation some time to play
set_stone("st-timer", 0, 2, {action="callback", target=tg, interval=0.4,
loop=FALSE})
end
function acendfall()
-- touch the ground without falling apart, use it after falling a single
storey
kill_stone(0, 2) -- cancel the animation timer (and make that way passable
again)
-- play the appearing ball animation, this unlocks the ball
local pl=enigma.GetNamedObject("player")
SendMessage(pl,"appear")
-- switch to the target floor
changefloor(lfz)
end
function accontfall()
-- fall a single floor and continue falling; it will surely end in a crash
kill_stone(0, 2) -- cancel the old timer
local tg="accontfall"
if floor > 1 then
changefloor(floor-1)
end
if lfz==floor-1 then
-- arrived at destination, crash
tg="acendfkill"
end
-- set up new timer (wait for 0.4 seconds before falling to next floor)
set_stone("st-timer", 0, 2, {action="callback", target=tg, interval=0.4,
loop=FALSE})
end
function acendfkill()
-- end the fall with a crash
acendfall()
killplayer()
end
function killplayer()
local pl=enigma.GetNamedObject("player")
SendMessage(pl,"shatter")
end
function checktrigger(x,y,z,o,p)
-- check whether the trigger at (x,y,z) is depressed by either a stone or
the player (the player is only counted if p is set)
-- o is the 'override flag', forcing to check the pstones[] array instead
of the enigma map even on the current floor
-- p sets whether the player on the trigger counts
local trd=0
if (floor==z) and (p==1) then
local pp=ppos()
if x+px==pp[1] and y+py==pp[2] then
trd=1
end
end
if (floor==z) and (o==0) then
local sn=enigma.GetStone(x+px,y+py)
if sn~=nil then
trd=1
end
else
if pstones[z][y][x]~="empty" then
trd=1
end
end
-- returns 1 if pressed, 0 if not
return trd
end
function doorstate(d,o,p)
-- a door should be open if all the corresponding triggers are pressed
local st=1
if puzzledone == 1 then
-- if the puzzle is completed, all doors remain open
return 1
else
for l,w in pairs(doors[d][4]) do
local tx,ty,tz=unpack(triggers[w])
if checktrigger(tx,ty,tz,o,p)==0 then
st=0
end
end
end
return st
end
function checkforcompletion()
-- checks whether the triggers necessary to complete the level are held down
if puzzledone == 0 then
local st=1
for l,w in pairs(completion) do
local tx,ty,tz=unpack(triggers[w])
if checktrigger(tx,ty,tz,0,0)==0 then
st=0
end
end
if st==1 then
puzzlecompleted()
end
end
end
function crdoorstate(x,y,z,o)
-- returns door state for a door given by coordinates
for i=1,#doors do
if doors[i]~=0 then
if (doors[i][1]==x) and (doors[i][2]==y) and (doors[i][3]==z) then
return doorstate(i,o,1)
end
end
end
-- perhaps some doors aren't listed in the doors[] array
-- in this case, they should be forever closed
-- (forever open doors can be achieved simply by passing an empty trigger
list)
return 0
end
function set_door(st,i,j)
-- wrapper for set_stone, creates a door with an initial
-- state determined by the triggers
if crdoorstate(i-px,j-py,floor,1)==1 then
if st=="st-door_a" then
-- st-door_a-open doesn't exist, we'll use an st-grate1
-- and open it manually by replacing it with st-door_a
-- (creating an st-door_a and sending it an "open"
-- message would produce an unwanted blinking animation)
set_stone("st-grate1",i,j)
else
set_stone(st.."-open",i,j)
end
else
set_stone(st,i,j)
end
end
function change_door(i,j,s)
local sn=enigma.GetStone(px+i,py+j)
if sn==nil then
if puzzledone==0 then
error("Cannot open/close empty cell at at ("..i..","..j.."), floor
"..floor)
end
else
local st=enigma.GetKind(sn)
if st=="st-grate1" then
if s=="close" then
set_stone("st-door_a",px+i,py+j)
end
else
SendMessage(sn,s)
end
end
end
function updatedoors()
-- update all doors to reflect the triggers' state
-- this function changes the door states by sending them
-- open/close messages, resulting in smooth animations
-- therefore, they aren't suitable for loading the current
-- floor; for that purpose, use set_door() instead
for i=1,#doors do
if doors[i]~=0 then
if doors[i][3]==floor then
-- only doors on the current floor are visible
local ds=doorstate(i,0,1)
local msg="close"
if ds==1 then
msg="open"
end
change_door(doors[i][1],doors[i][2],msg)
end
end
end
checkforcompletion()
end
function fntrigger(x,y,z)
-- a trigger was pressed or released (or perhaps only updated)
-- the simplest way to make doors reflect the new situation
-- is to update them all
if trigmode==1 then
updatedoors()
end
end
-- give the player some objects, to be used at startup/respawn
-- places the specified objects at the player's staring position, each for a
short period of time
-- the player should pick them up immediately (if he has free room)
-- the appearing animation for the player should give us enough time
-- to complete this action
-- note: you can't use giveobjects() more then once on a single
startup/respawn
-- you have to concatenate the lists instead
function giveobjects(objlist)
go_objlist = objlist
go_counter = 1
go_remaining = #objlist
set_stone("st-timer", 0, 3, {action="callback", target="placenextitem",
interval=0.025, loop=FALSE})
end
go_objlist = {}
go_counter = 0
go_remaining = 0
function placenextitem()
local gtest = 2
enigma.KillStone(0, 3)
if go_remaining > 0 then
set_item(go_objlist[go_counter][1], 0, 5, go_objlist[go_counter][2])
local iv=0.025
if go_remaining == 1 then
iv=0.5
end
set_stone("st-timer", 0, 3, {action="callback", target="placenextitem",
interval=iv, loop=FALSE})
go_counter=go_counter+1
go_remaining=go_remaining-1
else
set_item("it-sensor", 0, 6, {action="callback", target="warpcompleted"})
set_item("it-wormhole-off", 0, 5, {targetx=0.5, targety=6.5})
end
end
-- an incremental approach for giveobjects()
-- it's still forbidden to use more than one commitobjects() calls
simultaneously
ao_objlist = {}
function addobject(object, attributes)
table.insert(ao_objlist, {object, attributes})
end
function commitobjects()
giveobjects(ao_objlist)
ao_objlist={}
end
function warpcompleted()
set_item("it-sensor", 0, 5, {action="callback", target="respawned"})
set_item("it-sensor", 0, 6) -- dummy sensor
end
function initdz()
changeentrance("open")
if skippedintro==1 then
addobject("it-extralife", {}) -- compensate for F3 used to skip intro
end
if difficult then
--addobject("it-bag", {})
--for i=0,12 do
-- addobject("it-seed_nowood",{}) -- fill the bag; it's for the
effect only
--end
--addobject("it-sword", {})
addobject("it-document", {text="diff1"})
else
addobject("it-document", {text="easy2"})
addobject("it-document", {text="easy1"})
end
commitobjects()
end
respawncount=0
function respawned()
-- go to floor level when player dies
respawncount=respawncount+1
if floor ~= startfloor then
changefloor(startfloor)
-- if there was an animation in progress
-- (player pressed F3 while falling down),
-- we'll need to kill the timer
-- this also frees the way in the staircase corridor
kill_stone(0, 2)
end
changeentrance("open")
if specrespawn then
-- if respawnsleft is 0, you can still play until you die
-- -1, you've just failed
-- -2, you've already failed, but respawned again
if respawnsleft > -2 then
respawnsleft=respawnsleft-1
end
if respawnsleft==-1 then
puzzlefailed=1
changefloor(floor) -- redraw the map
end
local doctext=""
if respawnsleft == 20 then
doctext="20resp"
elseif respawnsleft == 10 then
doctext="10resp"
elseif respawnsleft == 0 then
doctext="lasttry"
elseif respawnsleft == -1 then
doctext="failed"
end
if doctext == "" then
giveobjects({
{"it-extralife", {}}
})
else
giveobjects({
{"it-extralife", {}},
{"it-document", {text=doctext}}
})
end
elseif unlimitedrespawn then
giveobjects({
{"it-extralife", {}}
})
else
giveobjects({
-- nothing, at least as of yet
})
end
showrespawncounter()
end
-- show the number of respawn possibilities as overlays to wall tiles
function showrespawncounter()
if specrespawn then
-- screen positions to draw overlays
ovlarr={
{0,8},{0,10},{0,12},{2,12},{4,12},{6,12},
{8,12},{10,12},{12,12},{14,12},{16,12},
{16,10},{16,8},{16,6},{16,4},{16,2},{16,0},
{14,0},{12,0},{10,0},{8,0},{6,0},{4,0},
{2,0},{0,0},{0,2},{0,4}
}
rpl=respawnsleft
if rpl > #ovlarr then
rpl = #ovlarr -- if there are too many extra lives, they aren't
shown
elseif rpl < 0 then
rpl = 0 -- avoid out-of-bounds indexing
end
for i=1,rpl do
set_stone(wallstoneovl, ox+ovlarr[i][1], oy+ovlarr[i][2])
end
for i=rpl+1,#ovlarr do
set_stone(wallstone, ox+ovlarr[i][1], oy+ovlarr[i][2])
end
end
end
function triggerdump()
-- this function is for maintainers only
-- after filling the puzzles[] array, this function can be used to
-- generate the code for the triggers[] array
-- to use it, simply uncomment the triggerdump() function call
-- at the end and load the level
print("--- trigger dump ---")
indent=" "
print(indent.."triggers={}")
print(indent.."t1=0")
for k=1,6 do
local lcnt=0
for i=1,11 do
for j=1,7 do
if pfloors[k][j][i]=="trigger" then
lcnt=lcnt+1
print(indent.."triggers[t"..k.."+"..lcnt.."]={"..i..","..j..","..k.."}")
end
end
end
if k~=6 then
print(indent.."t"..(k+1).."=t"..k.."+"..lcnt)
else
print(indent.."tsum=t6+"..lcnt)
end
end
print("")
end
function doordump()
-- this function is for maintainers only
-- generate code for the doors[] array
print("--- door dump ---")
indents=" "
indentl=" "
print(indents.."doors={")
for k=1,6 do
for i=1,11 do
for j=1,7 do
if pstones[k][j][i]=="door" then
print(indentl.."{"..i..","..j..","..k..",{}},")
end
end
end
end
for k=1,6 do
for n=1,6 do
print(indentl.."scd("..k..","..n..",{}),")
end
end
for k=1,6 do
print(indentl.."od("..k..",{}),")
end
print(indentl.."ed({})")
print(indents.."}")
end
function puzzledump()
-- this function is for maintainers only
-- dumps the current status of the virtual world to the
-- standard output in puzzle[] format
-- this can be used to save the game during testing
print("--- puzzle dump ---")
indents=" "
indentl=" "
print(indents.."puzzles={}")
for k=1,6 do
print(indents.."puzzles["..k.."]={")
for j=1,7 do
cline=indentl.."\""
for i=1,11 do
local pf=pfloors[k][j][i]
local ps=pstones[k][j][i]
local map="*"
for m=1,#mapping do
if mapping[m][2]==pf and mapping[m][3]==ps then
map=mapping[m][1]
end
end
cline=cline..map
end
cline=cline.."\""
if j~=7 then
cline=cline..","
end
print(cline)
end
print(indents.."}")
end
print("")
end
-- load all parts of the map
-- note that we had to place the actor before calling loadmap(),
-- since that function checks some coordinates against the player
function startgame()
drawbase()
loadmap()
loadstones()
updatedoors()
refreshghosts(0)
initdz()
startghosts()
--triggerdump()
--doordump()
end
--[=[-- (begin multiline lua comment)
-- These functions could be used to display a textual intro before the game is
started.
-- The intro feature is disabled by default since it uses some undocumented
enigma features,
-- and is therefore less likely to work in versions after 1.0.
function addcenteredtext(list)
-- works only for intro
local scr = video.GetScreen()
local srf = scr:get_surface()
local fnt = enigma.GetFont("levelmenu")
local w = srf:width()
local h = srf:height()
local cellsize = 0
if w==640 and h==480 then
cellsize = 32
elseif w==800 and h==600 then
cellsize = 40
elseif w==1024 and h==768 then
cellsize = 48
else
error("Unknown screen resolution")
end
xc = (srf:width()-3*cellsize)/2
yc = (13*cellsize)/2-8
for k,v in pairs(list) do
local yoff=v[1]
local text=v[2]
fnt:render(srf, xc-fnt:get_width(text)/2, yc+yoff*16, text)
end
scr:update_all()
scr:flush_updates()
end
function clearalltext()
-- works only for intro
for j=1,7 do
for i=1,11 do
set_floor("fl-abyss", px+i, py+j)
end
end
end
intromode=0
skippedintro=0
iphase=0
function introphase()
local lastphase=0
local wait=0.5
enigma.KillStone(0, 4)
if iphase==0 then
wait=0.1
elseif iphase==1 then
-- make it possible to cancel the intro using F3
set_item("it-sensor", 0, 7, {action="callback",
target="introwarpcomplete"})
set_item("it-wormhole-off", 0, 5, {targetx=0.5, targety=7.5}) -- warp
him to a different location
function introwarpcomplete()
set_item("it-sensor", 0, 5, {action="callback", target="skipintro"})
end
wait=0.1
-- set up intro frames here
elseif iphase==2 then
addcenteredtext({
{-1, "This is some sample text."},
{0, "This is some more sample text."},
{1, "This is even more sample text."},
{3, "(Press F3 to skip intro)"}
})
wait=3
elseif iphase==3 then
clearalltext()
wait=0.5
elseif iphase==4 then
addcenteredtext({
{-1, "It continues to span more"},
{0, "pages, enabling us to tell"},
{1, "a more complex storyline"}
})
wait=3
elseif iphase==5 then
clearalltext()
wait=0.5
elseif iphase==6 then
addcenteredtext({
{0, "And so on..."}
})
wait=2
elseif iphase==7 then
clearalltext()
wait=0.5
elseif iphase==8 then
lastphase=1
finishintro()
end
iphase=iphase+1
if lastphase==0 then
set_stone("st-timer", 0, 4, {loop=FALSE, interval=wait,
action="callback", target="introphase"})
end
end
function startintro()
intromode=1
iphase=0
introphase()
end
function finishintro()
intromode=0
set_item("it-sensor", 0, 5) -- dummy sensor to make sure skipintro() isn't
called
set_item("it-wormhole-off", 0, 7, {targetx=0.5, targety=5.5}) -- move back
to start position when intro is over
startgame()
end
function skipintro()
print("skipintro")
enigma.KillStone(0, 4) -- kill the timer
skippedintro=1 -- give an extra life to compensate for F3
finishintro()
end
--]=]-- (end multiline lua comment)
if enigma.CreatingPreview then
startgame()
else
--startintro()
startgame()
end
]]></el:luamain>
<el:i18n>
<el:string el:key="easy1">
<el:english el:translate="true">
Hint 1: As this is a sokoban level, you will only be able to reach
every oxyd if all the triggers are pressed down. There are exactly
as many blocks as triggers.
</el:english>
</el:string>
<el:string el:key="easy2">
<el:english el:translate="true">
Hint 2: Although you have got two exta lives, they are not required
to complete the level.
</el:english>
</el:string>
<el:string el:key="diff1">
<el:english el:translate="true">
Try the easy mode first.
</el:english>
</el:string>
<!-- (these strings are only needed when specrespawn is enabled)
<el:string el:key="20resp">
<el:english el:translate="true">
You can respawn twenty more times.
</el:english>
</el:string>
<el:string el:key="10resp">
<el:english el:translate="true">
You can respawn ten more times.
</el:english>
</el:string>
<el:string el:key="lasttry">
<el:english el:translate="true">
This is your last chance. Use it wisely.
</el:english>
</el:string>
<el:string el:key="failed">
<el:english el:translate="true">
It's over. Press Ctrl+A to try again.
</el:english>
</el:string>
-->
</el:i18n>
</el:protected>
</el:level>
#0 0x081f4c06 in traversetable (g=0xb51c59bc, h=0xb5069648) at lgc.c:181
#1 0x081f4f55 in propagatemark (g=0xb51c59bc) at lgc.c:285
#2 0x081f5730 in singlestep (L=0xb51c5950) at lgc.c:566
#3 0x081f5988 in luaC_step (L=0xb51c5950) at lgc.c:617
#4 0x081edaa5 in lua_pushlstring (L=0xb51c5950, s=0xb5144e70 "st-rock2",
len=8) at lapi.c:443
#5 0x081ee379 in lua_pushstring (L=0xb51c5950, s=0xb5144e70 "st-rock2") at
lapi.c:454
#6 0x080b52db in push_value (L=0xb51c5950, address@hidden) at lua.cc:152
#7 0x080b7845 in en_get_kind (L=0xb51c5950) at lua.cc:281
#8 0x081f30fe in luaD_precall (L=0xb51c5950, func=0xb5104d3c, nresults=1) at
ldo.c:319
#9 0x08205f77 in luaV_execute (L=0xb51c5950, nexeccalls=6) at lvm.c:587
#10 0x081f32ac in luaD_call (L=0xb51c5950, func=0xb5104c58, nResults=0) at
ldo.c:377
#11 0x081ee01c in f_call (L=0xb51c5950, ud=0xbff6adf4) at lapi.c:796
#12 0x081f29dd in luaD_rawrunprotected (L=0xb51c5950, f=0x81edff2 <f_call>,
ud=0xbff6adf4) at ldo.c:116
#13 0x081f2a83 in luaD_pcall (L=0xb51c5950, func=0x81edff2 <f_call>,
u=0xbff6adf4, old_top=24, ef=0) at ldo.c:461
#14 0x081ede19 in lua_pcall (L=0xb51c5950, nargs=2, nresults=0, errfunc=0) at
lapi.c:817
#15 0x080b5769 in lua::CallFunc (L=0xb51c5950, funcname=0xb5889644 "trigs",
address@hidden, obj=0xb589c108) at lua.cc:734
#16 0x0813a645 in world::PerformAction (o=0xb589c108, onoff=true) at
world.cc:1415
#17 0x080a23d9 in (anonymous namespace)::Sensor::actor_enter (this=0xb589c108)
at items.cc:2881
#18 0x080539fb in world::Actor::move (this=0xb5871c30) at actors.cc:166
#19 0x0813552b in world::World::move_actors (this=0xb53142f8, dtime=0.01) at
world.cc:996
#20 0x081356b5 in world::World::tick (this=0xb53142f8, dtime=0.01) at
world.cc:503
#21 0x0813584b in world::Tick (dtime=0.01) at world.cc:1772
#22 0x080e5189 in gametick (dtime=0.02) at server.cc:191
#23 0x080e7942 in enigma_server::Tick (dtime=0.02) at server.cc:318
#24 0x0808fbf1 in enigma_game::StartGame () at game.cc:86
#25 0x0815ff26 in enigma::gui::LevelMenu::on_action (this=0xbff6b438,
w=0x91e2880) at gui/LevelMenu.cc:237
#26 0x0817df2b in enigma::gui::LevelWidget::trigger_action (this=0x91e2880) at
gui/LevelWidget.cc:100
#27 0x0817f841 in enigma::gui::LevelWidget::handle_keydown (this=0x91e2880,
e=0xbff6b3d8) at gui/LevelWidget.cc:479
#28 0x0817f9de in enigma::gui::LevelWidget::on_event (this=0x91e2880,
address@hidden) at gui/LevelWidget.cc:401
#29 0x0816013d in enigma::gui::LevelMenu::on_event (this=0xbff6b438,
address@hidden) at gui/LevelMenu.cc:183
#30 0x081896af in enigma::gui::Menu::handle_event (this=0xbff6b438,
address@hidden) at gui/Menu.cc:115
#31 0x08189908 in enigma::gui::Menu::manage (this=0xbff6b438) at gui/Menu.cc:69
#32 0x08176c05 in enigma::gui::LevelPackMenu::manageLevelMenu (this=0xbff6b4e8)
at gui/LevelPackMenu.cc:307
#33 0x08187e65 in enigma::gui::MainMenu::on_action (this=0xbff6b680,
w=0x8e98820) at gui/MainMenu.cc:255
#34 0x08194169 in enigma::gui::Widget::invoke_listener (this=0x8e98820) at
gui/widgets.cc:61
#35 0x08197919 in enigma::gui::PushButton::on_event (this=0x8e98820,
address@hidden) at gui/widgets.cc:708
#36 0x081898b9 in enigma::gui::Menu::handle_event (this=0xbff6b680,
address@hidden) at gui/Menu.cc:151
#37 0x08189908 in enigma::gui::Menu::manage (this=0xbff6b680) at gui/Menu.cc:69
#38 0x08188792 in enigma::gui::ShowMainMenu () at gui/MainMenu.cc:370
#39 0x080c5a31 in main (argc=0, argv=0x0) at main.cc:727
- [Enigma-devel] New level: "The Tower",
Hubai Tamas <=