1origG = G
2origRGX = RGX
3origGX = GX
4
5map = obj {
6	nam = 'map';
7	var {
8		data = {};
9		nr = 1;
10		prev = 0;
11		map = {};
12		d = 0;
13	};
14	dist = function(s, n)
15		local nn = s.d
16		if n then s.d = n end
17		return nn
18	end;
19	mirror = function(s)
20		return maps[s.nr].mirror
21	end;
22	select = function(s, n)
23		if n then
24			if s.nr and maps[s.nr] and maps[s.nr].exit then maps[s.nr].exit(s.data) end
25			s.prev = s.nr
26			s.nr = n;
27			if n == CONTMAP then
28				save_music(game)
29				set_music 'snd/nothing.ogg'
30			end
31		else
32			n = s.nr
33		end
34		G = origG
35		RGX = origRGX
36		GX = origGX
37		if s.title then
38			sprite.free(s.title);
39		end
40		s.title = sprite.text(fn, string.format("%d: %s", n, _(maps[n].title)), "black");
41		if hero:state() == DEAD or #s.map == 0 then
42			sound_init()
43			if maps[n].color then
44				bg_color = maps[n].color;
45			else
46				bg_color = '#9cccfc';
47			end
48			local y, x
49			s.map = {}
50			for y = 1, 30 do
51				s.map[y] = {}
52				for x = 1, 40 do
53					local c = string.sub(maps[n].map[y], x, x);
54					if c == '#' then
55						c = BLOCK
56					elseif c == '.' then
57						c = INVI
58					elseif c == '@' then
59						c = SNOW
60					elseif c == '^' then
61						c = FAKE
62					elseif c == '~' then
63						c = WATER
64					elseif c == '*' then
65						c = EMERGENCY
66					elseif c == '%' then
67						c = SEMIBLOCK
68					elseif c == '=' then
69						c = BRIDGE
70					elseif c == '-' then
71						c = ROPE
72					elseif c == 'm' then
73						c = MINE
74					elseif c == '>' then
75						c = nil
76						s.map.x = (x - 1) * BW
77						s.map.y = (y) * BH - hero.h
78					elseif c == '+' then
79						c = HEART
80					elseif c == ' ' then
81						c = 0
82					else
83						c = c -- same character
84					end
85					table.insert(s.map[y], { c })
86				end
87			end
88		end
89		if hero:state() == DEAD then
90			hero.x = s.map.x
91			hero.y = s.map.y
92			map.data = {}
93			hero:state(WALK);
94			hero.speed_x = 0
95			hero.dir = 1
96		end
97	end;
98	next = function(s)
99		local n
100		n = s.nr + 1;
101		if s.nr == CONTMAP then
102			n = s.prev
103			game_lifes = LIVES
104			game:dist(0)
105			hero:state(DEAD)
106			restore_music(game)
107		end
108		s.data = {}
109		s.map = {}
110		s:select(n)
111		s:dist(0)
112		if hero:state() ~= DEAD then
113			hero.x = 0
114			hero.dir = 1
115		end
116	end;
117	pos2block = function(s, x, y)
118		x = math.floor(x / BW);
119		y = math.floor(y / BH);
120		return x, y
121	end;
122	show = function(s)
123		local y, x
124		for y=0, 29 do
125			for x=0, 39 do
126				local c = s:cell(x, y);
127				if c[1] == BLOCK or c[1] == FAKE then
128					sprite.fill(sprite.screen(), x * 16, y * 16, 16 - 1, 16 - 1, 'black');
129				elseif c[1] == SNOW then
130					sprite.fill(sprite.screen(), x * 16, y * 16, 16, 16, 'white');
131				elseif c[1] == SEMIBLOCK then
132					if (not c.move) or (c.move < SEMI_TO) then
133						sprite.fill(sprite.screen(), x * 16, y * 16, 16 - 1, 16 - 1, SEMICOL);
134					end
135				elseif c[1] == WATER then
136					sprite.fill(sprite.screen(), x * 16, y * 16, 16, 16, 'blue');
137				elseif c[1] == EMERGENCY or c[1] == MINE then
138					sprite.fill(sprite.screen(), x * 16 + 1, y * 16 + 1, 16 - 2, 16 - 2, 'red');
139				elseif c[1] == BRIDGE then
140					sprite.fill(sprite.screen(), x * 16, y * 16, 16 - 1, 8, 'black');
141				elseif c[1] == ROPE then
142					sprite.fill(sprite.screen(), x * 16, y * 16, 16 - 1, 4, 'black');
143				elseif c[1] == HEART then
144					if not heart_blink then heart_blink = 0 end
145					if math.floor(heart_blink / 20) % 2 ~= 0 then
146						sprite.draw(heart_bonus_spr, sprite.screen(), x * 16, y * 16);
147					end
148					heart_blink = heart_blink + 1
149					if heart_blink >= 40 then heart_blink = 0 end
150				end
151			end
152		end
153	end;
154	cell = function(s, x, y)
155		if x < 0 or y < 0 or x >= 40 or y >= 30 then
156			return
157		end
158		return s.map[y + 1][x + 1];
159	end,
160	block = function(s, x, y)
161		if x < 0 or y < 0 or x >= 40 or y >= 30 then
162			return
163		end
164		local l = s.map[y + 1];
165		local c = l[x + 1][1];
166		return c
167	end;
168	is_fall = function(s, x, y, w, dy)
169		local xx
170		if not dy then dy = 0 end
171		local rc = true
172		local water = false
173		local emerg = false
174		if game_state == INTRO then
175			return false
176		end
177		for xx = 0, math.floor((w - 1) / BW) do
178			local bx, by = s:pos2block(x + xx * BW, y)
179			local c = s:block(bx, by)
180			if c == BLOCK or c == SNOW or c == INVI then
181				rc = false
182			elseif c == SEMIBLOCK then
183				c = s:cell(bx, by)
184				if not c.move then c.move = 0 end
185				if c.move < SEMI_TO then
186					rc = false
187				end
188			elseif c == WATER then
189				water = true
190			elseif c == EMERGENCY or c == MINE then
191				hero:state(FLY)
192				return false
193			elseif c == BRIDGE and dy >= 0 and y - dy <= by * BH then
194				rc = false
195			elseif c == ROPE and dy == 0 and y - dy <= by * BH and hero.speed_x ~= 0 then
196				rc = false
197			elseif c == HEART then
198				game_lifes = game_lifes + 1
199				sound.play(bonus_snd)
200				c = s:cell(bx, by)
201				c[1] = 0
202			end
203		end
204		if rc and dy >= 0 and water then
205			hero:state(DROWN)
206			rc = false
207		end
208		return rc
209	end;
210	is_move = function(s, x, y, h)
211		local yy
212		local rc = true
213		if game_state == INTRO then
214			return true
215		end
216		for yy = 0, math.floor((h - 1) / BH) do
217			local c = s:cell(s:pos2block(x, y + yy*BH))
218			if c then
219				if c[1] == BLOCK or (c[1] == SEMIBLOCK and (not c.move or c.move < SEMI_TO)) then
220					return false
221				end
222				if c[1] == SNOW or c[1] == INVI then
223					return false
224				end
225				if c[1] == EMERGENCY or c[1] == MINE then
226					hero:state(FLY)
227					return false
228				elseif c[1] == HEART then
229					game_lifes = game_lifes + 1
230					sound.play(bonus_snd)
231					c[1] = 0
232				end
233			end
234		end
235		return true
236	end;
237	move = function(s, x, y, dx, dy, w, h)
238		local block_x = false
239		local block_y = false
240		local xx = x
241		local yy = y
242		if dx >= 0 then
243			xx = xx + w
244		end
245		if dy >= 0 then
246			yy = yy + h
247		end
248		if s:is_fall(x, yy + dy, w, dy) then
249			y = y + dy
250		else
251			y = math.floor((yy + dy) / BH) * BH
252			if dy >= 0 then
253				y = y - h
254			else
255				y = y + BH
256			end
257			block_y = true
258		end
259
260		if s:is_move(xx + dx, y, h) then
261			x = x + dx
262		else
263			x = math.floor((xx + dx) / BW) * BW
264			if dx >= 0 then
265				x = x - w
266			else
267				x = x + BW
268			end
269			block_x = true
270		end
271		if x < -hero.w / 2 and not map:mirror() then
272			x = -hero.w / 2
273		end
274		if dx >= 0 then
275			x = math.floor(x)
276		else
277			x = math.ceil(x)
278		end
279
280		y = math.floor(y)
281		return x, y, block_x, block_y
282	end;
283	life = function(s)
284		local y, x
285		for y = 1, 30 do
286			for x = 1, 40 do
287				local yy
288				local c = s.map[y][x]
289				if c.move then c.move = c.move + 1 end
290
291				if c[1] == MINE and not c.activated then
292					if hero:distance(x * BW + BW / 2, y * BH + BH /2) < MINE_DIST then
293						c.activated = true
294						sound.play(beep_snd)
295						c.move = 0
296					end
297				end
298
299				if c[1] == SEMIBLOCK and c.move then
300					if c.move >= SEMI_TO then
301						if not c.y then
302							c.y = (y - 1)* BH
303						end
304						c.y = c.y + (c.move - SEMI_TO) * G;
305						sprite.fill(sprite.screen(), (x - 1) * BW,
306							c.y,
307							16 - 1,
308							16 - 1, SEMICOL);
309					end
310					if c.move and c.y and c.y > 480 then
311						c[1] = 0
312					end
313				elseif c[1] == EMERGENCY or (c[1] == MINE and c.activated) then
314					if not c.step then
315						sprite.fill(sprite.screen(), (x - 1) * BW, (y - 1) * BH, BW - 1, BH/2, 'yellow');
316					end
317					c.step = not c.step
318				end
319				if c[1] == MINE and c.activated and c.move > MINE_TO then
320					explode.add(map.data, x - 1, y - 1)
321				end
322			end
323		end
324		explode.draw(s.data)
325		if maps[s.nr].life then
326			maps[s.nr].life(s.data)
327		end
328	end;
329	before = function(s)
330		if maps[s.nr].before then
331			maps[s.nr].before(s.data)
332		end
333	end;
334	after = function(s)
335		if maps[s.nr].after then
336			maps[s.nr].after(s.data)
337		end
338	end
339}
340
341ending_txt = {
342	"CONGRATULATIONS!",
343	"YOU HELPED THE CAT TO ESCAPE THE EVIL STATION!",
344	"THANK YOU FOR YOUR EFFORTS!",
345	":)",
346	"CODE: PETER KOSYH",
347	"ENGINE: PETER KOSYH",
348	"http://instead.syscall.ru",
349	"LEVELS: PETER KOSYH AND ANDREW LOBANOV",
350	"MUSIC: RoccoW",
351	"INPIRED BY GAMES:",
352	"Snoopy (1984)",
353	"Sqrzx",
354	"Giana's Return...",
355	"YOU CAN ENTER LEVEL NUMBER WITH KEYS 0..9",
356	"AND PRESS RETURN",
357}
358
359
360explode = {
361	add = function(s, x, y, m)
362		if not s.mines then
363			s.mines = {}
364		end
365		local v = {}
366		v.x = x * BW + BW / 2 - 2
367		v.y = y * BH + BH / 2 - 2
368		v.step = 0
369		local c = map:cell(x, y)
370		c[1] = 0
371		sound.play(boom_snd)
372		if m then
373			table.insert(m, v)
374		else
375			table.insert(s.mines, v)
376		end
377	end;
378	draw = function(s)
379		local SP = 8
380		if not s.mines then return end
381		local k,v
382		local mines = {}
383		for k, v in ipairs(s.mines) do
384			local x, y = v.x, v.y
385			local xx, yy
386			local p = v.step
387			local i
388			if p then
389				v.step = v.step + 1
390				p = v.step
391				for i = 1, 8 do
392					if not v[i] then
393						xx = x + p * SP * math.cos(3.14 * i / 4)
394						yy = y + p * SP * math.sin(3.14 * i / 4)
395						sprite.fill(sprite.screen(), xx, yy, 4, 4, 'black')
396						local c = map:cell(map:pos2block(xx, yy))
397						if c and c[1] ~= 0 and c[1] and c[1] ~= ' ' and c[1] ~= HEART then
398							v[i] = true
399						elseif hero:collision(xx, yy, 4, 4) then
400							hero:state(FLY)
401						end
402						if c and (c[1] == EMERGENCY or c[1] == MINE) then
403							local x1, y1 = map:pos2block(xx, yy)
404							explode.add(s, x1, y1, mines)
405						end
406					end
407				end
408				if p * SP > 40 * BW then
409					v.step = false
410				else
411					table.insert(mines, v)
412				end
413			end
414		end;
415		s.mines = mines
416	end
417}
418laser = {
419	on = function(x, y, len)
420		local w, h
421		local c = 'red'
422		if rnd(50) > 25 then
423			c = 'yellow'
424		end
425		if len > 0 then
426			x = x * BW + BW / 2 - 2
427			y = y * BH
428			w = 3
429			h = len * BH
430		else
431			x = x * BW
432			y = y * BH + BH/2 - 2
433			w = -len * BW
434			h = 3
435		end
436		sprite.fill(sprite.screen(), x, y, w, h, c)
437		laser_play()
438		if hero:collision(x, y, w, h) then
439			hero:state(FLY)
440		end
441	end;
442	off = function()
443		laser_mute();
444	end
445}
446
447snake = {
448	step = function(s)
449		local k, v
450		local dx = 0
451		local dy = 0
452		if not s.pos then s.pos = 1 end
453		local to = s.path[s.pos]
454		local x, y = s.snake[1][1], s.snake[1][2]
455		if to[1] == x and to[2] == y then
456			s.pos = s.pos + 1
457			to = s.path[s.pos]
458			if s.pos > #s.path then
459				s.pos = 1
460				to = s.path[s.pos]
461			end
462		end
463		if to[1] < x then dx = -1 elseif to[1] > x then dx = 1 end
464		if to[2] < y then dy = -1 elseif to[2] > y then dy = 1 end
465
466
467		x = x + dx
468		y = y + dy
469
470		for k, v in ipairs(s.snake) do
471			local ox, oy = v[1], v[2]
472			v[1], v[2] = x, y
473			x, y = ox, oy
474		end
475	end;
476	draw = function(s)
477		local k, v
478		for k, v in ipairs(s.snake) do
479			if k == 1 then
480				sprite.fill(sprite.screen(), v[1] * BW, v[2] * BH, BW, BH, 'yellow');
481				if not snake_step then
482					sprite.fill(sprite.screen(), v[1] * BW + 2, v[2] * BH + 2, BW - 4, BH - 4, 'red');
483				end
484				snake_step = not snake_step
485			else
486				sprite.fill(sprite.screen(), v[1] * BW, v[2] * BH, BW - 1, BH - 1, 'crimson');
487			end
488			if hero:collision(v[1] * BW, v[2] * BH, BW, BH) then
489				hero:state(FLY)
490			end
491		end
492	end;
493}
494lift = {
495	activate = function(s, x, y, w, dir, u, d)
496		s.y = y
497		s.x = x
498		s.dir = dir
499		s.w = w
500		s.hi = u
501		s.low = d
502	end;
503	draw = function(s)
504		local x, y
505		for x = s.x, s.x + s.w - 1 do
506			local c = map:cell(x, math.floor(s.y))
507			c[1] = 0
508		end
509		local on = false
510
511		if s.dir > 0 and hero:collision(s.x * BW, math.floor(s.y * BH) - BH, s.w * BW, BH)
512			and hero.y + hero.h <= s.y * BH and
513			map:is_fall(hero.x, hero.y + hero.h, hero.w, s.dir)
514			then
515			on = true
516		end
517		s.y = s.y + s.dir
518		if math.floor(s.y) < s.hi then
519			s.dir = -1 * s.dir
520			s.y = s.hi
521		end
522
523		if math.floor(s.y) > s.low then
524			s.dir = -1 * s.dir
525			s.y = s.low
526		end
527
528		for x = s.x, s.x + s.w - 1 do
529			local c = map:cell(x, math.floor(s.y))
530			c[1] = BRIDGE
531		end
532		if s.dir < 0 and  hero:collision(s.x * BW, math.floor(s.y * BH), s.w * BW, BH) and
533			hero.y + hero.h - BH < s.y * BH
534			then
535			on = true
536		end
537		if on then
538			map:move(hero.x, hero.y, 0, s.y * BH - (hero.y + hero.h), hero.w, hero.h)
539			hero.y = math.floor(s.y) * BH - hero.h
540		end
541	end
542}
543dofile "levels.lua"
544dofile "end.lua"