//* levman.c - Game level management module //* System library headers #include #include //debug //* Application headers #include "main.h" #include "levprocs.h" #include "procs.h" #include "preset.h" static void WaitLinkIn(register struct SCTRL *scp); static void WaitLinkOut(register struct SCTRL *scp); static void AddPolyCart(void); static void AddPickup(void); //* Static game level command function prototypes static void reset_enemy_control(void); static void set_enemy_min(void); static void set_mine_min(void); static void set_emerge_delay(void); static void set_kills_per_pickup(void); static void add_enemy(void); static void add_mine(void); static void end_of_level(void); static void keep_enemy_min(void); static void loop_start(void); static void loop_until_babies_saved(void); static void loop_until_level_clear(void); static void reduce_emerge_delay(void); static void change_enemy_min(void); //* Initialize command array to hold pointers to the various level command functions static void (*command[]) (void) = { reset_enemy_control, set_enemy_min, set_mine_min, set_emerge_delay, set_kills_per_pickup, add_enemy, add_mine, end_of_level, keep_enemy_min, loop_start, loop_until_babies_saved, loop_until_level_clear, reduce_emerge_delay, change_enemy_min }; //* Array of BGM sound management values static struct { int count; int note; int reps; } BGMlist[] = { 120, 60, 5, 120, 61, 6, 120, 62, 5, 120, 63, 6, 120, 64, 5, 120, 65, 6, 120, 66, 5, 120, 67, 6, 120, 68, 5, 120, 69, 6, 120, 70, 5, 120, 71, 6, 110, 71, 6, 100, 71, 6, 90, 71, 6, 80, 71, 6, 70, 71, 6, 60, 71, 6 }; #define BGM_LIST_TOP (17) //* Top valid index of BGMlist[] static int lastCall; //* Level commands are run until this flag is set static int *levelOp; //* Pointer to current level command op-code static int entranceID; //* Holds ID code of where to place in-coming enemy sprites static int *levLoopStart; //* Points to command at top of level proc loop static struct SCTRL *waitList; //* List head of all sprites waiting in the wings static struct SCTRL *WS[200]; //* Array of pointers to sprites waiting in the wings static int extraDelay; //* Used to stop enemies emerging on top of each other static u_long lastDelay; //* Holds *psdcnt when last time emergeDelay was reduced static u_long lastChange[ENEMY_TYPE_MAX]; //* Holds frame count when each enemy last had it's minimum altered static int mineCode; //* Used by add_mine to ensure mines flash in sequence static int BGMcount; //* Countdown until next BGM sound is played static int BGMnote; //* Note at which BGM sound will be played static int BGMreps; //* Number of times each new note & interval are used static int BGMidx; //* Index into array for current BGM values static int BGMpitch; //* BGM sound pitch - toggled between 0 and 64 //*------------------------------------------------------------------------------------------ //* ManageLevel //*------------------------------------------------------------------------------------- void ManageLevel(void) { register struct SCTRL *scp; register int i, sfxFlag; //* Decay (every vsync) the additional delay counter if (extraDelay) --extraDelay; //* Run current level procedure command(s) lastCall = 0; do { //* Call required function by indexing into the function pointer array (*command[*levelOp]) (); } while (!lastCall); //* Build array of pointers to all sprites waiting in the wings for (i=0, scp=waitList; scp; scp=scp->aliveLinkF) WS[i++] = scp; //* Release any enemy sprites waiting in the wings //* whose delay counters have reached zero sfxFlag = 0; while (i) if (!--WS[--i]->ICSALloopCount) { sfxFlag = 1; //* Trigger SFX below WaitLinkOut(WS[i]); AliveLinkIn(WS[i]); } //* Fire SFX once if any enemies entered from the wings if (sfxFlag) SsUtKeyOn(0, SFX_GHOST, 0, 60+(*psdcnt & 0x00000007), 0, 127, 127); //* Add a bonus pickup if it's time (MISSION game only) if (gameType==MODE_MISSION && killCount>=killsPerPickup) { killCount -= killsPerPickup; AddPickup(); AddPolyCart(); } //* Play BGM sound when appropriate if (!--BGMcount) { SsUtKeyOn(0, SFX_BGM, 0, BGMnote, BGMpitch, 100, 100); if (BGMidx void SetLevel(int levNo) { levelOp = level[levNo]; } //*------------------------------------------------------------------------------------------ //* WaitLinkIn - Adds sprite with scp to double-linked list of 'waiting' sprites //*------------------------------------------------------------------------------------- static void WaitLinkIn(register struct SCTRL *scp) { if (waitList) { //* There's at least 1 object in the list... waitList->aliveLinkB = scp; //* link scp in as first object in the list. scp->aliveLinkF = waitList; } waitList = scp; //* Set list head to point at scp object. } //*------------------------------------------------------------------------------------------ //* WaitLinkOut - Removes sprite with scp from double-linked list of 'waiting' sprites //*------------------------------------------------------------------------------------- static void WaitLinkOut(register struct SCTRL *scp) { if (scp->aliveLinkF) //* A forward link exists... scp->aliveLinkF->aliveLinkB = scp->aliveLinkB; //* so adjust pointers to keep link. if (scp->aliveLinkB) //* A backward link exists... scp->aliveLinkB->aliveLinkF = scp->aliveLinkF; //* so adjust pointers to keep link. else //* No backward link exists... waitList = scp->aliveLinkF; //* scp MUST=waitList so adjust. scp->aliveLinkF = scp->aliveLinkB = NULL; //* Clear pointers } //*------------------------------------------------------------------------------------------ //* AddPolyCart //*------------------------------------------------------------------------------------- static void AddPolyCart(void) { register struct SCTRL *scp; register GsSPRITE *sp; register int x, y, pType; register struct GRID_PAIR *gp; //* Get the x,y coords of an 'empty' grid position gp = GetRandGrid(); //* Convert grid coords to world coords for 16x16 sprite //* Note: odd y grid coords cause x to be right-shifted by //* 8 pixels making the grid a preferable 'brickwork' style y = 55 + (gp->r<<4); x = 28 + (gp->c<<4) + ((gp->r & 0x00000001)<<3); //* Get 2 sprites for PolyCart + HUD box scp = GetSprite(2); //* Get a random PolyCart type pType = (*psdcnt & 0x00000001) + 0x100; //* 0x100 is PolyCart ID bit //* Initialize PolyCart sprite PresetSprite(scp, PRESET_POLYCART); scp->subID = pType; scp->wx = x; scp->wy = y; AliveLinkIn(scp); //* Initialize HUD box sprite sp = (scp = scp->freeLink)->spriteLink; scp->subID = GetHUDidx(); sp->attribute = (A_BRIGHT_ON + A_4BIT_CLUT + A_ROTATE_OFF + A_TRATE_1 + A_TRANS_ON + A_DISPLAY_ON); sp->w = 0; sp->h = 16; sp->tpage = GetTPage(0, 0, timData[TIM_POLYCART].px, timData[TIM_POLYCART].py); sp->v = HUDbox[scp->subID].v; sp->u = HUDbox[scp->subID].u; sp->cx = timData[TIM_POLYCART].cx; sp->cy = timData[TIM_POLYCART].cy; scp->wx = x-10; scp->wy = y+600; scp->high = 620; scp->shadStat = 0; scp->isLight = 1; //* To prevent RGB alteration scp->ICSALop = (pType==CART_RAIN_DEATH) ? rainbowDeathHUD : fruitDropHUD; AliveLinkIn(scp); } //*------------------------------------------------------------------------------------------ //* AddPickup //*------------------------------------------------------------------------------------- static void AddPickup(void) { register struct SCTRL *scp; register GsSPRITE *sp; register int x, y, pType; register struct GRID_PAIR *gp; //* Get the x,y coords of an 'empty' grid position gp = GetRandGrid(); //* Convert grid coords to world coords for 16x16 sprite //* Note: odd y grid coords cause x to be right-shifted by //* 8 pixels making the grid a preferable 'brickwork' style y = 55 + (gp->r<<4); x = 28 + (gp->c<<4) + ((gp->r & 0x00000001)<<3); //* Get 1 sprite for pickup scp = GetSprite(1); //* Get a random PolyCart preset type pType = PRESET_PICKUP_MILKBOT + (rand()%7); PresetSprite(scp, pType); scp->wx = x; scp->wy = y; AliveLinkIn(scp); } //* //* Start of level control command functions //* //*------------------------------------------------------------------------------------------ //* reset_enemy_control //*------------------------------------------------------------------------------------- static void reset_enemy_control(void) { register int i; //* Reset individual & total enemies alive count & minimum to 0 //* Also, reset frame count (used in change_enemy_min) for ALL enemy types for (i=0; i static void set_enemy_min(void) { enemyAlive[*++levelOp][1] = *++levelOp; ++levelOp; } //*------------------------------------------------------------------------------------------ //* set_mine_min //*------------------------------------------------------------------------------------- static void set_mine_min(void) { mineMin = *++levelOp; ++levelOp; } //*------------------------------------------------------------------------------------------ //* set_emerge_delay //*------------------------------------------------------------------------------------- static void set_emerge_delay(void) { emergeDelay = *++levelOp; ++levelOp; } //*------------------------------------------------------------------------------------------ //* set_kills_per_pickup //*------------------------------------------------------------------------------------- static void set_kills_per_pickup(void) { killsPerPickup = *++levelOp; ++levelOp; } //*------------------------------------------------------------------------------------------ //* add_enemy //*------------------------------------------------------------------------------------- static void add_enemy(void) { register struct SCTRL *scp; register GsSPRITE *sp; register int enType, startR, startC, enDir; //* Command operands //* Get command operands enType = *++levelOp; //* Enemy type startR = *++levelOp; //* Placement grid row startC = *++levelOp; //* Placement grid column enDir = *++levelOp; //* Enemy initial direction scp = GetSprite(1); sp = scp->spriteLink; //* Point to actual GsSPRITE object //* Consider enemy type switch (enType) { case ENEMY_RED_GHOST: PresetSprite(scp, PRESET_ENEMY_GHOST); sp->cx = timData[TIM_ENEMY1].cx; //* Set ghost colour scp->frameLock = LOCK_ALTFRAMES; //* Set ghost speed break; case ENEMY_PINK_GHOST: PresetSprite(scp, PRESET_ENEMY_GHOST); sp->cx = timData[TIM_EN_PCLUT].cx; scp->frameLock = 0x00eeeeee; break; case ENEMY_CYAN_GHOST: PresetSprite(scp, PRESET_ENEMY_GHOST); sp->cx = timData[TIM_EN_CCLUT].cx; scp->frameLock = LOCK_2FRAMES; break; case ENEMY_ORANGE_GHOST: PresetSprite(scp, PRESET_ENEMY_GHOST); sp->cx = timData[TIM_EN_OCLUT].cx; scp->frameLock = 0x25294a52; break; case ENEMY_UFO: PresetSprite(scp, PRESET_ENEMY_UFO); scp->frameLock = LOCK_ALTFRAMES; break; } scp->subID = enType; scp->currLock = RunThisTime(scp->frameLock); //* To run initialization code //* Convert grid coords to world coords //* Note: grid is 0-26 rows (13 is centre) by 0-28 columns (14 is centre) scp->wx = 32 + (startC<<4); scp->wy = 56 + (startR<<4); //* Initialize direction-specific sprite members switch (scp->dir = enDir) { case DIR_DOWN: if (scp->subID!=ENEMY_UFO) { //* UFO u,v is set in preset! sp->u = 0; sp->v = 0; } scp->accelX = 0; scp->accelY = scp->speed; break; case DIR_RIGHT: if (scp->subID!=ENEMY_UFO) { sp->u = 0; sp->v = 32; } scp->accelX = scp->speed; scp->accelY = 0; break; case DIR_LEFT: if (scp->subID!=ENEMY_UFO) { sp->u = 128; sp->v = 32; } scp->accelX = -scp->speed; scp->accelY = 0; break; case DIR_UP: if (scp->subID!=ENEMY_UFO) { sp->u = 128; sp->v = 0; } scp->accelX = 0; scp->accelY = -scp->speed; break; case DIR_DOWN_RIGHT: if (scp->subID!=ENEMY_UFO) { sp->u = 0; sp->v = 96; } scp->accelX = scp->speed; scp->accelY = scp->speed; break; case DIR_DOWN_LEFT: if (scp->subID!=ENEMY_UFO) { sp->u = 128; sp->v = 96; } scp->accelX = -scp->speed; scp->accelY = scp->speed; break; case DIR_UP_RIGHT: if (scp->subID!=ENEMY_UFO) { sp->u = 0; sp->v = 64; } scp->accelX = scp->speed; scp->accelY = -scp->speed; break; case DIR_UP_LEFT: if (scp->subID!=ENEMY_UFO) { sp->u = 128; sp->v = 64; } scp->accelX = -scp->speed; scp->accelY = -scp->speed; break; } AliveLinkIn(scp); ++enemyAlive[enType][0]; //* Increase alive count for this enemy type ++enemyAlive[TOTAL_ALIVE][0]; //* Increase alive total count ++levelOp; } //*------------------------------------------------------------------------------------------ //* add_mine //*------------------------------------------------------------------------------------- static void add_mine(void) { register struct SCTRL *scp; register GsSPRITE *sp; register int r, c; //* Get row, column operands r = *++levelOp; c = *++levelOp; //* Remove r,c grid position from 'available' list GetAbsGrid(r, c); //* Create mine sprite at required world coords sp = (scp = GetSprite(1))->spriteLink; PresetSprite(scp, PRESET_MINE); scp->wx = 28 + (c<<4) + ((r & 0x00000001)<<3); scp->wy = 55 + (r<<4); //* Have mine start 1 step on from last one scp->ICSALloopCount = INFINITE_LOOP; scp->ICSALloopStart = scp->ICSALop+2; switch (mineCode) { case 0: scp->ICSALop += 2; break; case 1: scp->ICSALop += 4; break; case 2: scp->ICSALop += 6; sp->u += 16; break; case 3: scp->ICSALop += 8; sp->u += 32; break; case 4: scp->ICSALop += 22; sp->u += 48; break; case 5: scp->ICSALop += 24; sp->u += 32; break; } (mineCode < 5) ? (mineCode++) : (mineCode=0); AliveLinkIn(scp); ++mineCount; ++levelOp; } //*------------------------------------------------------------------------------------------ //* end_of_level //*------------------------------------------------------------------------------------- static void end_of_level(void) { lastCall = 1; } //*------------------------------------------------------------------------------------------ //* keep_enemy_min //*------------------------------------------------------------------------------------- static void keep_enemy_min(void) { register struct SCTRL *scp; register GsSPRITE *sp; register int i; //* Make sure enemies alive are at least at their minimum level for (i=1; ifreeLink) { sp = scp->spriteLink; //* Point to actual GsSPRITE object if (i==ENEMY_UFO) { //* New enemy is a UFO... //* Initialize common sprite members PresetSprite(scp, PRESET_ENEMY_UFO); scp->subID = ENEMY_UFO; scp->frameLock = LOCK_ALTFRAMES; } else { //* New enemy is a ghost... //* Initialize common sprite members PresetSprite(scp, PRESET_ENEMY_GHOST); //* Consider enemy type switch (scp->subID = i) { //* Set enemy type case ENEMY_RED_GHOST: sp->cx = timData[TIM_ENEMY1].cx; //* Set ghost colour scp->frameLock = LOCK_ALTFRAMES; //* Set ghost speed break; case ENEMY_PINK_GHOST: sp->cx = timData[TIM_EN_PCLUT].cx; scp->frameLock = 0x00eeeeee; break; case ENEMY_CYAN_GHOST: sp->cx = timData[TIM_EN_CCLUT].cx; scp->frameLock = LOCK_2FRAMES; break; case ENEMY_ORANGE_GHOST: sp->cx = timData[TIM_EN_OCLUT].cx; scp->frameLock = 0x25294a52; break; } } //* Set entry point specific members //* Note: wx and wy are set so enemies are 44 pixels away from //* reaching the true play area i.e. the end of the doorway tunnels switch (entranceID) { case 0: //* Left of top door scp->dir = DIR_DOWN; if (scp->subID!=ENEMY_UFO) { //* UFO u,v is set in preset! sp->u = 0; sp->v = 0; } scp->accelX = 0; scp->accelY = scp->speed; scp->wx = 223; scp->wy = -4; break; case 1: //* Right of top door scp->dir = DIR_DOWN; if (scp->subID!=ENEMY_UFO) { sp->u = 0; sp->v = 0; } scp->accelX = 0; scp->accelY = scp->speed; scp->wx = 258; scp->wy = -4; break; case 2: //* Right door scp->dir = DIR_LEFT; if (scp->subID!=ENEMY_UFO) { sp->u = 128; sp->v = 32; } scp->accelX = -scp->speed; scp->accelY = 0; scp->wx = 533; scp->wy = 280; break; case 3: //* Right of bottom door scp->dir = DIR_UP; if (scp->subID!=ENEMY_UFO) { sp->u = 128; sp->v = 0; } scp->accelX = 0; scp->accelY = -scp->speed; scp->wx = 258; scp->wy = 565; break; case 4: //* Left of bottom door scp->dir = DIR_UP; if (scp->subID!=ENEMY_UFO) { sp->u = 128; sp->v = 0; } scp->accelX = 0; scp->accelY = -scp->speed; scp->wx = 223; scp->wy = 565; break; case 5: //* Left door scp->dir = DIR_RIGHT; if (scp->subID!=ENEMY_UFO) { sp->u = 0; sp->v = 32; } scp->accelX = scp->speed; scp->accelY = 0; scp->wx = -54; scp->wy = 280; break; } scp->currLock = RunThisTime(scp->frameLock); //* To run initialization code scp->ICSALloopCount = emergeDelay+extraDelay; //* Set delay counter WaitLinkIn(scp); //* Add sprite to 'waiting' linked list ++enemyAlive[i][0]; //* Increase alive count for this enemy type ++enemyAlive[TOTAL_ALIVE][0]; //* Increase alive total count //* Set next entry point ID code if (++entranceID==6) { entranceID = 0; extraDelay += 46; } } } ++levelOp; } //*------------------------------------------------------------------------------------------ //* loop_start //*------------------------------------------------------------------------------------- static void loop_start(void) { levLoopStart = ++levelOp; } //*------------------------------------------------------------------------------------------ //* loop_until_babies_saved //*------------------------------------------------------------------------------------- static void loop_until_babies_saved(void) { register struct SCTRL *scp; register int i; register u_int low; if (babiesSaved==6) { //* All babies have been saved... //* Find lowest delay count out of sprites waiting in wings for (low=0xffffffff, scp=waitList; scp; scp=scp->aliveLinkF) { if (scp->ICSALloopCount < low) low = scp->ICSALloopCount; } //* Reduce delay counters so monsters start emerging immediately! if (--low) { for (scp=waitList; scp; scp=scp->aliveLinkF) scp->ICSALloopCount -= low; } ++levelOp; //* Drop out of loop } else { //* At least 1 baby still needs saving... levelOp = levLoopStart; //* Set to reiterate loop lastCall = 1; } } //*------------------------------------------------------------------------------------------ //* loop_until_level_clear //*------------------------------------------------------------------------------------- static void loop_until_level_clear(void) { register GsSPRITE *sp; if (enemyAlive[TOTAL_ALIVE][0]) { //* At least 1 enemy is still alive... levelOp = levLoopStart; //* Set to reiterate loop lastCall = 1; } else { //* All remaining enemies have been killed... SsUtKeyOn(0, SFX_BUZZER, 0, 60, 0, 127, 127); ok2beam = 1; //* Authorize BB to beam-up ++levelOp; //* Drop out of loop //* Update OSD message HUD sp = messHudScp->spriteLink; sp->attribute = (A_BRIGHT_OFF + A_4BIT_CLUT + A_ROTATE_OFF + A_TRATE_1 + A_TRANS_OFF + A_DISPLAY_ON); sp->w = 4; sp->v = timData[TIM_MESS_HUD].py+32; messHudScp->ICSALop = messHudText; } } //*------------------------------------------------------------------------------------------ //* reduce_emerge_delay //*------------------------------------------------------------------------------------- static void reduce_emerge_delay(void) { register int dec, freq; dec = *++levelOp; //* Get 1st operand (frame count to decrease emergeDelay by) freq = *++levelOp; //* Get 2nd operand (frame frequency of when to reduce emergeDelay) if ((*psdcnt-lastDelay) >= freq) { if ((emergeDelay-=dec) < 10) emergeDelay = 10; lastDelay = *psdcnt; } ++levelOp; } //*------------------------------------------------------------------------------------------ //* change_enemy_min //*------------------------------------------------------------------------------------- static void change_enemy_min(void) { register int enType, dif, hiLo, freq, n; enType = *++levelOp; //* Get 1st operand (enemy type constant) dif = *++levelOp; //* Get 2nd operand (amount to change min by; can be + or -) hiLo = *++levelOp; //* Get 3rd operand (highest/lowest that min can be taken to) freq = *++levelOp; //* Get 4th operand (frame frequency of when to change min) if ((*psdcnt-lastChange[enType]) >= freq) { //* Time to change... if (dif < 0) { //* Try to decrease minimum to no LOWER than hiLo enemyAlive[enType][1] = (n=enemyAlive[enType][1]+dif) < hiLo ? hiLo : n; } else { //* Try to increase minimum to no HIGHER than hiLo enemyAlive[enType][1] = (n=enemyAlive[enType][1]+dif) > hiLo ? hiLo : n; } lastChange[enType] = *psdcnt; //* Record frame that change has been made on } ++levelOp; }