enigma-devel
[Top][All Lists]
Advanced

[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 &amp; Andrew Hubai" el:email="address@hidden" 
el:homepage=""/>
      <el:copyright>Copyright © 2006, 2007 Thomas &amp; 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

reply via email to

[Prev in Thread] Current Thread [Next in Thread]