1
2 /**
3 *
4 * @file jj1bonuslevel.cpp
5 *
6 * Part of the OpenJazz project
7 *
8 * @par History:
9 * - 23rd August 2005: Created bonus.c
10 * - 3rd February 2009: Renamed bonus.c to bonus.cpp
11 * - 1st August 2012: Renamed bonus.cpp to jj1bonuslevel.cpp
12 *
13 * @par Licence:
14 * Copyright (c) 2005-2017 Alister Thomson
15 *
16 * OpenJazz is distributed under the terms of
17 * the GNU General Public License, version 2.0
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 *
23 * @par Description:
24 * Deals with the loading, running and freeing of bonus levels.
25 *
26 */
27
28
29 #include "jj1bonuslevelplayer/jj1bonuslevelplayer.h"
30 #include "jj1bonuslevel.h"
31
32 #include "game/game.h"
33 #include "game/gamemode.h"
34 #include "io/controls.h"
35 #include "io/file.h"
36 #include "io/gfx/font.h"
37 #include "io/gfx/paletteeffects.h"
38 #include "io/gfx/sprite.h"
39 #include "io/gfx/video.h"
40 #include "io/sound.h"
41 #include "util.h"
42
43 #include <string.h>
44
45
46 /**
47 * Load sprites.
48 *
49 * @return Error code
50 */
loadSprites()51 int JJ1BonusLevel::loadSprites () {
52
53 File *file;
54 unsigned char* pixels;
55 int pos, maskLength, pixelsLength;
56 int width, height;
57 int count;
58
59 try {
60
61 file = new File("BONUS.000", false);
62
63 } catch (int e) {
64
65 return e;
66
67 }
68
69 file->seek(2, true);
70
71 sprites = file->loadShort(256);
72 spriteSet = new Sprite[sprites];
73
74 for (count = 0; count < sprites; count++) {
75
76 // Load dimensions
77 width = file->loadShort(SW);
78 height = file->loadShort(SH);
79
80 pixelsLength = file->loadShort();
81 maskLength = file->loadShort();
82
83 // Sprites can be either masked or not masked.
84 if (pixelsLength != 0xFFFF) {
85
86 // Masked
87 width <<= 2;
88
89 pos = file->tell() + (pixelsLength << 2) + maskLength;
90
91 // Read scrambled, masked pixel data
92 pixels = file->loadPixels(width * height, 0);
93 spriteSet[count].setPixels(pixels, width, height, 0);
94
95 delete[] pixels;
96
97 file->seek(pos, true);
98
99 } else if (width) {
100
101 // Not masked
102
103 // Read pixel data
104 pixels = file->loadBlock(width * height);
105 spriteSet[count].setPixels(pixels, width, height, 0);
106
107 delete[] pixels;
108
109 } else {
110
111 // Zero-length sprite
112
113 // Create blank sprite
114 spriteSet[count].clearPixels();
115
116 }
117
118 }
119
120 delete file;
121
122 return E_NONE;
123
124 }
125
126
127 /**
128 * Load the tileset.
129 *
130 * @param fileName Name of the file containing the tileset
131 *
132 * @return Error code
133 */
loadTiles(char * fileName)134 int JJ1BonusLevel::loadTiles (char *fileName) {
135
136 File *file;
137 unsigned char *pixels;
138 unsigned char *sorted;
139 int count, x, y;
140
141 try {
142
143 file = new File(fileName, false);
144
145 } catch (int e) {
146
147 return e;
148
149 }
150
151 // Load background
152 pixels = file->loadRLE(832 * 20);
153 sorted = new unsigned char[512 * 20];
154
155 for (count = 0; count < 20; count++) memcpy(sorted + (count * 512), pixels + (count * 832), 512);
156
157 background = createSurface(sorted, 512, 20);
158
159 delete[] sorted;
160 delete[] pixels;
161
162 // Load palette
163 file->loadPalette(palette);
164
165 // Load tile graphics
166 pixels = file->loadRLE(1024 * 60);
167 tileSet = createSurface(pixels, 32, 32 * 60);
168
169 // Create mask
170 for (count = 0; count < 60; count++) {
171
172 memset(mask[count], 0, 64);
173
174 for (y = 0; y < 32; y++) {
175
176 for (x = 0; x < 32; x++) {
177
178 if ((pixels[(count << 10) + (y << 5) + x] & 240) == 192)
179 mask[count][((y << 1) & 56) + ((x >> 2) & 7)] = 1;
180
181 }
182
183 }
184
185 }
186
187 delete[] pixels;
188
189 delete file;
190
191 return E_NONE;
192
193 }
194
195
196 /**
197 * Create a JJ1 bonus level.
198 *
199 * @param owner The current game
200 * @param fileName Name of the file containing the level data.
201 * @param multi Whether or not the level will be multi-player
202 */
JJ1BonusLevel(Game * owner,char * fileName,bool multi)203 JJ1BonusLevel::JJ1BonusLevel (Game* owner, char * fileName, bool multi) : Level(owner) {
204
205 Anim* pAnims[BPANIMS];
206 File *file;
207 unsigned char *buffer;
208 char *string, *fileString;
209 int count, x, y;
210
211
212 try {
213
214 font = new Font(true);
215
216 } catch (int e) {
217
218 throw e;
219
220 }
221
222 try {
223
224 file = new File(fileName, false);
225
226 } catch (int e) {
227
228 delete font;
229
230 throw e;
231
232 }
233
234 // Load sprites
235 count = loadSprites();
236
237 if (count < 0) {
238
239 delete file;
240 delete font;
241
242 throw count;
243
244 }
245
246
247 // Load tileset
248
249 file->seek(90, true);
250 string = file->loadString();
251 fileString = createFileName(string, 0);
252 x = loadTiles(fileString);
253 delete[] string;
254 delete[] fileString;
255
256 if (x != E_NONE) throw x;
257
258
259 // Load music
260
261 file->seek(121, true);
262 fileString = file->loadString();
263 playMusic(fileString);
264 delete[] fileString;
265
266
267 // Load animations
268
269 file->seek(134, true);
270 buffer = file->loadBlock(BANIMS << 6);
271
272 // Create animation set based on that data
273 for (count = 0; count < BANIMS; count++) {
274
275 animSet[count].setData(buffer[(count << 6) + 6],
276 buffer[count << 6], buffer[(count << 6) + 1],
277 buffer[(count << 6) + 3], buffer[(count << 6) + 4],
278 buffer[(count << 6) + 2], buffer[(count << 6) + 5]);
279
280 for (y = 0; y < buffer[(count << 6) + 6]; y++) {
281
282 // Get frame
283 x = buffer[(count << 6) + 7 + y];
284 if (x > sprites) x = sprites;
285
286 // Assign sprite and vertical offset
287 animSet[count].setFrame(y, true);
288 animSet[count].setFrameData(spriteSet + x,
289 buffer[(count << 6) + 26 + y], buffer[(count << 6) + 45 + y]);
290
291 }
292
293 }
294
295 delete[] buffer;
296
297
298 // Load tiles
299
300 file->seek(2694, true);
301 buffer = file->loadRLE(BLW * BLH);
302
303 for (y = 0; y < BLH; y++) {
304
305 for (x = 0; x < BLW; x++) {
306
307 grid[y][x].tile = buffer[x + (y * BLW)];
308
309 if (grid[y][x].tile > 59) grid[y][x].tile = 59;
310
311 }
312
313 }
314
315 delete[] buffer;
316
317
318 file->skipRLE(); // Mysterious block
319
320
321 // Load events
322
323 buffer = file->loadRLE(BLW * BLH);
324
325 for (y = 0; y < BLW; y++) {
326
327 for (x = 0; x < BLH; x++) {
328
329 grid[y][x].event = buffer[x + (y * BLW)];
330
331 }
332
333 }
334
335 delete[] buffer;
336
337
338 file->seek(178, false);
339
340 // Set the tick at which the level will end
341 endTime = file->loadShort() * 1000;
342
343
344 // Number of gems to collect
345 items = file->loadShort();
346
347
348 // The players' coordinates
349 x = file->loadShort();
350 y = file->loadShort();
351
352 // Generate player's animation set references
353
354 for (count = 0; count < BPANIMS; count++) pAnims[count] = animSet + count;
355
356
357 createLevelPlayers(LT_JJ1BONUS, pAnims, NULL, false, x, y);
358
359
360 delete file;
361
362
363 // Palette animations
364
365 // Spinny whirly thing
366 paletteEffects = new RotatePaletteEffect(112, 16, F32, NULL);
367
368 // Track sides
369 paletteEffects = new RotatePaletteEffect(192, 16, F32, paletteEffects);
370
371 // Bouncy things
372 paletteEffects = new RotatePaletteEffect(240, 16, F32, paletteEffects);
373
374
375 // Adjust panelBigFont to use bonus level palette
376 panelBigFont->mapPalette(0, 32, 15, -16);
377
378
379 multiplayer = multi;
380
381
382 return;
383
384 }
385
386
387 /**
388 * Delete the JJ1 bonus level.
389 */
~JJ1BonusLevel()390 JJ1BonusLevel::~JJ1BonusLevel () {
391
392 // Restore panelBigFont palette
393 panelBigFont->restorePalette();
394
395 SDL_FreeSurface(tileSet);
396
397 delete[] spriteSet;
398
399 delete font;
400
401 return;
402
403 }
404
405
406 /**
407 * Determine whether or not the given point is in the event area of its tile.
408 *
409 * @param x X-coordinate
410 * @param y Y-coordinate
411 *
412 * @return True if in the event area
413 */
isEvent(fixed x,fixed y)414 bool JJ1BonusLevel::isEvent (fixed x, fixed y) {
415
416 return ((x & 32767) > F12) && ((x & 32767) < F20) &&
417 ((y & 32767) > F12) && ((y & 32767) < F20);
418
419 }
420
421
422 /**
423 * Determine whether or not the given point is solid.
424 *
425 * @param x X-coordinate
426 * @param y Y-coordinate
427 *
428 * @return Solidity
429 */
checkMask(fixed x,fixed y)430 bool JJ1BonusLevel::checkMask (fixed x, fixed y) {
431
432 JJ1BonusLevelGridElement *ge;
433
434 ge = grid[FTOT(y) & 255] + (FTOT(x) & 255);
435
436 // Hand
437 if ((ge->event == 3) && isEvent(x, y)) return true;
438
439 // Check the mask in the tile in question
440 return mask[ge->tile][((y >> 9) & 56) + ((x >> 12) & 7)];
441
442 }
443
444
445 /**
446 * Interpret data received from client/server
447 *
448 * @param buffer Received data
449 */
receive(unsigned char * buffer)450 void JJ1BonusLevel::receive (unsigned char* buffer) {
451
452 switch (buffer[1]) {
453
454 case MT_L_PROP:
455
456 if (buffer[2] == 2) {
457
458 if (stage == LS_NORMAL)
459 endTime += buffer[3] * 1000;
460
461 }
462
463 break;
464
465 case MT_L_GRID:
466
467 if (buffer[4] == 0) grid[buffer[3]][buffer[2]].tile = buffer[5];
468 else if (buffer[4] == 2)
469 grid[buffer[3]][buffer[2]].event = buffer[5];
470
471 break;
472
473 case MT_L_STAGE:
474
475 stage = LevelStage(buffer[2]);
476
477 break;
478
479 }
480
481 return;
482
483 }
484
485
486 /**
487 * Level iteration.
488 *
489 * @return Error code
490 */
step()491 int JJ1BonusLevel::step () {
492
493 JJ1BonusLevelPlayer* bonusPlayer;
494 fixed playerX, playerY;
495 int gridX, gridY;
496 int count;
497
498 // Check if time has run out
499 if (ticks > endTime) return LOST;
500
501
502 // Apply controls to local player
503 for (count = 0; count < PCONTROLS; count++)
504 localPlayer->setControl(count, controls.getState(count));
505
506 // Process players
507 for (count = 0; count < nPlayers; count++) {
508
509 bonusPlayer = players[count].getJJ1BonusLevelPlayer();
510
511 playerX = bonusPlayer->getX();
512 playerY = bonusPlayer->getY();
513
514 bonusPlayer->step(ticks, 16, this);
515
516 if ((bonusPlayer->getZ() < FH) && isEvent(playerX, playerY)) {
517
518 gridX = FTOT(playerX) & 255;
519 gridY = FTOT(playerY) & 255;
520
521 switch (grid[gridY][gridX].event) {
522
523 case 1: // Extra time
524
525 addTimer(60);
526 grid[gridY][gridX].event = 0;
527
528 break;
529
530 case 2: // Gem
531
532 bonusPlayer->addGem();
533 grid[gridY][gridX].event = 0;
534
535 if (bonusPlayer->getGems() >= items) {
536
537 players[count].addLife();
538
539 return WON;
540
541 }
542
543 break;
544
545 case 4: // Exit
546
547 return LOST;
548
549 default:
550
551 // Do nothing
552
553 break;
554
555 }
556
557 }
558
559 }
560
561 direction = localPlayer->getJJ1BonusLevelPlayer()->getDirection();
562
563 return E_NONE;
564
565 }
566
567
568 /**
569 * Draw the level.
570 */
draw()571 void JJ1BonusLevel::draw () {
572
573 JJ1BonusLevelPlayer *bonusPlayer;
574 unsigned char* row;
575 Sprite* sprite;
576 SDL_Rect dst;
577 fixed playerX, playerY, playerSin, playerCos;
578 fixed distance, fwdX, fwdY, nX, sideX, sideY;
579 int levelX, levelY;
580 int x, y;
581
582
583 // Draw the background
584
585 for (x = -(direction & 1023); x < canvasW; x += background->w) {
586
587 dst.x = x;
588 dst.y = (canvasH >> 1) - 4;
589 SDL_BlitSurface(background, NULL, canvas, &dst);
590
591 }
592
593 x = 171;
594
595 for (y = (canvasH >> 1) - 5; (y >= 0) && (x > 128); y--) drawRect(0, y, canvasW, 1, x--);
596
597 if (y > 0) drawRect(0, 0, canvasW, y + 1, 128);
598
599
600 bonusPlayer = localPlayer->getJJ1BonusLevelPlayer();
601
602
603 // Draw the ground
604
605 playerX = bonusPlayer->getX();
606 playerY = bonusPlayer->getY();
607 playerSin = fSin(direction);
608 playerCos = fCos(direction);
609
610 if (SDL_MUSTLOCK(canvas)) SDL_LockSurface(canvas);
611
612 for (y = 1; y <= (canvasH >> 1) - 15; y++) {
613
614 distance = DIV(ITOF(800), ITOF(92) - (ITOF(y * 84) / ((canvasH >> 1) - 16)));
615 sideX = MUL(distance, playerCos);
616 sideY = MUL(distance, playerSin);
617 fwdX = playerX + MUL(distance - F16, playerSin) - (sideX >> 1);
618 fwdY = playerY - MUL(distance - F16, playerCos) - (sideY >> 1);
619
620 row = ((unsigned char *)(canvas->pixels)) + (canvas->pitch * (canvasH - y));
621
622 for (x = 0; x < canvasW; x++) {
623
624 nX = ITOF(x) / canvasW;
625
626 levelX = FTOI(fwdX + MUL(nX, sideX));
627 levelY = FTOI(fwdY + MUL(nX, sideY));
628
629 row[x] = ((unsigned char *)(tileSet->pixels))
630 [(grid[ITOT(levelY) & 255][ITOT(levelX) & 255].tile << 10) +
631 ((levelY & 31) * tileSet->pitch) + (levelX & 31)];
632
633 }
634
635 }
636
637 if (SDL_MUSTLOCK(canvas)) SDL_UnlockSurface(canvas);
638
639
640 // Draw nearby events
641
642 for (y = -6; y < 6; y++) {
643
644 fixed sY = TTOF(((direction - FQ) & 512)? y: -y) + F16 - (playerY & 32767);
645
646 for (x = -6; x < 6; x++) {
647
648 fixed sX = TTOF((direction & 512)? x: -x) + F16 - (playerX & 32767);
649
650 fixed divisor = F16 + MUL(sX, playerSin) - MUL(sY, playerCos);
651
652 if (FTOI(divisor) > 8) {
653
654 switch (grid[((((direction - FQ) & 512)? y: -y) + FTOT(playerY)) & 255][(((direction & 512)? x: -x) + FTOT(playerX)) & 255].event) {
655
656 case 0: // No event
657
658 sprite = NULL;
659
660 break;
661
662 case 1: // Extra time
663
664 sprite = spriteSet + 46;
665
666 break;
667
668 case 2: // Gem
669
670 sprite = spriteSet + 47;
671
672 break;
673
674 case 3: // Hand
675
676 sprite = spriteSet + 48;
677
678 break;
679
680 case 4: // Exit
681
682 sprite = spriteSet + 49;
683
684 break;
685
686 case 5: // Bounce
687
688 sprite = spriteSet + 50;
689
690 break;
691
692 default:
693
694 sprite = spriteSet + 14;
695
696 break;
697
698 }
699
700 if (sprite) {
701
702 nX = DIV(MUL(sX, playerCos) + MUL(sY, playerSin), divisor);
703 dst.x = FTOI(nX * canvasW) + (canvasW >> 1);
704 dst.y = canvasH >> 1;
705 sprite->drawScaled(dst.x, dst.y, DIV(F64 * canvasW / SW, divisor));
706
707 }
708
709 }
710
711 }
712
713 }
714
715
716 // Show the player
717 bonusPlayer->draw(ticks);
718
719
720 // Show gem count
721 font->showString("*", 0, 0);
722 font->showNumber(bonusPlayer->getGems() / 10, 50, 0);
723 font->showNumber(bonusPlayer->getGems() % 10, 68, 0);
724 font->showString("/", 65, 0);
725 font->showNumber(items, 124, 0);
726
727
728 // Show time remaining
729 if (endTime > ticks) x = (endTime - ticks) / 1000;
730 else x = 0;
731 font->showNumber(x / 60, 250, 0);
732 font->showString(":", 247, 0);
733 font->showNumber((x / 10) % 6, 274, 0);
734 font->showNumber(x % 10, 291, 0);
735
736
737 return;
738
739 }
740
741
742 /**
743 * Play the level.
744 *
745 * @return Error code
746 */
play()747 int JJ1BonusLevel::play () {
748
749 bool pmenu, pmessage;
750 int option;
751 unsigned int returnTime;
752 int ret;
753
754
755 tickOffset = globalTicks;
756 ticks = T_STEP;
757 steps = 0;
758
759 pmessage = pmenu = false;
760 option = 0;
761
762 returnTime = 0;
763
764 video.setPalette(palette);
765
766 while (true) {
767
768 ret = loop(pmenu, option, pmessage);
769
770 if (ret < 0) return ret;
771
772
773 // Check if level has been won
774 if (returnTime && (ticks > returnTime)) {
775
776 if (localPlayer->getJJ1BonusLevelPlayer()->getGems() >= items) {
777
778 if (playScene("BONUS.0SC") == E_QUIT) return E_QUIT;
779
780 return WON;
781
782 }
783
784 return LOST;
785
786 }
787
788
789 // Process frame-by-frame activity
790
791 while ((getTimeChange() >= T_STEP) && (stage == LS_NORMAL)) {
792
793 ret = step();
794 steps++;
795
796 if (ret < 0) return ret;
797 else if (ret) {
798
799 stage = LS_END;
800 paletteEffects = new WhiteOutPaletteEffect(T_BONUS_END, paletteEffects);
801 returnTime = ticks + T_BONUS_END;
802
803 }
804
805 }
806
807
808 // Draw the graphics
809
810 if ((ticks < returnTime) && !paused) direction += (ticks - prevTicks) * T_BONUS_END / (returnTime - ticks);
811
812 draw();
813
814
815 // If paused, draw "PAUSE"
816 if (pmessage && !pmenu)
817 font->showString("pause", (canvasW >> 1) - 44, 32);
818
819
820 // Draw statistics, menu etc.
821 drawOverlay(0, pmenu, option, 0, 31, 16);
822
823 }
824
825 return E_NONE;
826
827 }
828
829
830