1
2 #include "nx.h"
3 #include "map.h"
4 #include "map.fdh"
5 #include "libretro_shared.h"
6 #include "../extract-auto/cachefiles.h"
7
8 stMap map;
9
10 extern MapRecord stages[MAX_STAGES];
11 int num_stages;
12
13 #define MAX_BACKDROPS 32
14 NXSurface *backdrop[MAX_BACKDROPS];
15
16 // for FindObject--finding NPC's by ID2
17 Object *ID2Lookup[65536];
18
19 uint8_t tilecode[MAX_TILES]; // tile codes for every tile in current tileset
20 uint32_t tileattr[MAX_TILES]; // tile attribute bits for every tile in current tileset
21 uint32_t tilekey[MAX_TILES] = {0, 0, 64, 2, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 39, 48, 103, 34, 32, 33, 32, 32, 32, 32, 32, 33, 33, 33, 33, 544, 544, 544, 544, 544, 544, 544, 544, 32, 32, 32, 32, 32, 32, 32, 32, 160, 39, 176, 32, 34, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 672, 672, 672, 672, 672, 672, 672, 672, 32, 32, 32, 32, 32, 32, 32, 32, 256, 256, 256, 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 416, 416, 416, 416, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // mapping from tile codes -> tile attributes
22
23
24 // load stage "stage_no", this entails loading the map (pxm), enemies (pxe), tileset (pbm),
25 // tile attributes (pxa), and script (tsc).
load_stage(int stage_no)26 bool load_stage(int stage_no)
27 {
28 char stage[MAXPATHLEN];
29 char fname[MAXPATHLEN];
30
31 char slash;
32 #ifdef _WIN32
33 slash = '\\';
34 #else
35 slash = '/';
36 #endif
37
38 NX_LOG(" >> Entering stage %d: '%s'.\n", stage_no, stages[stage_no].stagename);
39 game.curmap = stage_no; // do it now so onspawn events will have it
40
41 if (Tileset::Load(stages[stage_no].tileset))
42 return 1;
43
44 // get the base name of the stage without extension
45 const char *mapname = stages[stage_no].filename;
46 if (!strcmp(mapname, "lounge")) mapname = "Lounge";
47 snprintf(stage, sizeof(stage), "%s%c%s", stage_dir, slash, mapname);
48
49 snprintf(fname, sizeof(fname), "%s.pxm", stage);
50 if (load_map(fname)) return 1;
51
52 snprintf(fname, sizeof(fname), "%s%c%s.pxa", stage_dir, slash, tileset_names[stages[stage_no].tileset]);
53 if (load_tileattr(fname)) return 1;
54
55 snprintf(fname, sizeof(fname), "%s.pxe", stage);
56 if (load_entities(fname)) return 1;
57
58 snprintf(fname, sizeof(fname), "%s.tsc", stage);
59 if (tsc_load(fname, SP_MAP) == -1) return 1;
60
61 map_set_backdrop(stages[stage_no].bg_no);
62 map.scrolltype = stages[stage_no].scroll_type;
63 map.motionpos = 0;
64
65 //hack to show nice backdrop in menu, like nicalis
66 stages[0].bg_no=9;
67 stages[0].scroll_type=BK_FASTLEFT_LAYERS;
68
69 return 0;
70 }
71
72 /*
73 void c------------------------------() {}
74 */
75
76 // load a PXM map
load_map(const char * fname)77 bool load_map(const char *fname)
78 {
79 CFILE *fp;
80 int x, y;
81
82 NX_LOG("load_map: %s\n", fname);
83
84 fp = copen(fname, "rb");
85 if (!fp)
86 {
87 NX_ERR("load_map: no such file: '%s'\n", fname);
88 return 1;
89 }
90
91 if (!cverifystring(fp, "PXM"))
92 {
93 NX_ERR("load_map: invalid map format: '%s'\n", fname);
94 return 1;
95 }
96
97 memset(&map, 0, sizeof(map));
98
99 cgetc(fp);
100 map.xsize = cgeti(fp);
101 map.ysize = cgeti(fp);
102
103 if (map.xsize > MAP_MAXSIZEX || map.ysize > MAP_MAXSIZEY)
104 {
105 NX_ERR("load_map: map is too large -- size %dx%d but max is %dx%d\n", map.xsize, map.ysize, MAP_MAXSIZEX, MAP_MAXSIZEY);
106 cclose(fp);
107 return 1;
108 }
109 else
110 {
111 NX_LOG("load_map: level size %dx%d\n", map.xsize, map.ysize);
112 }
113
114 for(y=0;y<map.ysize;y++)
115 for(x=0;x<map.xsize;x++)
116 {
117 map.tiles[x][y] = cgetc(fp);
118 }
119
120 cclose(fp);
121
122 map.maxxscroll = (((map.xsize * TILE_W) - SCREEN_WIDTH) - 8) << CSF;
123 map.maxyscroll = (((map.ysize * TILE_H) - SCREEN_HEIGHT) - 8) << CSF;
124
125 NX_LOG("load_map: '%s' loaded OK! - %dx%d\n", fname, map.xsize, map.ysize);
126 return 0;
127 }
128
129
130 // load a PXE (entity list for a map)
load_entities(const char * fname)131 bool load_entities(const char *fname)
132 {
133 CFILE *fp;
134 int i;
135 int nEntities;
136
137 // gotta destroy all objects before creating new ones
138 Objects::DestroyAll(false);
139 FloatText::ResetAll();
140
141 NX_LOG("load_entities: reading in %s\n", fname);
142 // now we can load in the new objects
143 fp = copen(fname, "rb");
144 if (!fp)
145 {
146 NX_ERR("load_entities: no such file: '%s'\n", fname);
147 return 1;
148 }
149
150 if (!cverifystring(fp, "PXE"))
151 {
152 NX_ERR("load_entities: not a PXE: '%s'\n", fname);
153 return 1;
154 }
155
156 cgetc(fp);
157 nEntities = cgetl(fp);
158
159 for(i=0;i<nEntities;i++)
160 {
161 int x = cgeti(fp);
162 int y = cgeti(fp);
163 int id1 = cgeti(fp);
164 int id2 = cgeti(fp);
165 int type = cgeti(fp);
166 int flags = cgeti(fp);
167
168 int dir = (flags & FLAG_FACES_RIGHT) ? RIGHT : LEFT;
169
170 //lprintf(" %d: [%d, %d]\t id1=%d\t id2=%d Type %d flags %04x\n", i, x, y, id1, id2, type, flags);
171
172 // most maps have apparently garbage entities--invisible do-nothing objects??
173 // i dunno but no point in spawning those...
174 if (type || id1 || id2 || flags)
175 {
176 bool addobject = false;
177
178 // check if object is dependent on a flag being set/not set
179 if (flags & FLAG_APPEAR_ON_FLAGID)
180 {
181 if (game.flags[id1])
182 addobject = true;
183 }
184 else if (flags & FLAG_DISAPPEAR_ON_FLAGID)
185 {
186 if (!game.flags[id1])
187 addobject = true;
188 }
189 else
190 addobject = true;
191
192 if (addobject)
193 {
194 // hack for chests (can we do this elsewhere?)
195 if (type == OBJ_CHEST_OPEN) y++;
196 // hack for skydragon in Fall end cinematic
197 if (type == OBJ_SKY_DRAGON && id2 == 230) y++;
198
199 Object *o = CreateObject((x * TILE_W) << CSF, \
200 (y * TILE_H) << CSF, type,
201 0, 0, dir, NULL, CF_NO_SPAWN_EVENT);
202
203 o->id1 = id1;
204 o->id2 = id2;
205 o->flags |= flags;
206
207 ID2Lookup[o->id2] = o;
208
209 // now that it's all set up, execute OnSpawn,
210 // since we didn't do it in CreateObject.
211 o->OnSpawn();
212 }
213 }
214 }
215
216 //NX_LOG("load_entities: loaded %d objects\n", nEntities);
217 cclose(fp);
218 return 0;
219 }
220
221 // loads a pxa (tileattr) file
load_tileattr(const char * fname)222 bool load_tileattr(const char *fname)
223 {
224 CFILE *fp;
225 int i;
226 unsigned char tc;
227
228 map.nmotiontiles = 0;
229
230 NX_LOG("load_pxa: reading in %s\n", fname);
231 fp = copen(fname, "rb");
232 if (!fp)
233 {
234 NX_ERR("load_pxa: no such file: '%s'\n", fname);
235 return 1;
236 }
237
238 for(i=0;i<256;i++)
239 {
240 tc = cgetc(fp);
241 tilecode[i] = tc;
242 tileattr[i] = tilekey[tc];
243 //NX_LOG("Tile %02x TC %02x Attr %08x tilekey[%02x] = %08x\n", i, tc, tileattr[i], tc, tilekey[tc]);
244
245 //FIXME: Destroyable star tiles not showing up right now
246 if (tc == 0x43) // destroyable block - have to replace graphics
247 {
248 CopySpriteToTile(SPR_DESTROYABLE, i, 0, 0);
249 }
250
251 // add water currents to animation list
252 if (tileattr[i] & TA_CURRENT)
253 {
254 map.motiontiles[map.nmotiontiles].tileno = i;
255 map.motiontiles[map.nmotiontiles].dir = CVTDir(tc & 3);
256 map.motiontiles[map.nmotiontiles].sprite = SPR_WATER_CURRENT;
257
258 map.nmotiontiles++;
259 NX_LOG("Added tile %02x to animation list, tc=%02x\n", i, tc);
260 }
261 }
262
263 cclose(fp);
264 return 0;
265 }
266
load_stages(void)267 bool load_stages(void)
268 {
269 num_stages = MAX_STAGES;
270
271 return 0;
272 }
273
274
initmapfirsttime(void)275 bool initmapfirsttime(void)
276 {
277 char fname[1024];
278 FILE *fp;
279 int i;
280
281 retro_create_path_string(fname, sizeof(fname), g_dir, "tilekey.dat");
282
283 NX_LOG("initmapfirsttime: loading %s.\n", fname);
284 if (!(fp = fopen(fname, "rb")))
285 {
286 NX_LOG("%s is missing, using default\n", fname);
287 }
288 else
289 {
290 for(i=0;i<256;i++)
291 tilekey[i] = fgetl(fp);
292
293 fclose(fp);
294 }
295 return load_stages();
296 }
297
initmap(void)298 void initmap(void)
299 {
300 map_focus(NULL);
301 map.parscroll_x = map.parscroll_y = 0;
302 }
303
304 /*
305 void c------------------------------() {}
306 */
307
308 // backdrop_no - backdrop # to switch to
map_set_backdrop(int backdrop_no)309 void map_set_backdrop(int backdrop_no)
310 {
311 if (!LoadBackdropIfNeeded(backdrop_no))
312 map.backdrop = backdrop_no;
313 }
314
315
map_draw_backdrop(void)316 void map_draw_backdrop(void)
317 {
318 int x, y;
319
320 if (!backdrop[map.backdrop])
321 {
322 LoadBackdropIfNeeded(map.backdrop);
323 if (!backdrop[map.backdrop])
324 return;
325 }
326
327 switch(map.scrolltype)
328 {
329 case BK_FIXED:
330 map.parscroll_x = 0;
331 map.parscroll_y = 0;
332 break;
333
334 case BK_FOLLOWFG:
335 map.parscroll_x = (map.displayed_xscroll >> CSF);
336 map.parscroll_y = (map.displayed_yscroll >> CSF);
337 break;
338
339 case BK_PARALLAX:
340 map.parscroll_y = (map.displayed_yscroll >> CSF) / 2;
341 map.parscroll_x = (map.displayed_xscroll >> CSF) / 2;
342 break;
343
344 case BK_FASTLEFT: // Ironhead
345 map.parscroll_x += 6;
346 map.parscroll_y = 0;
347 break;
348
349 case BK_FASTLEFT_LAYERS:
350 case BK_FASTLEFT_LAYERS_NOFALLLEFT:
351 {
352 DrawFastLeftLayered();
353 return;
354 }
355 break;
356
357 case BK_HIDE:
358 case BK_HIDE2:
359 case BK_HIDE3:
360 {
361 if (game.curmap == STAGE_KINGS) // intro cutscene
362 ClearScreen(BLACK);
363 else
364 ClearScreen(DK_BLUE);
365 }
366 return;
367
368 default:
369 map.parscroll_x = map.parscroll_y = 0;
370 NX_ERR("map_draw_backdrop: unhandled map scrolling type %d\n", map.scrolltype);
371 break;
372 }
373
374 map.parscroll_x %= backdrop[map.backdrop]->Width();
375 map.parscroll_y %= backdrop[map.backdrop]->Height();
376 int w = backdrop[map.backdrop]->Width();
377 int h = backdrop[map.backdrop]->Height();
378
379 for(y=0;y<SCREEN_HEIGHT+map.parscroll_y; y+=h)
380 {
381 for(x=0;x<SCREEN_WIDTH+map.parscroll_x; x+=w)
382 {
383 DrawSurface(backdrop[map.backdrop], x - map.parscroll_x, y - map.parscroll_y);
384 }
385 }
386 }
387
388 // blit OSide's BK_FASTLEFT_LAYERS
DrawFastLeftLayered(void)389 static void DrawFastLeftLayered(void)
390 {
391 static const int layer_ys[] = { 80, 122, 145, 176, 240 };
392 static const int move_spd[] = { 0, 1, 2, 4, 8 };
393 const int nlayers = sizeof(layer_ys) / sizeof(layer_ys[0]);
394 int y1, y2;
395 int i, x;
396
397 const int W = backdrop[map.backdrop]->Width();
398
399 if (--map.parscroll_x <= -(W*2))
400 map.parscroll_x = 0;
401
402 y1 = x = 0;
403 for(i=0;i<nlayers;i++)
404 {
405 y2 = layer_ys[i];
406
407 if (i) // not the static moon layer?
408 {
409 x = (map.parscroll_x * move_spd[i]) >> 1;
410 x %= W;
411 }
412
413 BlitPatternAcross(backdrop[map.backdrop], x, y1, y1, (y2-y1)+1);
414 y1 = (y2 + 1);
415 }
416 }
417
418
419 // loads a backdrop into memory, if it hasn't already been loaded
LoadBackdropIfNeeded(int backdrop_no)420 static bool LoadBackdropIfNeeded(int backdrop_no)
421 {
422 char fname[MAXPATHLEN];
423 char slash;
424 #ifdef _WIN32
425 slash = '\\';
426 #else
427 slash = '/';
428 #endif
429 // load backdrop now if it hasn't already been loaded
430 if (!backdrop[backdrop_no])
431 {
432 // use chromakey (transparency) on bkwater, all others don't
433 bool use_chromakey = (backdrop_no == 8);
434
435 snprintf(fname, sizeof(fname), "%s%c%s.pbm", data_dir, slash, backdrop_names[backdrop_no]);
436
437 backdrop[backdrop_no] = NXSurface::FromFile(fname, use_chromakey);
438 if (!backdrop[backdrop_no])
439 {
440 NX_ERR("Failed to load backdrop '%s'\n", fname);
441 return 1;
442 }
443 }
444
445 return 0;
446 }
447
map_flush_graphics()448 void map_flush_graphics()
449 {
450 int i;
451
452 for(i=0;i<MAX_BACKDROPS;i++)
453 {
454 delete backdrop[i];
455 backdrop[i] = NULL;
456 }
457
458 // re-copy star files
459 for(i=0;i<256;i++)
460 {
461 if (tilecode[i] == 0x43)
462 {
463 CopySpriteToTile(SPR_DESTROYABLE, i, 0, 0);
464 }
465 }
466 }
467
468
469 /*
470 void c------------------------------() {}
471 */
472
473 // draw rising/falling water from eg Almond etc
map_drawwaterlevel(void)474 void map_drawwaterlevel(void)
475 {
476 // water_sfc: 16 tall at 0
477 // just under: 16 tall at 32
478 // main tile: 32 tall at 16 (yes, overlapping)
479 int water_x, water_y;
480
481 if (!map.waterlevelobject)
482 return;
483
484 water_x = -(map.displayed_xscroll >> CSF);
485 water_x %= SCREEN_WIDTH;
486
487 water_y = (map.waterlevelobject->y >> CSF) - (map.displayed_yscroll >> CSF);
488
489 // draw the surface and just under the surface
490 BlitPatternAcross(backdrop[map.backdrop], water_x, water_y, 0, 16);
491 water_y += 16;
492
493 BlitPatternAcross(backdrop[map.backdrop], water_x, water_y, 32, 16);
494 water_y += 16;
495
496 // draw the rest of the pattern all the way down
497 while(water_y < (SCREEN_HEIGHT-1))
498 {
499 BlitPatternAcross(backdrop[map.backdrop], water_x, water_y, 16, 32);
500 water_y += 32;
501 }
502 }
503
504
505 // draw the map.
506 // if foreground = TA_FOREGROUND, draws the foreground tile layer.
507 // if foreground = 0, draws backdrop and background tiles.
map_draw(uint8_t foreground)508 void map_draw(uint8_t foreground)
509 {
510 int x, y;
511 int mapx, mapy;
512 int blit_x, blit_y, blit_x_start;
513 int scroll_x, scroll_y;
514
515 scroll_x = (map.displayed_xscroll >> CSF);
516 scroll_y = (map.displayed_yscroll >> CSF);
517
518 mapx = (scroll_x / TILE_W);
519 mapy = (scroll_y / TILE_H);
520
521 blit_y = -(scroll_y % TILE_H);
522 blit_x_start = -(scroll_x % TILE_W);
523
524 // MAP_DRAW_EXTRA_Y etc is 1 if resolution is changed to
525 // something not a multiple of TILE_H.
526 for(y=0; y <= (SCREEN_HEIGHT / TILE_H)+MAP_DRAW_EXTRA_Y; y++)
527 {
528 blit_x = blit_x_start;
529
530 for(x=0; x <= (SCREEN_WIDTH / TILE_W)+MAP_DRAW_EXTRA_X; x++)
531 {
532 int t = map.tiles[mapx+x][mapy+y];
533 if ((tileattr[t] & TA_FOREGROUND) == foreground)
534 draw_tile(blit_x, blit_y, t);
535
536 blit_x += TILE_W;
537 }
538
539 blit_y += TILE_H;
540 }
541 }
542
543
544 /*
545 void c------------------------------() {}
546 */
547
548 // map scrolling code
scroll_normal(void)549 void scroll_normal(void)
550 {
551 const int scroll_adj_rate = (0x2000 / map.scrollspeed);
552
553 // how many pixels to let player stray from the center of the screen
554 // before we start scrolling. high numbers let him reach closer to the edges,
555 // low numbers keep him real close to the center.
556 #define P_VARY_FROM_CENTER (64 << CSF)
557
558 if (player->dir == LEFT)
559 {
560 map.scrollcenter_x -= scroll_adj_rate;
561 if (map.scrollcenter_x < -P_VARY_FROM_CENTER)
562 map.scrollcenter_x = -P_VARY_FROM_CENTER;
563 }
564 else
565 {
566 map.scrollcenter_x += scroll_adj_rate;
567 if (map.scrollcenter_x > P_VARY_FROM_CENTER)
568 map.scrollcenter_x = P_VARY_FROM_CENTER;
569 }
570
571 // compute where the map "wants" to be
572 map.target_x = (player->CenterX() + map.scrollcenter_x) - ((SCREEN_WIDTH / 2) << CSF);
573
574 // Y scrolling
575 if (player->lookscroll == UP)
576 {
577 map.scrollcenter_y -= scroll_adj_rate;
578 if (map.scrollcenter_y < -P_VARY_FROM_CENTER) map.scrollcenter_y = -P_VARY_FROM_CENTER;
579 }
580 else if (player->lookscroll == DOWN)
581 {
582 map.scrollcenter_y += scroll_adj_rate;
583 if (map.scrollcenter_y > P_VARY_FROM_CENTER) map.scrollcenter_y = P_VARY_FROM_CENTER;
584 }
585 else
586 {
587 if (map.scrollcenter_y <= -scroll_adj_rate)
588 {
589 map.scrollcenter_y += scroll_adj_rate;
590 }
591 else if (map.scrollcenter_y >= scroll_adj_rate)
592 {
593 map.scrollcenter_y -= scroll_adj_rate;
594 }
595 }
596
597 map.target_y = (player->CenterY() + map.scrollcenter_y) - ((SCREEN_HEIGHT / 2) << CSF);
598 }
599
map_scroll_do(void)600 void map_scroll_do(void)
601 {
602 bool doing_normal_scroll = false;
603
604 if (!map.scroll_locked)
605 {
606 if (map.focus.has_target)
607 { // FON command
608 // this check makes it so if we <FON on an object which
609 // gets destroyed, the scroll stays locked at the last known
610 // position of the object.
611 if (map.focus.target)
612 {
613 Object *t = map.focus.target;
614
615 // Generally we want to focus on the center of the object, not it's UL corner.
616 // But a few objects (Cage in mimiga village) have offset drawpoints
617 // that affect the positioning of the scene. If the object has a drawpoint,
618 // we'll assume it's in an appropriate position, otherwise, we'll try to find
619 // the center ourselves.
620 if (sprites[t->sprite].frame[t->frame].dir[t->dir].drawpoint.equ(0, 0))
621 {
622 map.target_x = map.focus.target->CenterX() - ((SCREEN_WIDTH / 2) << CSF);
623 map.target_y = map.focus.target->CenterY() - ((SCREEN_HEIGHT / 2) << CSF);
624 }
625 else
626 {
627 map.target_x = map.focus.target->x - ((SCREEN_WIDTH / 2) << CSF);
628 map.target_y = map.focus.target->y - ((SCREEN_HEIGHT / 2) << CSF);
629 }
630 }
631 }
632 else
633 {
634 if (!player->hide)
635 {
636 scroll_normal();
637
638 doing_normal_scroll = true;
639 }
640 }
641 }
642
643 map.real_xscroll += (map.target_x - map.real_xscroll) / map.scrollspeed;
644 map.real_yscroll += (map.target_y - map.real_yscroll) / map.scrollspeed;
645
646 map.displayed_xscroll = (map.real_xscroll + map.phase_adj);
647 map.displayed_yscroll = map.real_yscroll; // we don't compensate on Y, because player falls > 2 pixels per frame
648
649 if (doing_normal_scroll)
650 {
651 run_phase_compensator();
652 }
653 else
654 {
655 map.phase_adj -= MAP_PHASE_ADJ_SPEED;
656 if (map.phase_adj < 0) map.phase_adj = 0;
657 }
658
659 map_sanitycheck();
660
661 // do quaketime after sanity check so quake works in
662 // small levels like Shack.
663 if (game.quaketime)
664 {
665 if (!map.scroll_locked)
666 {
667 int pushx, pushy;
668
669 if (game.megaquaketime) // Ballos fight
670 {
671 game.megaquaketime--;
672 pushx = random(-5, 5) << CSF;
673 pushy = random(-3, 3) << CSF;
674 }
675 else
676 {
677 pushx = random(-1, 1) << CSF;
678 pushy = random(-1, 1) << CSF;
679 }
680
681 map.real_xscroll += pushx;
682 map.real_yscroll += pushy;
683 map.displayed_xscroll += pushx;
684 map.displayed_yscroll += pushy;
685 }
686 else
687 {
688 // quake after IronH battle...special case cause we don't
689 // want to show the walls of the arena.
690 int pushy = random(-0x500, 0x500);
691
692 map.real_yscroll += pushy;
693 if (map.real_yscroll < 0) map.real_yscroll = 0;
694 if (map.real_yscroll > (15 << CSF)) map.real_yscroll = (15 << CSF);
695
696 map.displayed_yscroll += pushy;
697 if (map.displayed_yscroll < 0) map.displayed_yscroll = 0;
698 if (map.displayed_yscroll > (15 << CSF)) map.displayed_yscroll = (15 << CSF);
699 }
700
701 game.quaketime--;
702 }
703 }
704
705 // this attempts to prevent jitter most visible when the player is walking on a
706 // long straight stretch. the jitter occurs because map.xscroll and player->x
707 // tend to be out-of-phase, and thus cross over pixel boundaries at different times.
708 // what we do here is try to tweak/fudge the displayed xscroll value by up to 512 subpixels
709 // (1 real pixel), so that it crosses pixel boundaries on exactly the same frame as
710 // the player does.
run_phase_compensator(void)711 void run_phase_compensator(void)
712 {
713 int displayed_phase_offs = (map.displayed_xscroll - player->x) % 512;
714
715 if (displayed_phase_offs != 0)
716 {
717 int phase_offs = abs(map.real_xscroll - player->x) % 512;
718 //debug("%d", phase_offs);
719
720 // move phase_adj towards phase_offs; phase_offs is how far
721 // out of sync we are with the player and so once we reach it
722 // we will compensating exactly.
723 if (map.phase_adj < phase_offs)
724 {
725 map.phase_adj += MAP_PHASE_ADJ_SPEED;
726 if (map.phase_adj > phase_offs)
727 map.phase_adj = phase_offs;
728 }
729 else
730 {
731 map.phase_adj -= MAP_PHASE_ADJ_SPEED;
732 if (map.phase_adj < phase_offs)
733 map.phase_adj = phase_offs;
734 }
735 }
736 }
737
738 /*
739 void c------------------------------() {}
740 */
741
742
743 // scroll position sanity checking
map_sanitycheck(void)744 void map_sanitycheck(void)
745 {
746 #define MAP_BORDER_AMT (8<<CSF)
747 if (map.real_xscroll < MAP_BORDER_AMT) map.real_xscroll = MAP_BORDER_AMT;
748 if (map.real_yscroll < MAP_BORDER_AMT) map.real_yscroll = MAP_BORDER_AMT;
749 if (map.real_xscroll > map.maxxscroll) map.real_xscroll = map.maxxscroll;
750 if (map.real_yscroll > map.maxyscroll) map.real_yscroll = map.maxyscroll;
751
752 if (map.displayed_xscroll < MAP_BORDER_AMT) map.displayed_xscroll = MAP_BORDER_AMT;
753 if (map.displayed_yscroll < MAP_BORDER_AMT) map.displayed_yscroll = MAP_BORDER_AMT;
754 if (map.displayed_xscroll > map.maxxscroll) map.displayed_xscroll = map.maxxscroll;
755 if (map.displayed_yscroll > map.maxyscroll) map.displayed_yscroll = map.maxyscroll;
756 }
757
758
map_scroll_jump(int x,int y)759 void map_scroll_jump(int x, int y)
760 {
761 map.target_x = x - ((SCREEN_WIDTH / 2) << CSF);
762 map.target_y = y - ((SCREEN_HEIGHT / 2) << CSF);
763 map.real_xscroll = map.target_x;
764 map.real_yscroll = map.target_y;
765
766 map.displayed_xscroll = map.real_xscroll;
767 map.displayed_yscroll = map.real_yscroll;
768 map.phase_adj = 0;
769
770 map.scrollcenter_x = map.scrollcenter_y = 0;
771 map_sanitycheck();
772 }
773
774 // lock the scroll in it's current position. the target position will not change,
775 // however if the scroll is moved off the target (really only a quake could do this)
776 // the map will still seek it's old position.
map_scroll_lock(bool lockstate)777 void map_scroll_lock(bool lockstate)
778 {
779 map.scroll_locked = lockstate;
780 if (lockstate)
781 { // why do we do this?
782 map.real_xscroll = map.target_x;
783 map.real_yscroll = map.target_y;
784 }
785 }
786
787 // set the map focus and scroll speed.
788 // if o is specified, focuses on that object.
789 // if o is NULL, focuses on the player.
map_focus(Object * o,int spd)790 void map_focus(Object *o, int spd)
791 {
792 map.focus.target = o;
793 map.focus.has_target = (o != NULL);
794
795 map.scrollspeed = spd;
796 map.scroll_locked = false;
797 }
798
799 /*
800 void c------------------------------() {}
801 */
802
803 // change tile at x,y into newtile while optionally spawning smoke clouds and boomflash
map_ChangeTileWithSmoke(int x,int y,int newtile,int nclouds,bool boomflash,Object * push_behind)804 void map_ChangeTileWithSmoke(int x, int y, int newtile, int nclouds, bool boomflash, Object *push_behind)
805 {
806 if (x < 0 || y < 0 || x >= map.xsize || y >= map.ysize)
807 return;
808
809 map.tiles[x][y] = newtile;
810
811 int xa = ((x * TILE_W) + (TILE_W / 2)) << CSF;
812 int ya = ((y * TILE_H) + (TILE_H / 2)) << CSF;
813 SmokeXY(xa, ya, nclouds, TILE_W/2, TILE_H/2, push_behind);
814
815 if (boomflash)
816 effect(xa, ya, EFFECT_BOOMFLASH);
817 }
818
819
820
map_get_stage_name(int mapno)821 const char *map_get_stage_name(int mapno)
822 {
823 if (mapno == STAGE_KINGS)
824 return "";//Studio Pixel Presents";
825
826 return stages[mapno].stagename;
827 }
828
829 // show map name for "ticks" ticks
map_show_map_name()830 void map_show_map_name()
831 {
832 game.mapname_x = (SCREEN_WIDTH / 2) - (GetFontWidth(map_get_stage_name(game.curmap), 0) / 2);
833 game.showmapnametime = 120;
834 }
835
map_draw_map_name(void)836 void map_draw_map_name(void)
837 {
838 if (game.showmapnametime)
839 {
840 font_draw(game.mapname_x, 84, map_get_stage_name(game.curmap), 0, &bluefont); // Workaround for avoiding diacritics not showing on map location names
841 game.showmapnametime--;
842 }
843 }
844
845
846 // animate all motion tiles
AnimateMotionTiles(void)847 void AnimateMotionTiles(void)
848 {
849 int i;
850 int x_off, y_off;
851
852 for(i=0;i<map.nmotiontiles;i++)
853 {
854 switch(map.motiontiles[i].dir)
855 {
856 case LEFT: y_off = 0; x_off = map.motionpos; break;
857 case RIGHT: y_off = 0; x_off = (TILE_W - map.motionpos); break;
858
859 case UP: x_off = 0; y_off = map.motionpos; break;
860 case DOWN: x_off = 0; y_off = (TILE_H - map.motionpos); break;
861
862 default: x_off = y_off = 0; break;
863 }
864
865 CopySpriteToTile(map.motiontiles[i].sprite, map.motiontiles[i].tileno, x_off, y_off);
866 }
867
868 map.motionpos += 2;
869 if (map.motionpos >= TILE_W) map.motionpos = 0;
870 }
871
872
873 // attempts to find an object with id2 matching the given value else returns NULL
FindObjectByID2(int id2)874 Object *FindObjectByID2(int id2)
875 {
876 Object *result = ID2Lookup[id2];
877
878 if (!result)
879 NX_ERR("FindObjectByID2: no such object %04d\n", id2);
880
881 return result;
882 }
883
884