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