-
Notifications
You must be signed in to change notification settings - Fork 4
/
level.lua
304 lines (279 loc) · 6.94 KB
/
level.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
-- Add the walls belonging to the given level tile.
function AddWalls(c,r,td)
local s=TSIZE
local xw,xe=c*s,(c+1)*s -- x of east and west
local zn,zs=r*s,(r+1)*s -- z of north and south
local interest=(0~=td.f&TF.DOOR) or
(0~=td.f&TF.LEVER) or (0~=td.f&TF.GATE)
if 0~=(td.f&TF.N) then
-- north wall
AddWall({lx=xe,rx=xw,lz=zn,rz=zn,tid=td.tid},
c,r,interest)
end
if 0~=(td.f&TF.S) then
-- south wall
AddWall({lx=xw,rx=xe,lz=zs,rz=zs,tid=td.tid},
c,r,interest)
end
if 0~=(td.f&TF.E) then
-- east wall
AddWall({lx=xe,rx=xe,lz=zs,rz=zn,tid=td.tid},
c,r,interest)
end
if 0~=(td.f&TF.W) then
-- west wall
AddWall({lx=xw,rx=xw,lz=zn,rz=zs,tid=td.tid},
c,r,interest)
end
end
-- Adds a wall at the given tile.
-- interest: (bool) whether it's a wall of interest
-- (door, button, etc).
function AddWall(w,c,r,interest)
S3WallAdd(w)
if interest then IwallAdd(c,r,w) end
-- Apply the CMT (color mapping table) if the level
-- requires it.
if G.lvl.wallCmt then w.cmt=G.lvl.wallCmt[w.tid] end
end
-- Add a wall of interest at col/row.
function IwallAdd(c,r,w) G.iwalls[r*240+c]=w end
-- Looks for a wall of interest at the given col,row,
-- nil if not found.
function IwallAt(c,r) return G.iwalls[r*240+c] end
-- Deletes a wall of interest at the given col,row.
function IwallDel(c,r) G.iwalls[r*240+c]=nil end
-- Opens the door at the given coordinates.
function DoorOpen(c,r)
local w=IwallAt(c,r)
if not w then return false end
-- If it's a locked door, require key.
local t=LvlTile(c,r)
if TD[t] and 0~=TD[t].f&TF.LOCKED and
not G.hasKey then
Say("***You need a key***")
return false
end
-- Start door open animation.
G.doorAnim={w=w,phi=0,irx=w.rx,irz=w.rz}
LvlTile(c,r,T.FLOOR) -- becomes floor tile
IwallDel(c,r)
Snd(SND.DOOR)
return true
end
function DoorAnimUpdate(dt)
local anim=G.doorAnim
if not anim then return end
anim.phi=anim.phi+dt*1.5
local phi=min(anim.phi,1.5)
anim.w.rx,anim.w.rz=RotPoint(
anim.w.lx,anim.w.lz,anim.irx,anim.irz,-phi)
if anim.phi>1.5 then
G.doorAnim=nil
return
end
end
-- Gets the tile that the user is facing toward,
-- the one that can be "interacted" with (e.g.,
-- open a door). Returns c,r of focus tile, or nil,nil
-- to indicate there is no focus tile.
function GetFocusTile()
local fx,fz=PlrFwdVec(TSIZE)
local c,r=
floor((G.ex+fx)/TSIZE),floor((G.ez+fz)/TSIZE)
local t=LvlTile(c,r)
local td=TD[t]
if not td then return nil,nil end
if 0~=td.f&TF.INTEREST then return c,r
else return nil,nil end
end
function UpdateFocusTile()
G.focC,G.focR=GetFocusTile()
end
function Interact()
if not G.focC then return end
local td=TD[LvlTile(G.focC,G.focR)]
if td.f&TF.DOOR~=0 then
DoorOpen(G.focC,G.focR)
elseif td.f&TF.LEVER~=0 then
PullLever(G.focC,G.focR)
end
end
-- Loads the given level. This won't set the mode
-- to play mode, it will only load the level.
function LoadLevel(lvlNo)
-- Reset G (game state), resetting it to the initial
-- state.
PalSet()
G=DeepCopy(G_INIT)
G.lvlNo=lvlNo
G.lvl=LVL[lvlNo]
local lvl=G.lvl
S3Reset()
S3.FLOOR_CLR,S3.CEIL_CLR=lvl.floorC,lvl.ceilC
G.ex=nil
-- First, load all spawners.
LoadSpawners()
-- Now load regular entities and tiles.
for r=0,lvl.pgh*17-1 do
for c=0,lvl.pgw*30-1 do
local cx,cz=(c+0.5)*TSIZE,(r+0.5)*TSIZE
local t=LvlTile(c,r)
local lbl=TileLabel(c,r)
local td=TD[t]
if td then AddWalls(c,r,td) end
if t==S.FLAG then
assert(lbl)
if lbl==0 then
-- Player start pos
G.ex,G.ez=cx,cz
end
end
if not D_NOENTS and ECFG[t] then
-- It's an entity.
EntAdd(t,cx,cz)
end
end
end
assert(G.ex,"Start pos flag not found.")
-- Create weapon overlay.
G.weapOver=S3OverAdd({sx=84,sy=94,tid=460,scale=2})
end
-- Loads all spawners in the level, storing them in
-- G.spawners.
function LoadSpawners()
local cols,rows=LvlSize()
G.spawners={}
for r=0,rows-1 do
for c=0,cols-1 do
local t=LvlTile(c,r)
if t==T.SPAWNER then LoadSpawner(c,r) end
end
end
end
-- Loads the spawner located at the given position.
function LoadSpawner(c0,r0)
local lbl=TileLabel(c0,r0)
assert(lbl)
LvlTile(c0,r0,T.FLOOR) -- remove spawner from map
if not G.spawners[lbl] then G.spawners[lbl]={} end
-- All entities around the spawner tile get absorbed
-- into the spawner.
for r=r0-1,r0+1 do
for c=c0-1,c0+1 do
local t=LvlTile(c,r)
if ECFG[t] then
-- It's an entity. Remove it from the map so it
-- doesn't spawn normally.
LvlTile(c,r,T.FLOOR)
-- Add it to the spawner.
local cx,cz=(c+0.5)*TSIZE,(r+0.5)*TSIZE
table.insert(G.spawners[lbl],{x=cx,z=cz,eid=t})
end
end
end
end
-- Checks if player stepped on a trigger tile. If
-- so, do the appropriate thing.
function CheckTriggers()
local c,r=floor(G.ex/TSIZE),floor(G.ez/TSIZE)
if LvlTile(c,r)~=T.TRIGGER then return end
local lbl=TileLabel(c,r)
if not lbl or G.trigdone[lbl] then return end
G.trigdone[lbl]=true
local spawner=G.spawners[lbl]
if not spawner then return end
for i=1,#spawner do
local s=spawner[i]
local ent=EntAdd(s.eid,s.x,s.z)
-- Spawned entities are awake by default.
ent.asleep=false
end
end
-- Returns the level tile at c,r.
-- If newval is given, it will be set as the new
-- value.
function LvlTile(c,r,newval)
local cols,rows=LvlSize()
if c>=cols or r>=rows or c<0 or r<0 then
return 0
end
local val=G.otiles[r*240+c]
if not val then
local c0,r0=MapPageStart(G.lvl.pg)
val=mget(c0+c,r0+r)
end
if newval then G.otiles[r*240+c]=newval end
return val
end
function LvlSize()
return G.lvl.pgw*30,G.lvl.pgh*17
end
-- Returns the level tile at the given x,z pos.
function LvlTileAtXz(x,z)
local c,r=floor(x/TSIZE),floor(z/TSIZE)
return LvlTile(c,r)
end
function PullLever(c,r)
LvlTile(c,r,T.SOLID)
local w=IwallAt(c,r)
if w then w.tid=TID.LEVER_P end
-- TODO: sfx
-- Open the gate.
-- (For now we assume there's a single gate,
-- and open it).
local cols,rows=LvlSize()
local gatec,gater=nil,nil
for r=0,rows-1 do
for c=0,cols-1 do
local t=LvlTile(c,r)
if TD[t] and TD[t].f&TF.GATE~=0 then
gatec,gater=c,r
break
end
end
end
-- Remove gate.
assert(gatec)
local w=IwallAt(gatec,gater)
assert(w)
IwallDel(gatec,gater)
S3WallDel(w)
LvlTile(gatec,gater,T.FLOOR)
Say("THE GATE OPENED!")
end
-- Returns the interaction hint for the currently
-- focused tile.
function GetInteractHint()
local c,r=G.focC,G.focR
if not c then return end
local hint=nil
local td=TD[LvlTile(c,r)]
if td.f&TF.DOOR~=0 then
if td.f&TF.LOCKED~=0 then
if G.hasKey then
return "Press S to unlock"
else
return "You need a key"
end
else
return "Press S to open door"
end
elseif td.f&TF.LEVER~=0 then
return "Press S to activate"
elseif td.f&TF.GATE~=0 then
return "Gate opens elsewhere!"
elseif td.f&TF.PORTAL~=0 then
return "Step into the portal!"
end
return nil
end
function CheckLevelEnd()
local t=LvlTileAtXz(G.ex,G.ez)
if TD[t] and TD[t].f&TF.PORTAL then
-- Player stepped through portal
SetMode(MODE.EOL)
Snd(SND.EOL)
MarkLvlDone(G.lvlNo)
end
end