Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
716 lines (550 sloc) 17.5 KB
#include "nx.h"
#include "endgame/island.h"
#include "endgame/credits.h"
#include "intro/intro.h"
#include "intro/title.h"
#include "pause/pause.h"
#include "pause/options.h"
#include "inventory.h"
#include "map_system.h"
#include "game.h"
#include "profile.h"
#include "game.fdh"
static struct TickFunctions
{
void (*OnTick)(void);
bool (*OnEnter)(int param);
void (*OnExit)(void);
}
tickfunctions[] =
{
NULL, NULL, NULL, // GM_NONE
game_tick_normal, NULL, NULL, // GM_NORMAL
inventory_tick, inventory_init, NULL, // GM_INVENTORY
ms_tick, ms_init, ms_close, // GM_MAP_SYSTEM
island_tick, island_init, NULL, // GM_ISLAND
credit_tick, credit_init, credit_close, // GM_CREDITS
intro_tick, intro_init, NULL, // GM_INTRO
title_tick, title_init, NULL, // GM_TITLE
pause_tick, pause_init, NULL, // GP_PAUSED
options_tick, options_init, options_close // GP_OPTIONS
//old_options_tick, old_options_init, old_options_close // GP_OPTIONS
};
Object *onscreen_objects[MAX_OBJECTS];
int nOnscreenObjects;
Game game;
TextBox textbox;
DebugConsole console;
ObjProp objprop[OBJ_LAST];
// init Game object: only called once during startup
bool Game::init()
{
int i;
memset(&game, 0, sizeof(game));
// set default properties
memset(objprop, 0, sizeof(objprop));
for(i=0;i<OBJ_LAST;i++)
{
objprop[i].shaketime = 16;
#ifdef DEBUG // big red "NO" sprite points out unimplemented objects
objprop[i].sprite = SPR_UNIMPLEMENTED_OBJECT;
#else
objprop[i].sprite = SPR_NULL;
#endif
}
AssignSprites(); // auto-generated function to assign sprites to objects
AssignExtraSprites(); // assign rest of sprites (to be replaced at some point)
if (ai_init()) return 1; // setup function pointers to AI routines
if (initslopetable()) return 1;
if (initmapfirsttime()) return 1;
// create the player object--note that the player is NOT destroyed on map change
if (game.createplayer()) return 1;
return 0;
}
// reset things to prepare for entry to the next stage
bool Game::initlevel()
{
Carets::DestroyAll(); // delete smoke clouds, ZZzz's etc...
ScreenEffects::Stop(); // prevents white flash after island scene when ballos defeated
game.frozen = false;
game.bossbar.object = NULL;
nOnscreenObjects = 0;
if (statusbar_init()) return 1; // reset his displayed health value
InitPlayer();
initmap();
game.stageboss.SetType(stages[game.curmap].bossNo);
game.stageboss.OnMapEntry();
map_scroll_jump(player->CenterX(), player->CenterY());
if (game.switchstage.eventonentry)
{
// this prevents a glitch otherwise caused by entry script to Last Cave.
// i.e. the script immediately <PRI's then fades in while the game is still
// frozen, thus the player code never has a chance to set the initial frame.
PHandleAttributes();
PSelectFrame();
stat("-- Starting on-entry script %d", game.switchstage.eventonentry);
StartScript(game.switchstage.eventonentry);
game.switchstage.eventonentry = 0;
}
return 0;
}
bool Game::createplayer()
{
if (player)
{
staterr("game.createplayer: player already exists!");
return 1;
}
player = (Player *)CreateObject(0, 0, OBJ_PLAYER);
PInitFirstTime();
return 0;
}
void Game::close(void)
{
// call any onexit/cleanup function for the current mode
setmode(GM_NONE);
Objects::DestroyAll(true); // destroy all objects and player
FloatText::DeleteAll();
}
/*
void c------------------------------() {}
*/
bool Game::setmode(int newmode, int param, bool force)
{
if (newmode == 0)
newmode = GM_NORMAL;
if (game.mode == newmode && !force)
return 0;
stat("Setting tick function to type %d param %d", newmode, param);
if (tickfunctions[game.mode].OnExit)
tickfunctions[game.mode].OnExit();
game.mode = newmode;
if (tickfunctions[game.mode].OnEnter)
{
if (tickfunctions[game.mode].OnEnter(param))
{
staterr("game.setmode: initilization failed for mode %d", newmode);
game.mode = GM_NONE;
return 1;
}
}
return 0;
}
bool Game::pause(int pausemode, int param)
{
if (game.paused == pausemode)
return 0;
stat("Setting pause: type %d param %d", pausemode, param);
if (tickfunctions[game.paused].OnExit)
tickfunctions[game.paused].OnExit();
game.paused = pausemode;
if (tickfunctions[game.paused].OnEnter)
{
if (tickfunctions[game.paused].OnEnter(param))
{
staterr("game.pause: initilization failed for mode %d", pausemode);
game.paused = 0;
return 1;
}
}
if (!game.paused)
memset(inputs, 0, sizeof(inputs));
return 0;
}
void Game::tick(void)
{
debug_clear();
if (game.paused)
{
tickfunctions[game.paused].OnTick();
}
else
{
// record/playback replays
Replay::run();
// run scripts
RunScripts();
// call the tick function for the current game mode
tickfunctions[game.mode].OnTick();
}
DrawDebug();
console.Draw();
}
void Game::switchmap(int mapno, int scriptno, int px, int py)
{
game.switchstage.mapno = mapno;
game.switchstage.playerx = px;
game.switchstage.playery = py;
game.switchstage.eventonentry = scriptno;
}
void Game::reset()
{
memset(inputs, 0, sizeof(inputs));
StopLoopSounds();
StopScripts();
Replay::end_record();
Replay::end_playback();
game.pause(false);
game.setmode(GM_INTRO, 0, true);
console.SetVisible(false);
}
/*
void c------------------------------() {}
*/
// standard in-game tick (as opposed to title-screen, inventory etc)
void game_tick_normal(void)
{
Object *o;
player->riding = NULL;
player->bopped_object = NULL;
Objects::UpdateBlockStates();
if (!game.frozen)
{
// run AI for player and stageboss first
HandlePlayer();
game.stageboss.Run();
// now objects AI and move all objects to their new positions
Objects::RunAI();
Objects::PhysicsSim();
// run the "aftermove" AI routines
HandlePlayer_am();
game.stageboss.RunAftermove();
FOREACH_OBJECT(o)
{
if (!o->deleted)
o->OnAftermove();
}
}
// important to put this before and not after DrawScene(), or non-existant objects
// can wind up in the onscreen_objects[] array, and blow up the program on the next tick.
Objects::CullDeleted();
map_scroll_do();
DrawScene();
DrawStatusBar();
fade.Draw();
niku_run();
if (player->equipmask & EQUIP_NIKUMARU)
niku_draw(game.counter);
textbox.Draw();
ScreenEffects::Draw();
map_draw_map_name(); // stage name overlay as on entry
}
// shake screen.
void quake(int quaketime, int snd)
{
if (game.quaketime < quaketime)
game.quaketime = quaketime;
if (snd)
sound((snd != -1) ? snd : SND_QUAKE);
}
// during Ballos fight, since there's already a perpetual quake,
// we need to be able to make an even BIGGER quake effect.
void megaquake(int quaketime, int snd)
{
if (game.megaquaketime < quaketime)
{
game.megaquaketime = quaketime;
if (game.quaketime < game.megaquaketime)
game.quaketime = game.megaquaketime;
}
if (snd)
sound((snd != -1) ? snd : SND_QUAKE);
}
void DrawScene(void)
{
int scr_x, scr_y;
extern int flipacceltime;
// sporidically-used animated tile feature,
// e.g. water currents in Waterway
if (map.nmotiontiles)
AnimateMotionTiles();
// draw background map tiles
if (!flipacceltime)
{
map_draw_backdrop();
map_draw(false);
}
// draw all objects following their z-order
nOnscreenObjects = 0;
for(Object *o = lowestobject;
o != NULL;
o = o->higher)
{
if (o == player) continue; // player drawn specially in DrawPlayer
// keep it's floattext linked with it's position
o->DamageText->UpdatePos(o);
// shake enemies that were just hit. when they stop shaking,
// start rising up how many damage they took.
if (o->shaketime)
{
o->display_xoff = (o->shaketime & 2) ? 1 : -1;
if (!--o->shaketime) o->display_xoff = 0;
}
else if (o->DamageWaiting > 0)
{
o->DamageText->AddQty(o->DamageWaiting);
o->DamageWaiting = 0;
}
// get object's onscreen position
scr_x = (o->x >> CSF) - (map.displayed_xscroll >> CSF);
scr_y = (o->y >> CSF) - (map.displayed_yscroll >> CSF);
scr_x -= sprites[o->sprite].frame[o->frame].dir[o->dir].drawpoint.x;
scr_y -= sprites[o->sprite].frame[o->frame].dir[o->dir].drawpoint.y;
// don't draw objects that are completely offscreen
// (+26 so floattext won't suddenly disappear on object near bottom of screen)
if (scr_x <= SCREEN_WIDTH && scr_y <= SCREEN_HEIGHT+26 && \
scr_x >= -sprites[o->sprite].w && scr_y >= -sprites[o->sprite].h)
{
if (nOnscreenObjects < MAX_OBJECTS-1)
{
onscreen_objects[nOnscreenObjects++] = o;
o->onscreen = true;
}
else
{
staterr("%s:%d: Max Objects Overflow", __FILE__, __LINE__);
return;
}
if (!o->invisible && o->sprite != SPR_NULL)
{
scr_x += o->display_xoff;
if (o->clip_enable)
{
draw_sprite_clipped(scr_x, scr_y, o->sprite, o->frame, o->dir, o->clipx1, o->clipx2, o->clipy1, o->clipy2);
}
else
{
draw_sprite(scr_x, scr_y, o->sprite, o->frame, o->dir);
}
}
}
else
{
o->onscreen = false;
}
}
// draw the player
DrawPlayer();
// draw foreground map tiles
if (!flipacceltime)
map_draw(TA_FOREGROUND);
// draw carets (always-on-top effects such as boomflash)
Carets::DrawAll();
// draw rising/falling water in maps like Almond
map_drawwaterlevel();
// draw all floattext (rising damage and XP amounts)
FloatText::DrawAll();
if (game.debug.DrawBoundingBoxes) DrawBoundingBoxes();
//if (game.debug.debugmode) DrawAttrPoints();
}
/*
void c------------------------------() {}
*/
bool game_load(int num)
{
Profile p;
stat("game_load: loading savefile %d", num);
if (profile_load(GetProfileName(num), &p))
return 1;
return game_load(&p);
}
bool game_load(Profile *p)
{
int i;
player->hp = p->hp;
player->maxHealth = p->maxhp;
player->whimstar.nstars = p->num_whimstars;
player->equipmask = p->equipmask;
// load weapons
for(i=0;i<WPN_COUNT;i++)
{
player->weapons[i].hasWeapon = p->weapons[i].hasWeapon;
player->weapons[i].level = p->weapons[i].level;
player->weapons[i].xp = p->weapons[i].xp;
player->weapons[i].ammo = p->weapons[i].ammo;
player->weapons[i].maxammo = p->weapons[i].maxammo;
}
player->curWeapon = p->curWeapon;
// load inventory
memcpy(player->inventory, p->inventory, sizeof(player->inventory));
player->ninventory = p->ninventory;
// load flags
memcpy(game.flags, p->flags, sizeof(game.flags));
// load teleporter slots
textbox.StageSelect.ClearSlots();
for(i=0;i<p->num_teleslots;i++)
{
int slotno = p->teleslots[i].slotno;
int scriptno = p->teleslots[i].scriptno;
textbox.StageSelect.SetSlot(slotno, scriptno);
stat(" - Read Teleporter Slot %d: slotno=%d scriptno=%d", i, slotno, scriptno);
}
// have to load the stage last AFTER the flags are loaded because
// of the options to appear and disappear objects based on flags.
if (load_stage(p->stage)) return 1;
music(p->songno);
player->x = p->px;
player->y = p->py;
player->dir = p->pdir;
player->hide = false;
game.showmapnametime = 0;
return 0;
}
bool game_save(int num)
{
Profile p;
stat("game_save: writing savefile %d", num);
if (game_save(&p))
return 1;
if (profile_save(GetProfileName(num), &p))
return 1;
return 0;
}
bool game_save(Profile *p)
{
int i;
memset(p, 0, sizeof(Profile));
p->stage = game.curmap;
p->songno = music_cursong();
p->px = player->x;
p->py = player->y;
p->pdir = player->dir;
p->hp = player->hp;
p->maxhp = player->maxHealth;
p->num_whimstars = player->whimstar.nstars;
p->equipmask = player->equipmask;
// save weapons
p->curWeapon = player->curWeapon;
for(i=0;i<WPN_COUNT;i++)
{
p->weapons[i].hasWeapon = player->weapons[i].hasWeapon;
p->weapons[i].level = player->weapons[i].level;
p->weapons[i].xp = player->weapons[i].xp;
p->weapons[i].ammo = player->weapons[i].ammo;
p->weapons[i].maxammo = player->weapons[i].maxammo;
}
// save inventory
p->ninventory = player->ninventory;
memcpy(p->inventory, player->inventory, sizeof(p->inventory));
// save flags
memcpy(p->flags, game.flags, sizeof(p->flags));
// save teleporter slots
for(i=0;i<NUM_TELEPORTER_SLOTS;i++)
{
int slotno, scriptno;
if (!textbox.StageSelect.GetSlotByIndex(i, &slotno, &scriptno))
{
p->teleslots[p->num_teleslots].slotno = slotno;
p->teleslots[p->num_teleslots].scriptno = scriptno;
p->num_teleslots++;
}
}
return 0;
}
/*
void c------------------------------() {}
*/
// assign sprites for the objects that didn't get covered by the
// auto-generated spritesetup->cpp, and set some properties on the objects.
// This is mostly for objects where the sprite is not named the same as
// the object it is assigned to.
void AssignExtraSprites(void)
{
objprop[OBJ_PLAYER].sprite = SPR_MYCHAR;
objprop[OBJ_NPC_PLAYER].sprite = SPR_MYCHAR;
objprop[OBJ_PTELIN].sprite = SPR_MYCHAR;
objprop[OBJ_PTELOUT].sprite = SPR_MYCHAR;
objprop[OBJ_NULL].sprite = SPR_NULL;
objprop[OBJ_HVTRIGGER].sprite = SPR_NULL;
objprop[OBJ_BUBBLE_SPAWNER].sprite = SPR_NULL;
objprop[OBJ_DROPLET_SPAWNER].sprite = SPR_NULL;
objprop[OBJ_HEY_SPAWNER].sprite = SPR_NULL;
objprop[OBJ_WATERLEVEL].sprite = SPR_NULL;
objprop[OBJ_LAVA_DRIP_SPAWNER].sprite = SPR_NULL;
objprop[OBJ_RED_BAT_SPAWNER].sprite = SPR_NULL;
objprop[OBJ_SCROLL_CONTROLLER].sprite = SPR_NULL;
objprop[OBJ_DOCTOR_GHOST].sprite = SPR_NULL;
objprop[OBJ_FALLING_BLOCK].sprite = SPR_NULL; // set at runtime based on current map
objprop[OBJ_FALLING_BLOCK_SPAWNER].sprite = SPR_NULL;
objprop[OBJ_QUAKE].sprite = SPR_NULL;
objprop[OBJ_BUTE_SPAWNER].sprite = SPR_NULL;
objprop[OBJ_SMOKE_DROPPER].sprite = SPR_NULL;
objprop[OBJ_BUTE_ARROW].sprite = SPR_BUTE_ARROW_LEFT; // so spawn point is applied
objprop[OBJ_POLISHBABY].defaultnxflags |= NXFLAG_SLOW_WHEN_HURT;
objprop[OBJ_MIMIGAC1].sprite = SPR_MIMIGAC;
objprop[OBJ_MIMIGAC2].sprite = SPR_MIMIGAC;
objprop[OBJ_MIMIGAC_ENEMY].sprite = SPR_MIMIGAC;
objprop[OBJ_MIMIGAC_ENEMY].shaketime = 0;
objprop[OBJ_MISERY_FLOAT].sprite = SPR_MISERY;
objprop[OBJ_MISERY_FLOAT].damage = 1;
objprop[OBJ_MISERY_STAND].sprite = SPR_MISERY;
objprop[OBJ_PUPPY_WAG].sprite = SPR_PUPPY;
objprop[OBJ_PUPPY_BARK].sprite = SPR_PUPPY;
objprop[OBJ_PUPPY_CARRY].sprite = SPR_PUPPY;
objprop[OBJ_PUPPY_SLEEP].sprite = SPR_PUPPY_ASLEEP;
objprop[OBJ_PUPPY_RUN].sprite = SPR_PUPPY;
objprop[OBJ_PUPPY_ITEMS].sprite = SPR_PUPPY;
objprop[OBJ_BALROG_DROP_IN].sprite = SPR_BALROG;
objprop[OBJ_BALROG_BUST_IN].sprite = SPR_BALROG;
objprop[OBJ_CROWWITHSKULL].sprite = SPR_CROW;
objprop[OBJ_ARMADILLO].defaultnxflags |= (NXFLAG_FOLLOW_SLOPE | NXFLAG_SLOW_WHEN_HURT);
objprop[OBJ_SKULLHEAD_CARRIED].sprite = SPR_SKULLHEAD;
objprop[OBJ_TOROKO].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
objprop[OBJ_TOROKO_TELEPORT_IN].sprite = SPR_TOROKO;
objprop[OBJ_KING].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
objprop[OBJ_FAN_DROPLET].sprite = SPR_WATER_DROPLET;
objprop[OBJ_MGUN_TRAIL].defaultflags |= FLAG_IGNORE_SOLID;
objprop[OBJ_BLOCK_MOVEH].sprite = SPR_MOVING_BLOCK;
objprop[OBJ_BLOCK_MOVEV].sprite = SPR_MOVING_BLOCK;
objprop[OBJ_IRONH].shaketime = 8;
objprop[OBJ_OMEGA_BODY].shaketime = 0; // omega handles his own shaketime
objprop[OBJ_OMEGA_BODY].hurt_sound = SND_ENEMY_HURT_BIG;
objprop[OBJ_OMEGA_LEG].sprite = SPR_OMG_LEG_INAIR;
objprop[OBJ_OMEGA_STRUT].sprite = SPR_OMG_STRUT;
objprop[OBJ_OMEGA_SHOT].death_smoke_amt = 4;
objprop[OBJ_OMEGA_SHOT].death_sound = SND_EXPL_SMALL;
objprop[OBJ_OMEGA_SHOT].initial_hp = 1;
objprop[OBJ_OMEGA_SHOT].xponkill = 1;
objprop[OBJ_BAT_HANG].sprite = SPR_BAT;
objprop[OBJ_BAT_CIRCLE].sprite = SPR_BAT;
objprop[OBJ_FIREBALL1].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
objprop[OBJ_FIREBALL23].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
objprop[OBJ_CURLY_AI].sprite = SPR_CURLY;
objprop[OBJ_CURLY_AI].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
objprop[OBJ_CURLY].defaultnxflags |= NXFLAG_FOLLOW_SLOPE;
objprop[OBJ_MINICORE].hurt_sound = SND_ENEMY_HURT_COOL;
objprop[OBJ_CORE_CONTROLLER].hurt_sound = SND_CORE_HURT;
objprop[OBJ_CURLY_CARRIED].sprite = SPR_CURLY;
objprop[OBJ_BALROG_BOSS_RUNNING].sprite = SPR_BALROG;
objprop[OBJ_BALROG_BOSS_FLYING].sprite = SPR_BALROG;
objprop[OBJ_BALROG_BOSS_MISSILES].sprite = SPR_BALROG;
objprop[OBJ_XP].sprite = SPR_XP_SMALL;
objprop[OBJ_NPC_IGOR].sprite = SPR_IGOR;
objprop[OBJ_BOSS_IGOR].sprite = SPR_IGOR;
objprop[OBJ_BOSS_IGOR_DEFEATED].sprite = SPR_IGOR;
objprop[OBJ_IGOR_BALCONY].sprite = SPR_IGOR;
objprop[OBJ_X_TARGET].hurt_sound = SND_ENEMY_HURT_COOL;
objprop[OBJ_X_INTERNALS].shaketime = 9;
objprop[OBJ_X_MAINOBJECT].xponkill = 1;
objprop[OBJ_POOH_BLACK_BUBBLE].xponkill = 0;
objprop[OBJ_POOH_BLACK_DYING].sprite = SPR_POOH_BLACK;
objprop[OBJ_BOOSTER_FALLING].sprite = SPR_PROFESSOR_BOOSTER;
objprop[OBJ_MIMIGA_FARMER_STANDING].sprite = SPR_MIMIGA_FARMER;
objprop[OBJ_MIMIGA_FARMER_WALKING].sprite = SPR_MIMIGA_FARMER;
objprop[OBJ_DROLL_GUARD].sprite = SPR_DROLL;
objprop[OBJ_MA_PIGNON_CLONE].sprite = SPR_MA_PIGNON;
objprop[OBJ_DOCTOR_SHOT_TRAIL].sprite = SPR_DOCTOR_SHOT;
// they're still able to detect when they touch floor; etc,
// but we don't want say a falling one to get blocked by the ceiling.
objprop[OBJ_RED_ENERGY].defaultflags |= FLAG_IGNORE_SOLID;
objprop[OBJ_SUE_TELEPORT_IN].sprite = SPR_SUE;
objprop[OBJ_MISERY_BAT].sprite = SPR_ORANGE_BAT_FINAL;
objprop[OBJ_UD_MINICORE_IDLE].sprite = SPR_UD_MINICORE;
objprop[OBJ_WHIMSICAL_STAR].sprite = SPR_WHIMSICAL_STAR; // for bbox only, object is invisible
// these are set by AI; this is just to silence unimplemented object warnings
#ifdef DEBUG
objprop[OBJ_CRITTER_FLYING].sprite = SPR_CRITTER_FLYING_CYAN;
for(int i=OBJ_SHOTS_START;i<=OBJ_SHOTS_END;i++)
if (objprop[i].sprite==SPR_UNIMPLEMENTED_OBJECT) objprop[i].sprite = SPR_NULL;
#endif
}