1
2 /**
3 *
4 * @file level.cpp
5 *
6 * Part of the OpenJazz project
7 *
8 * @par History:
9 * - 23rd August 2005: Created level.c
10 * - 3rd February 2009: Renamed level.c to level.cpp
11 * - 19th July 2009: Created levelframe.cpp from parts of level.cpp
12 * - 19th July 2009: Added parts of levelload.cpp to level.cpp
13 * - 30th March 2010: Created baselevel.cpp from parts of level.cpp and
14 * levelframe.cpp
15 * - 1st August 2012: Renamed baselevel.cpp to level.cpp
16 *
17 * @par Licence:
18 * Copyright (c) 2005-2017 Alister Thomson
19 *
20 * OpenJazz is distributed under the terms of
21 * the GNU General Public License, version 2.0
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26 *
27 * @par Description:
28 * Deals with functionality common to ordinary levels and bonus levels.
29 *
30 */
31
32
33 #include "level.h"
34
35 #include "game/game.h"
36 #include "io/controls.h"
37 #include "io/gfx/font.h"
38 #include "io/gfx/sprite.h"
39 #include "io/gfx/video.h"
40 #include "io/sound.h"
41 #include "player/player.h"
42 #include "jj1scene/jj1scene.h"
43 #include "loop.h"
44 #include "setup.h"
45
46
47 /**
48 * Create a new base level
49 */
Level(Game * owner)50 Level::Level (Game* owner) {
51
52 game = owner;
53
54 menuOptions[0] = "continue game";
55 menuOptions[2] = "save game";
56 menuOptions[3] = "load game";
57 menuOptions[4] = "setup options";
58 menuOptions[5] = "quit game";
59
60 // Arbitrary initial value
61 smoothfps = 50.0f;
62
63 paletteEffects = NULL;
64
65 paused = false;
66
67 // Set the level stage
68 stage = LS_NORMAL;
69
70 stats = 0;
71
72 return;
73
74 }
75
76
77 /**
78 * Destroy base level
79 */
~Level()80 Level::~Level () {
81
82 stopMusic();
83
84 if (paletteEffects) delete paletteEffects;
85
86 return;
87
88 }
89
90
91 /**
92 * Set the players' initial values.
93 *
94 * @param levelType The type of level for which to create a level player
95 * @param anims New level player animations
96 * @param flippedAnims New level player flipped animations
97 * @param checkpoint Whether or not a checkpoint is in use
98 * @param x The level players' new grid x-coordinate
99 * @param y The level players' new grid y-coordinate
100 */
createLevelPlayers(LevelType levelType,Anim ** anims,Anim ** flippedAnims,bool checkpoint,unsigned char x,unsigned char y)101 void Level::createLevelPlayers (LevelType levelType, Anim** anims,
102 Anim** flippedAnims, bool checkpoint, unsigned char x, unsigned char y) {
103
104 int count;
105
106 if (!checkpoint) game->setCheckpoint(x, y);
107
108 for (count = 0; count < nPlayers; count++) {
109
110 players[count].createLevelPlayer(levelType, anims, flippedAnims, x, y);
111 game->resetPlayer(players + count);
112
113 }
114
115 return;
116
117 }
118
119
120 /**
121 * Play a cutscene.
122 *
123 * @param file File name of the cutscene to be played
124 *
125 * @return Error code
126 */
playScene(const char * file)127 int Level::playScene (const char* file) {
128
129 JJ1Scene* scene;
130 int ret;
131
132 delete paletteEffects;
133 paletteEffects = NULL;
134
135 try {
136
137 scene = new JJ1Scene(file);
138
139 } catch (int e) {
140
141 return e;
142
143 }
144
145 ret = scene->play();
146
147 delete scene;
148
149 return ret;
150
151 }
152
153
154 /**
155 * Perform timing calculations.
156 */
timeCalcs()157 void Level::timeCalcs () {
158
159 // Calculate smoothed fps
160 smoothfps = smoothfps + 1.0f -
161 (smoothfps * ((float)(ticks - prevTicks)) / 1000.0f);
162 /* This equation is a simplified version of
163 (fps * c) + (smoothfps * (1 - c))
164 where c = (1 / fps)
165 and fps = 1000 / (ticks - prevTicks)
166 In other words, the response of smoothFPS to changes in FPS decreases as the
167 framerate increases
168 The following version is for c = (1 / smoothfps)
169 */
170 // smoothfps = (fps / smoothfps) + smoothfps - 1;
171
172 // Ignore outlandish values
173 if (smoothfps > 9999.0f) smoothfps = 9999.0f;
174 if (smoothfps < 1.0f) smoothfps = 1.0f;
175
176
177 // Track number of ticks of gameplay since the level started
178
179 if (paused) {
180
181 tickOffset = globalTicks - ticks;
182
183 } else if (globalTicks - tickOffset > ticks + 100) {
184
185 prevTicks = ticks;
186 ticks += 100;
187
188 tickOffset = globalTicks - ticks;
189
190 } else {
191
192 prevTicks = ticks;
193 ticks = globalTicks - tickOffset;
194
195 }
196
197 return;
198
199 }
200
201
202 /**
203 * Calculate the amount of time since the last completed step.
204 *
205 * @return Time since last step
206 */
getTimeChange()207 int Level::getTimeChange () {
208
209 return paused? 0: ticks - ((steps * (setup.slowMotion? 100: 50)) / 3);
210
211 }
212
213
214 /**
215 * Display menu (if visible) and statistics.
216 *
217 * @param bg Palette index of the box(es)
218 * @param menu Whether or not the level menu should be displayed
219 * @param option Selected menu uption
220 * @param textPalIndex The first palette index for unseleceted text
221 * @param selectedTextPalIndex The first palette index for selected text
222 * @param textPalSpan The number of palette indices for text
223 */
drawOverlay(unsigned char bg,bool menu,int option,unsigned char textPalIndex,unsigned char selectedTextPalIndex,int textPalSpan)224 void Level::drawOverlay (unsigned char bg, bool menu, int option,
225 unsigned char textPalIndex, unsigned char selectedTextPalIndex,
226 int textPalSpan) {
227
228 const char* difficultyOptions[4] = {"easy", "medium", "hard", "turbo"};
229 int count, width;
230
231 // Draw graphics statistics
232
233 if (stats & S_SCREEN) {
234
235 #ifdef SCALE
236 if (video.getScaleFactor() > 1)
237 drawRect(canvasW - 84, 11, 80, 37, bg);
238 else
239 #endif
240 drawRect(canvasW - 84, 11, 80, 25, bg);
241
242 panelBigFont->showNumber(video.getWidth(), canvasW - 52, 14);
243 panelBigFont->showString("x", canvasW - 48, 14);
244 panelBigFont->showNumber(video.getHeight(), canvasW - 12, 14);
245 panelBigFont->showString("fps", canvasW - 76, 26);
246 panelBigFont->showNumber((int)smoothfps, canvasW - 12, 26);
247
248 #ifdef SCALE
249 if (video.getScaleFactor() > 1) {
250
251 panelBigFont->showNumber(canvasW, canvasW - 52, 38);
252 panelBigFont->showString("x", canvasW - 48, 39);
253 panelBigFont->showNumber(canvasH, canvasW - 12, 38);
254
255 }
256 #endif
257
258 }
259
260 // Draw player list
261
262 if (stats & S_PLAYERS) {
263
264 width = 39;
265
266 for (count = 0; count < nPlayers; count++)
267 if (panelBigFont->getStringWidth(players[count].getName()) > width)
268 width = panelBigFont->getStringWidth(players[count].getName());
269
270 drawRect((canvasW >> 1) - 48, 11, width + 57, (nPlayers * 12) + 1, bg);
271
272 for (count = 0; count < nPlayers; count++) {
273
274 panelBigFont->showNumber(count + 1,
275 (canvasW >> 1) - 24, 14 + (count * 12));
276 panelBigFont->showString(players[count].getName(),
277 (canvasW >> 1) - 16, 14 + (count * 12));
278 panelBigFont->showNumber(players[count].teamScore,
279 (canvasW >> 1) + width + 1, 14 + (count * 12));
280
281 }
282
283 }
284
285
286 if (menu) {
287
288 // Draw the menu
289
290 drawRect((canvasW >> 2) - 8, (canvasH >> 1) - 54, 144, 108, bg);
291
292 menuOptions[1] = difficultyOptions[game->getDifficulty()];
293
294 for (count = 0; count < 6; count++) {
295
296 if (count == option) fontmn2->mapPalette(240, 8, selectedTextPalIndex, textPalSpan);
297 else fontmn2->mapPalette(240, 8, textPalIndex, textPalSpan);
298
299 fontmn2->showString(menuOptions[count], canvasW >> 2, (canvasH >> 1) + (count << 4) - 46);
300
301 }
302
303 fontmn2->restorePalette();
304
305 }
306
307 return;
308
309 }
310
311
312 /**
313 * Process in-game menu selection.
314 *
315 * @param menu Whether or not the level menu should be displayed
316 * @param option Chosen menu option
317 *
318 * @return Error code
319 */
select(bool & menu,int option)320 int Level::select (bool& menu, int option) {
321
322 bool wasSlow;
323
324 switch (option) {
325
326 case 0: // Continue
327
328 menu = false;
329
330 case 1: // Change difficulty
331
332 if (!multiplayer) game->setDifficulty((game->getDifficulty() + 1) & 3);
333
334 break;
335
336 case 2: // Save
337
338 break;
339
340 case 3: // Load
341
342 break;
343
344 case 4: // Setup
345
346 if (!multiplayer) {
347
348 wasSlow = setup.slowMotion;
349
350 if (setupMenu.setupMain() == E_QUIT) return E_QUIT;
351
352 if (wasSlow && !setup.slowMotion) steps <<= 1;
353 else if (!wasSlow && setup.slowMotion) steps >>= 1;
354
355 // Restore level palette
356 video.setPalette(palette);
357
358 }
359
360 break;
361
362 case 5: // Quit game
363
364 return E_RETURN;
365
366 }
367
368 return E_NONE;
369
370 }
371
372 /**
373 * Process iteration.
374 *
375 * @param menu Whether or not the level menu should be displayed
376 * @param option Selected menu uption
377 * @param message Whether or not the "paused" message is being displayed
378 *
379 * @return Error code
380 */
loop(bool & menu,int & option,bool & message)381 int Level::loop (bool& menu, int& option, bool& message) {
382
383 int ret, x, y;
384
385 // Networking
386 if (multiplayer) {
387
388 ret = game->step(ticks);
389
390 if (ret < 0) return ret;
391
392 }
393
394
395 // Main loop
396 if (::loop(NORMAL_LOOP, paletteEffects) == E_QUIT) return E_QUIT;
397
398
399 if (controls.release(C_ESCAPE)) {
400
401 menu = !menu;
402 option = 0;
403
404 }
405
406 if (controls.release(C_PAUSE)) message = !message;
407
408 if (controls.release(C_STATS)) {
409
410 if (!multiplayer) stats ^= S_SCREEN;
411 else stats = (stats + 1) & 3;
412
413 }
414
415 if (menu) {
416
417 // Deal with menu controls
418
419 if (controls.release(C_UP)) option = (option + 5) % 6;
420
421 if (controls.release(C_DOWN)) option = (option + 1) % 6;
422
423 if (controls.release(C_ENTER)) {
424
425 ret = select(menu, option);
426
427 if (ret < 0) return ret;
428
429 }
430
431 if (controls.getCursor(x, y)) {
432
433 x -= canvasW >> 2;
434 y -= (canvasH >> 1) - 46;
435
436 if ((x >= 0) && (x < 128) && (y >= 0) && (y < 96)) {
437
438 option = y >> 4;
439
440 if (controls.wasCursorReleased()) {
441
442 ret = select(menu, option);
443
444 if (ret < 0) return ret;
445
446 }
447
448 } else if (controls.wasCursorReleased()) menu = false;
449
450 }
451
452 }
453 #if !defined(ANDROID)
454 else {
455
456 if (controls.wasCursorReleased()) menu = true;
457
458 }
459 #endif
460
461 if (!multiplayer) paused = message || menu;
462
463 timeCalcs();
464
465 return E_NONE;
466
467 }
468
469
470 /**
471 * Add extra time.
472 *
473 * @param seconds Number of seconds to add
474 */
addTimer(int seconds)475 void Level::addTimer (int seconds) {
476
477 unsigned char buffer[MTL_L_PROP];
478
479 if (stage != LS_NORMAL) return;
480
481 endTime += seconds * 1000;
482
483 if (endTime >= ticks + (10 * 60 * 1000))
484 endTime = ticks + (10 * 60 * 1000) - 1;
485
486 if (multiplayer) {
487
488 buffer[0] = MTL_L_PROP;
489 buffer[1] = MT_L_PROP;
490 buffer[2] = 2; // add timer
491 buffer[3] = seconds;
492 buffer[4] = 0; // not used
493
494 game->send(buffer);
495
496 }
497
498 return;
499
500 }
501
502
503 /**
504 * Set the level stage.
505 *
506 * @param newStage New level stage
507 */
setStage(LevelStage newStage)508 void Level::setStage (LevelStage newStage) {
509
510 unsigned char buffer[MTL_L_STAGE];
511
512 if (stage == newStage) return;
513
514 stage = newStage;
515
516 if (multiplayer) {
517
518 buffer[0] = MTL_L_STAGE;
519 buffer[1] = MT_L_STAGE;
520 buffer[2] = stage;
521 game->send(buffer);
522
523 }
524
525 return;
526
527 }
528
529
530 /**
531 * Determine the current level stage.
532 *
533 * @return The current level stage.
534 */
getStage()535 LevelStage Level::getStage () {
536
537 return stage;
538
539 }
540
541