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"