NXEngine v1.0.0.5
[NXEngine.git] / game.cpp
blobac79e8c2ccabe987cf14dc4da223b219fcbc8164
2 #include "nx.h"
3 #include "endgame/island.h"
4 #include "endgame/credits.h"
5 #include "intro/intro.h"
6 #include "intro/title.h"
7 #include "pause/pause.h"
8 #include "pause/options.h"
9 #include "inventory.h"
10 #include "map_system.h"
11 #include "game.h"
12 #include "profile.h"
13 #include "game.fdh"
15 static struct TickFunctions
17 void (*OnTick)(void);
18 bool (*OnEnter)(int param);
19 void (*OnExit)(void);
21 tickfunctions[] =
23 NULL, NULL, NULL, // GM_NONE
24 game_tick_normal, NULL, NULL, // GM_NORMAL
25 inventory_tick, inventory_init, NULL, // GM_INVENTORY
26 ms_tick, ms_init, ms_close, // GM_MAP_SYSTEM
27 island_tick, island_init, NULL, // GM_ISLAND
28 credit_tick, credit_init, credit_close, // GM_CREDITS
29 intro_tick, intro_init, NULL, // GM_INTRO
30 title_tick, title_init, NULL, // GM_TITLE
31 pause_tick, pause_init, NULL, // GP_PAUSED
32 options_tick, options_init, options_close // GP_OPTIONS
33 //old_options_tick, old_options_init, old_options_close // GP_OPTIONS
36 Object *onscreen_objects[MAX_OBJECTS];
37 int nOnscreenObjects;
39 Game game;
40 TextBox textbox;
41 DebugConsole console;
42 ObjProp objprop[OBJ_LAST];
44 // init Game object: only called once during startup
45 bool Game::init()
47 int i;
49 memset(&game, 0, sizeof(game));
51 // set default properties
52 memset(objprop, 0, sizeof(objprop));
53 for(i=0;i<OBJ_LAST;i++)
55 objprop[i].shaketime = 16;
56 #ifdef DEBUG // big red "NO" sprite points out unimplemented objects
57 objprop[i].sprite = SPR_UNIMPLEMENTED_OBJECT;
58 #else
59 objprop[i].sprite = SPR_NULL;
60 #endif
63 AssignSprites(); // auto-generated function to assign sprites to objects
64 AssignExtraSprites(); // assign rest of sprites (to be replaced at some point)
66 if (ai_init()) return 1; // setup function pointers to AI routines
68 if (initslopetable()) return 1;
69 if (initmapfirsttime()) return 1;
71 // create the player object--note that the player is NOT destroyed on map change
72 if (game.createplayer()) return 1;
74 return 0;
78 // reset things to prepare for entry to the next stage
79 bool Game::initlevel()
81 Carets::DestroyAll(); // delete smoke clouds, ZZzz's etc...
82 ScreenEffects::Stop(); // prevents white flash after island scene when ballos defeated
84 game.frozen = false;
85 game.bossbar.object = NULL;
86 nOnscreenObjects = 0;
88 if (statusbar_init()) return 1; // reset his displayed health value
89 InitPlayer();
90 initmap();
92 game.stageboss.SetType(stages[game.curmap].bossNo);
93 game.stageboss.OnMapEntry();
95 map_scroll_jump(player->CenterX(), player->CenterY());
97 if (game.switchstage.eventonentry)
99 // this prevents a glitch otherwise caused by entry script to Last Cave.
100 // i.e. the script immediately <PRI's then fades in while the game is still
101 // frozen, thus the player code never has a chance to set the initial frame.
102 PHandleAttributes();
103 PSelectFrame();
105 stat("-- Starting on-entry script %d", game.switchstage.eventonentry);
106 StartScript(game.switchstage.eventonentry);
107 game.switchstage.eventonentry = 0;
110 return 0;
113 bool Game::createplayer()
115 if (player)
117 staterr("game.createplayer: player already exists!");
118 return 1;
121 player = (Player *)CreateObject(0, 0, OBJ_PLAYER);
122 PInitFirstTime();
124 return 0;
128 void Game::close(void)
130 // call any onexit/cleanup function for the current mode
131 setmode(GM_NONE);
133 Objects::DestroyAll(true); // destroy all objects and player
134 FloatText::DeleteAll();
138 void c------------------------------() {}
141 bool Game::setmode(int newmode, int param, bool force)
143 if (newmode == 0)
144 newmode = GM_NORMAL;
146 if (game.mode == newmode && !force)
147 return 0;
149 stat("Setting tick function to type %d param %d", newmode, param);
151 if (tickfunctions[game.mode].OnExit)
152 tickfunctions[game.mode].OnExit();
154 game.mode = newmode;
156 if (tickfunctions[game.mode].OnEnter)
158 if (tickfunctions[game.mode].OnEnter(param))
160 staterr("game.setmode: initilization failed for mode %d", newmode);
161 game.mode = GM_NONE;
162 return 1;
166 return 0;
169 bool Game::pause(int pausemode, int param)
171 if (game.paused == pausemode)
172 return 0;
174 stat("Setting pause: type %d param %d", pausemode, param);
176 if (tickfunctions[game.paused].OnExit)
177 tickfunctions[game.paused].OnExit();
179 game.paused = pausemode;
181 if (tickfunctions[game.paused].OnEnter)
183 if (tickfunctions[game.paused].OnEnter(param))
185 staterr("game.pause: initilization failed for mode %d", pausemode);
186 game.paused = 0;
187 return 1;
191 if (!game.paused)
192 memset(inputs, 0, sizeof(inputs));
194 return 0;
197 void Game::tick(void)
199 debug_clear();
201 if (game.paused)
203 tickfunctions[game.paused].OnTick();
205 else
207 // record/playback replays
208 Replay::run();
210 // run scripts
211 RunScripts();
213 // call the tick function for the current game mode
214 tickfunctions[game.mode].OnTick();
217 DrawDebug();
218 console.Draw();
222 void Game::switchmap(int mapno, int scriptno, int px, int py)
224 game.switchstage.mapno = mapno;
225 game.switchstage.playerx = px;
226 game.switchstage.playery = py;
227 game.switchstage.eventonentry = scriptno;
231 void Game::reset()
233 memset(inputs, 0, sizeof(inputs));
234 StopLoopSounds();
235 StopScripts();
237 Replay::end_record();
238 Replay::end_playback();
240 game.pause(false);
241 game.setmode(GM_INTRO, 0, true);
242 console.SetVisible(false);
246 void c------------------------------() {}
249 // standard in-game tick (as opposed to title-screen, inventory etc)
250 void game_tick_normal(void)
252 Object *o;
254 player->riding = NULL;
255 player->bopped_object = NULL;
256 Objects::UpdateBlockStates();
258 if (!game.frozen)
260 // run AI for player and stageboss first
261 HandlePlayer();
262 game.stageboss.Run();
264 // now objects AI and move all objects to their new positions
265 Objects::RunAI();
266 Objects::PhysicsSim();
268 // run the "aftermove" AI routines
269 HandlePlayer_am();
270 game.stageboss.RunAftermove();
272 FOREACH_OBJECT(o)
274 if (!o->deleted)
275 o->OnAftermove();
279 // important to put this before and not after DrawScene(), or non-existant objects
280 // can wind up in the onscreen_objects[] array, and blow up the program on the next tick.
281 Objects::CullDeleted();
283 map_scroll_do();
285 DrawScene();
286 DrawStatusBar();
287 fade.Draw();
289 niku_run();
290 if (player->equipmask & EQUIP_NIKUMARU)
291 niku_draw(game.counter);
293 textbox.Draw();
295 ScreenEffects::Draw();
296 map_draw_map_name(); // stage name overlay as on entry
300 // shake screen.
301 void quake(int quaketime, int snd)
303 if (game.quaketime < quaketime)
304 game.quaketime = quaketime;
306 if (snd)
307 sound((snd != -1) ? snd : SND_QUAKE);
310 // during Ballos fight, since there's already a perpetual quake,
311 // we need to be able to make an even BIGGER quake effect.
312 void megaquake(int quaketime, int snd)
314 if (game.megaquaketime < quaketime)
316 game.megaquaketime = quaketime;
317 if (game.quaketime < game.megaquaketime)
318 game.quaketime = game.megaquaketime;
321 if (snd)
322 sound((snd != -1) ? snd : SND_QUAKE);
326 void DrawScene(void)
328 int scr_x, scr_y;
329 extern int flipacceltime;
331 // sporidically-used animated tile feature,
332 // e.g. water currents in Waterway
333 if (map.nmotiontiles)
334 AnimateMotionTiles();
336 // draw background map tiles
337 if (!flipacceltime)
339 map_draw_backdrop();
340 map_draw(false);
343 // draw all objects following their z-order
344 nOnscreenObjects = 0;
346 for(Object *o = lowestobject;
347 o != NULL;
348 o = o->higher)
350 if (o == player) continue; // player drawn specially in DrawPlayer
352 // keep it's floattext linked with it's position
353 o->DamageText->UpdatePos(o);
355 // shake enemies that were just hit. when they stop shaking,
356 // start rising up how many damage they took.
357 if (o->shaketime)
359 o->display_xoff = (o->shaketime & 2) ? 1 : -1;
360 if (!--o->shaketime) o->display_xoff = 0;
362 else if (o->DamageWaiting > 0)
364 o->DamageText->AddQty(o->DamageWaiting);
365 o->DamageWaiting = 0;
368 // get object's onscreen position
369 scr_x = (o->x >> CSF) - (map.displayed_xscroll >> CSF);
370 scr_y = (o->y >> CSF) - (map.displayed_yscroll >> CSF);
371 scr_x -= sprites[o->sprite].frame[o->frame].dir[o->dir].drawpoint.x;
372 scr_y -= sprites[o->sprite].frame[o->frame].dir[o->dir].drawpoint.y;
374 // don't draw objects that are completely offscreen
375 // (+26 so floattext won't suddenly disappear on object near bottom of screen)
376 if (scr_x <= SCREEN_WIDTH && scr_y <= SCREEN_HEIGHT+26 && \
377 scr_x >= -sprites[o->sprite].w && scr_y >= -sprites[o->sprite].h)
379 if (nOnscreenObjects < MAX_OBJECTS-1)
381 onscreen_objects[nOnscreenObjects++] = o;
382 o->onscreen = true;
384 else
386 staterr("%s:%d: Max Objects Overflow", __FILE__, __LINE__);
387 return;
390 if (!o->invisible && o->sprite != SPR_NULL)
392 scr_x += o->display_xoff;
394 if (o->clip_enable)
396 draw_sprite_clipped(scr_x, scr_y, o->sprite, o->frame, o->dir, o->clipx1, o->clipx2, o->clipy1, o->clipy2);
398 else
400 draw_sprite(scr_x, scr_y, o->sprite, o->frame, o->dir);
404 else
406 o->onscreen = false;
410 // draw the player
411 DrawPlayer();
413 // draw foreground map tiles
414 if (!flipacceltime)
415 map_draw(TA_FOREGROUND);
417 // draw carets (always-on-top effects such as boomflash)
418 Carets::DrawAll();
420 // draw rising/falling water in maps like Almond
421 map_drawwaterlevel();
423 // draw all floattext (rising damage and XP amounts)
424 FloatText::DrawAll();
426 if (game.debug.DrawBoundingBoxes) DrawBoundingBoxes();
427 //if (game.debug.debugmode) DrawAttrPoints();
431 void c------------------------------() {}
434 bool game_load(int num)
436 Profile p;
438 stat("game_load: loading savefile %d", num);
440 if (profile_load(GetProfileName(num), &p))
441 return 1;
443 return game_load(&p);
446 bool game_load(Profile *p)
448 int i;
450 player->hp = p->hp;
451 player->maxHealth = p->maxhp;
453 player->whimstar.nstars = p->num_whimstars;
454 player->equipmask = p->equipmask;
456 // load weapons
457 for(i=0;i<WPN_COUNT;i++)
459 player->weapons[i].hasWeapon = p->weapons[i].hasWeapon;
460 player->weapons[i].level = p->weapons[i].level;
461 player->weapons[i].xp = p->weapons[i].xp;
462 player->weapons[i].ammo = p->weapons[i].ammo;
463 player->weapons[i].maxammo = p->weapons[i].maxammo;
466 player->curWeapon = p->curWeapon;
468 // load inventory
469 memcpy(player->inventory, p->inventory, sizeof(player->inventory));
470 player->ninventory = p->ninventory;
472 // load flags
473 memcpy(game.flags, p->flags, sizeof(game.flags));
475 // load teleporter slots
476 textbox.StageSelect.ClearSlots();
477 for(i=0;i<p->num_teleslots;i++)
479 int slotno = p->teleslots[i].slotno;
480 int scriptno = p->teleslots[i].scriptno;
482 textbox.StageSelect.SetSlot(slotno, scriptno);
483 stat(" - Read Teleporter Slot %d: slotno=%d scriptno=%d", i, slotno, scriptno);
486 // have to load the stage last AFTER the flags are loaded because
487 // of the options to appear and disappear objects based on flags.
488 if (load_stage(p->stage)) return 1;
489 music(p->songno);
491 player->x = p->px;
492 player->y = p->py;
493 player->dir = p->pdir;
494 player->hide = false;
495 game.showmapnametime = 0;
497 return 0;
501 bool game_save(int num)
503 Profile p;
505 stat("game_save: writing savefile %d", num);
507 if (game_save(&p))
508 return 1;
510 if (profile_save(GetProfileName(num), &p))
511 return 1;
513 return 0;
516 bool game_save(Profile *p)
518 int i;
520 memset(p, 0, sizeof(Profile));
522 p->stage = game.curmap;
523 p->songno = music_cursong();
525 p->px = player->x;
526 p->py = player->y;
527 p->pdir = player->dir;
529 p->hp = player->hp;
530 p->maxhp = player->maxHealth;
532 p->num_whimstars = player->whimstar.nstars;
533 p->equipmask = player->equipmask;
535 // save weapons
536 p->curWeapon = player->curWeapon;
538 for(i=0;i<WPN_COUNT;i++)
540 p->weapons[i].hasWeapon = player->weapons[i].hasWeapon;
541 p->weapons[i].level = player->weapons[i].level;
542 p->weapons[i].xp = player->weapons[i].xp;
543 p->weapons[i].ammo = player->weapons[i].ammo;
544 p->weapons[i].maxammo = player->weapons[i].maxammo;
547 // save inventory
548 p->ninventory = player->ninventory;
549 memcpy(p->inventory, player->inventory, sizeof(p->inventory));
551 // save flags
552 memcpy(p->flags, game.flags, sizeof(p->flags));
554 // save teleporter slots
555 for(i=0;i<NUM_TELEPORTER_SLOTS;i++)
557 int slotno, scriptno;
558 if (!textbox.StageSelect.GetSlotByIndex(i, &slotno, &scriptno))
560 p->teleslots[p->num_teleslots].slotno = slotno;
561 p->teleslots[p->num_teleslots].scriptno = scriptno;
562 p->num_teleslots++;
566 return 0;
570 void c------------------------------() {}
573 // assign sprites for the objects that didn't get covered by the
574 // auto-generated spritesetup->cpp, and set some properties on the objects.
575 // This is mostly for objects where the sprite is not named the same as
576 // the object it is assigned to.
577 void AssignExtraSprites(void)
579 objprop[OBJ_PLAYER].sprite = SPR_MYCHAR;
580 objprop[OBJ_NPC_PLAYER].sprite = SPR_MYCHAR;
581 objprop[OBJ_PTELIN].sprite = SPR_MYCHAR;
582 objprop[OBJ_PTELOUT].sprite = SPR_MYCHAR;
584 objprop[OBJ_NULL].sprite = SPR_NULL;
585 objprop[OBJ_HVTRIGGER].sprite = SPR_NULL;
586 objprop[OBJ_BUBBLE_SPAWNER].sprite = SPR_NULL;
587 objprop[OBJ_DROPLET_SPAWNER].sprite = SPR_NULL;
588 objprop[OBJ_HEY_SPAWNER].sprite = SPR_NULL;
589 objprop[OBJ_WATERLEVEL].sprite = SPR_NULL;
590 objprop[OBJ_LAVA_DRIP_SPAWNER].sprite = SPR_NULL;
591 objprop[OBJ_RED_BAT_SPAWNER].sprite = SPR_NULL;
592 objprop[OBJ_SCROLL_CONTROLLER].sprite = SPR_NULL;
593 objprop[OBJ_DOCTOR_GHOST].sprite = SPR_NULL;
594 objprop[OBJ_FALLING_BLOCK].sprite = SPR_NULL; // set at runtime based on current map
595 objprop[OBJ_FALLING_BLOCK_SPAWNER].sprite = SPR_NULL;
596 objprop[OBJ_QUAKE].sprite = SPR_NULL;
597 objprop[OBJ_BUTE_SPAWNER].sprite = SPR_NULL;
598 objprop[OBJ_SMOKE_DROPPER].sprite = SPR_NULL;
601 objprop[OBJ_BUTE_ARROW].sprite = SPR_BUTE_ARROW_LEFT; // so spawn point is applied
603 objprop[OBJ_POLISHBABY].defaultnxflags |= NXFLAG_SLOW_WHEN_HURT;
605 objprop[OBJ_MIMIGAC1].sprite = SPR_MIMIGAC;
606 objprop[OBJ_MIMIGAC2].sprite = SPR_MIMIGAC;
607 objprop[OBJ_MIMIGAC_ENEMY].sprite = SPR_MIMIGAC;
608 objprop[OBJ_MIMIGAC_ENEMY].shaketime = 0;
610 objprop[OBJ_MISERY_FLOAT].sprite = SPR_MISERY;
611 objprop[OBJ_MISERY_FLOAT].damage = 1;
612 objprop[OBJ_MISERY_STAND].sprite = SPR_MISERY;
614 objprop[OBJ_PUPPY_WAG].sprite = SPR_PUPPY;
615 objprop[OBJ_PUPPY_BARK].sprite = SPR_PUPPY;
616 objprop[OBJ_PUPPY_CARRY].sprite = SPR_PUPPY;
617 objprop[OBJ_PUPPY_SLEEP].sprite = SPR_PUPPY_ASLEEP;
618 objprop[OBJ_PUPPY_RUN].sprite = SPR_PUPPY;
619 objprop[OBJ_PUPPY_ITEMS].sprite = SPR_PUPPY;
621 objprop[OBJ_BALROG_DROP_IN].sprite = SPR_BALROG;
622 objprop[OBJ_BALROG_BUST_IN].sprite = SPR_BALROG;
624 objprop[OBJ_CROWWITHSKULL].sprite = SPR_CROW;
625 objprop[OBJ_ARMADILLO].defaultnxflags |= (NXFLAG_FOLLOW_SLOPE | NXFLAG_SLOW_WHEN_HURT);
626 objprop[OBJ_SKULLHEAD_CARRIED].sprite = SPR_SKULLHEAD;
628 objprop[OBJ_TOROKO].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
629 objprop[OBJ_TOROKO_TELEPORT_IN].sprite = SPR_TOROKO;
631 objprop[OBJ_KING].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
633 objprop[OBJ_FAN_DROPLET].sprite = SPR_WATER_DROPLET;
635 objprop[OBJ_MGUN_TRAIL].defaultflags |= FLAG_IGNORE_SOLID;
637 objprop[OBJ_BLOCK_MOVEH].sprite = SPR_MOVING_BLOCK;
638 objprop[OBJ_BLOCK_MOVEV].sprite = SPR_MOVING_BLOCK;
640 objprop[OBJ_IRONH].shaketime = 8;
642 objprop[OBJ_OMEGA_BODY].shaketime = 0; // omega handles his own shaketime
643 objprop[OBJ_OMEGA_BODY].hurt_sound = SND_ENEMY_HURT_BIG;
645 objprop[OBJ_OMEGA_LEG].sprite = SPR_OMG_LEG_INAIR;
646 objprop[OBJ_OMEGA_STRUT].sprite = SPR_OMG_STRUT;
648 objprop[OBJ_OMEGA_SHOT].death_smoke_amt = 4;
649 objprop[OBJ_OMEGA_SHOT].death_sound = SND_EXPL_SMALL;
650 objprop[OBJ_OMEGA_SHOT].initial_hp = 1;
651 objprop[OBJ_OMEGA_SHOT].xponkill = 1;
653 objprop[OBJ_BAT_HANG].sprite = SPR_BAT;
654 objprop[OBJ_BAT_CIRCLE].sprite = SPR_BAT;
656 objprop[OBJ_FIREBALL1].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
657 objprop[OBJ_FIREBALL23].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
659 objprop[OBJ_CURLY_AI].sprite = SPR_CURLY;
660 objprop[OBJ_CURLY_AI].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
662 objprop[OBJ_CURLY].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
664 objprop[OBJ_MINICORE].hurt_sound = SND_ENEMY_HURT_COOL;
665 objprop[OBJ_CORE_CONTROLLER].hurt_sound = SND_CORE_HURT;
667 objprop[OBJ_CURLY_CARRIED].sprite = SPR_CURLY;
669 objprop[OBJ_BALROG_BOSS_RUNNING].sprite = SPR_BALROG;
670 objprop[OBJ_BALROG_BOSS_FLYING].sprite = SPR_BALROG;
671 objprop[OBJ_BALROG_BOSS_MISSILES].sprite = SPR_BALROG;
673 objprop[OBJ_XP].sprite = SPR_XP_SMALL;
675 objprop[OBJ_NPC_IGOR].sprite = SPR_IGOR;
676 objprop[OBJ_BOSS_IGOR].sprite = SPR_IGOR;
677 objprop[OBJ_BOSS_IGOR_DEFEATED].sprite = SPR_IGOR;
678 objprop[OBJ_IGOR_BALCONY].sprite = SPR_IGOR;
680 objprop[OBJ_X_TARGET].hurt_sound = SND_ENEMY_HURT_COOL;
681 objprop[OBJ_X_INTERNALS].shaketime = 9;
682 objprop[OBJ_X_MAINOBJECT].xponkill = 1;
684 objprop[OBJ_POOH_BLACK_BUBBLE].xponkill = 0;
685 objprop[OBJ_POOH_BLACK_DYING].sprite = SPR_POOH_BLACK;
687 objprop[OBJ_BOOSTER_FALLING].sprite = SPR_PROFESSOR_BOOSTER;
689 objprop[OBJ_MIMIGA_FARMER_STANDING].sprite = SPR_MIMIGA_FARMER;
690 objprop[OBJ_MIMIGA_FARMER_WALKING].sprite = SPR_MIMIGA_FARMER;
691 objprop[OBJ_DROLL_GUARD].sprite = SPR_DROLL;
693 objprop[OBJ_MA_PIGNON_CLONE].sprite = SPR_MA_PIGNON;
695 objprop[OBJ_DOCTOR_SHOT_TRAIL].sprite = SPR_DOCTOR_SHOT;
697 // they're still able to detect when they touch floor; etc,
698 // but we don't want say a falling one to get blocked by the ceiling.
699 objprop[OBJ_RED_ENERGY].defaultflags |= FLAG_IGNORE_SOLID;
701 objprop[OBJ_SUE_TELEPORT_IN].sprite = SPR_SUE;
703 objprop[OBJ_MISERY_BAT].sprite = SPR_ORANGE_BAT_FINAL;
704 objprop[OBJ_UD_MINICORE_IDLE].sprite = SPR_UD_MINICORE;
706 objprop[OBJ_WHIMSICAL_STAR].sprite = SPR_WHIMSICAL_STAR; // for bbox only, object is invisible
708 // these are set by AI; this is just to silence unimplemented object warnings
709 #ifdef DEBUG
710 objprop[OBJ_CRITTER_FLYING].sprite = SPR_CRITTER_FLYING_CYAN;
711 for(int i=OBJ_SHOTS_START;i<=OBJ_SHOTS_END;i++)
712 if (objprop[i].sprite==SPR_UNIMPLEMENTED_OBJECT) objprop[i].sprite = SPR_NULL;
713 #endif