X-Git-Url: http://4ch.mooo.com/gitweb/?p=16.git;a=blobdiff_plain;f=16%2Fkeen456%2FKEEN4-6%2FCK_KEEN2.C;fp=16%2Fkeen456%2FKEEN4-6%2FCK_KEEN2.C;h=e30e5d7a85e8914c29c803e02e666b1bce76e5e2;hp=0000000000000000000000000000000000000000;hb=7d1948e210bb7b58af0a0412e71f2a0a0a2010af;hpb=ebc247a0a67daa69a027f31d9d7d9572db765e56 diff --git a/16/keen456/KEEN4-6/CK_KEEN2.C b/16/keen456/KEEN4-6/CK_KEEN2.C new file mode 100755 index 00000000..e30e5d7a --- /dev/null +++ b/16/keen456/KEEN4-6/CK_KEEN2.C @@ -0,0 +1,1606 @@ +/* Reconstructed Commander Keen 4-6 Source Code + * Copyright (C) 2021 K1n9_Duk3 + * + * This file is loosely based on: + * Keen Dreams Source Code + * Copyright (C) 2014 Javier M. Chavez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* +CK_KEEN2.C +========== + +Contains the following actor types (in this order): + +- Score Box & Demo sprites +- Keen (world map) +- Flags (world map) +- Neural Stunner Shots +- Gem Door Opener +- Card Door Opener (Keen 5 only) + +*/ + +#include "CK_DEF.H" + +Direction opposite[8] = {dir_South, dir_SouthWest, dir_West, dir_NorthWest, dir_North, dir_NorthEast, dir_East, dir_SouthEast}; + +/* +============================================================================= + + SCORE BOX ROUTINES + +============================================================================= +*/ + +statetype s_score = { 0, 0, think, false, false, 0, 0, 0, NULL, NULL, NULL, NULL}; +statetype s_demo = {DEMOPLAQUESPR, DEMOPLAQUESPR, think, false, false, 0, 0, 0, NULL, NULL, NULL, NULL}; + +/* +====================== += += SpawnScore += +====================== +*/ + +void SpawnScore(void) +{ + scoreobj->obclass = inertobj; + scoreobj->priority = 3; + scoreobj->active = ac_allways; + scoreobj->needtoclip = cl_noclip; + scoreobj->temp2 = -1; + scoreobj->temp1 = -1; + scoreobj->temp3 = -1; + scoreobj->temp4 = -1; + if (scorescreenkludge) + { + scoreobj->state = &sc_deadstate; + } + else if (!DemoMode) + { + NewState(scoreobj, &s_score); + } + else + { + NewState(scoreobj, &s_demo); + CA_MarkGrChunk(DEMOPLAQUESPR); + } +} + + +// Taken from Keen Dreams: MemDrawChar and ShiftScore + +/* +====================== += += MemDrawChar += +====================== +*/ + +#if GRMODE == EGAGR + +void MemDrawChar(Sint16 char8, Uint8 far *dest, Uint16 width, Uint16 planesize) +{ + Uint16 source = (Uint16)grsegs[STARTTILE8]; // Note: this differs from Keen Dreams source + +asm mov si,[char8] +asm shl si,1 +asm shl si,1 +asm shl si,1 +asm shl si,1 +asm shl si,1 // index into char 8 segment + +asm mov ds,[WORD PTR source] // Note: this differs from Keen Dreams source +asm mov es,[WORD PTR dest+2] + +asm mov cx,4 // draw four planes +asm mov bx,[width] +asm dec bx + +planeloop: + +asm mov di,[WORD PTR dest] + +asm movsb +asm add di,bx +asm movsb +asm add di,bx +asm movsb +asm add di,bx +asm movsb +asm add di,bx +asm movsb +asm add di,bx +asm movsb +asm add di,bx +asm movsb +asm add di,bx +asm movsb + +asm mov ax,[planesize] +asm add [WORD PTR dest],ax + +asm loop planeloop + +asm mov ax,ss +asm mov ds,ax +} + +#elif GRMODE == CGAGR + +void MemDrawChar (int char8,byte far *dest,unsigned width,unsigned planesize) +{ +asm mov si,[char8] +asm shl si,1 +asm shl si,1 +asm shl si,1 +asm shl si,1 // index into char 8 segment + +asm mov ds,[WORD PTR grsegs+STARTTILE8*2] +asm mov es,[WORD PTR dest+2] + +asm mov bx,[width] +asm sub bx,2 + +asm mov di,[WORD PTR dest] + +asm movsw +asm add di,bx +asm movsw +asm add di,bx +asm movsw +asm add di,bx +asm movsw +asm add di,bx +asm movsw +asm add di,bx +asm movsw +asm add di,bx +asm movsw +asm add di,bx +asm movsw + +asm mov ax,ss +asm mov ds,ax + + planesize++; // shut the compiler up +} +#endif + +/* +==================== += += ShiftScore += +==================== +*/ +#if GRMODE == EGAGR +void ShiftScore (void) +{ + spritetabletype far *spr; + spritetype _seg *dest; + + spr = &spritetable[SCOREBOXSPR-STARTSPRITES]; + dest = (spritetype _seg *)grsegs[SCOREBOXSPR]; + + CAL_ShiftSprite (FP_SEG(dest),dest->sourceoffset[0], + dest->sourceoffset[1],spr->width,spr->height,2); + + CAL_ShiftSprite (FP_SEG(dest),dest->sourceoffset[0], + dest->sourceoffset[2],spr->width,spr->height,4); + + CAL_ShiftSprite (FP_SEG(dest),dest->sourceoffset[0], + dest->sourceoffset[3],spr->width,spr->height,6); +} +#endif + +/* +=============== += += UpdateScore += +=============== +*/ + +void UpdateScore(objtype *ob) +{ + char str[10],*ch; + spritetype _seg *block; + Uint8 far *dest; + Uint16 i, length, width, planesize, number; + boolean changed; + + if (scorescreenkludge) + return; + + if (DemoMode) + { + DrawDemoPlaque(ob); + return; + } + + if (!showscorebox) + return; + + changed = false; + +//code below is a combination of ScoreThink and ScoreReact from Keen Dreams with minor changes + +// +// score changed +// + if ((gamestate.score>>16) != ob->temp1 + || (Uint16)gamestate.score != ob->temp2 ) + { + block = (spritetype _seg *)grsegs[SCOREBOXSPR]; + width = block->width[0]; + planesize = block->planesize[0]; + dest = (Uint8 far *)grsegs[SCOREBOXSPR]+block->sourceoffset[0] + + planesize + width*4; + + ltoa (gamestate.score,str,10); + + // erase leading spaces + length = strlen(str); + for (i=9;i>length;i--) + MemDrawChar (41,dest+=CHARWIDTH,width,planesize); + + // draw digits + ch = str; + while (*ch) + MemDrawChar (*ch++ - '0'+42,dest+=CHARWIDTH,width,planesize); + +#if GRMODE == EGAGR + ShiftScore (); +#endif + ob->needtoreact = true; + ob->temp1 = gamestate.score>>16; + ob->temp2 = gamestate.score; + + changed = true; + } + +// +// ammo changed +// + number = gamestate.ammo; + if (number != ob->temp3) + { + block = (spritetype _seg *)grsegs[SCOREBOXSPR]; + width = block->width[0]; + planesize = block->planesize[0]; + dest = (byte far *)grsegs[SCOREBOXSPR]+block->sourceoffset[0] + + planesize + width*20 + 7*CHARWIDTH; + + if (number > 99) + strcpy (str,"99"); + else + ltoa (number,str,10); + + // erase leading spaces + length = strlen(str); + for (i=2;i>length;i--) + MemDrawChar (41,dest+=CHARWIDTH,width,planesize); + + // draw digits + ch = str; + while (*ch) + MemDrawChar (*ch++ - '0'+42,dest+=CHARWIDTH,width,planesize); + +#if GRMODE == EGAGR + ShiftScore (); +#endif + ob->needtoreact = true; + ob->temp3 = number; + + changed = true; + } + +// +// lives changed +// + if (gamestate.lives != ob->temp4) + { + block = (spritetype _seg *)grsegs[SCOREBOXSPR]; + width = block->width[0]; + planesize = block->planesize[0]; + dest = (byte far *)grsegs[SCOREBOXSPR]+block->sourceoffset[0] + + planesize + width*20 + 2*CHARWIDTH; + + if (gamestate.lives > 99) + strcpy (str,"99"); + else + ltoa (gamestate.lives,str,10); + + // erase leading spaces + length = strlen(str); + for (i=2;i>length;i--) + MemDrawChar (41,dest+=CHARWIDTH,width,planesize); + + // draw digits + ch = str; + while (*ch) + MemDrawChar (*ch++ - '0'+42,dest+=CHARWIDTH,width,planesize); + +#if GRMODE == EGAGR + ShiftScore (); +#endif + ob->needtoreact = true; + ob->temp4 = gamestate.lives; + + changed = true; + } + +/* +Note: +----- + +It would be more efficient to use + + if (changed) + ShiftScore(); + +here instead of the individual ShiftScore() calls above. Because if the player +gains a life by collecting points items, both the score and lives numbers need +to be updated, which means the sprite would be shifted twice. And if the player +fires a shot during the same frame, the ammo number also needs to be updated, +leading to up to three shifts in one frame. +*/ + + if (ob->x != originxglobal || ob->y != originyglobal) + { + ob->x = originxglobal; + ob->y = originyglobal; + changed = true; + } + + if (changed) +#if GRMODE == EGAGR + RF_PlaceSprite(&ob->sprite, ob->x+4*PIXGLOBAL, ob->y+4*PIXGLOBAL, SCOREBOXSPR, spritedraw, 3); +#elif GRMODE == CGAGR + RF_PlaceSprite(&ob->sprite, ob->x+8*PIXGLOBAL, ob->y+8*PIXGLOBAL, SCOREBOXSPR, spritedraw, 3); +#endif +} + +/* +=============== += += DrawDemoPlaque += +=============== +*/ + +void DrawDemoPlaque(objtype *ob) +{ + if (ob->x != originxglobal || ob->y != originyglobal) + { + ob->x = originxglobal; + ob->y = originyglobal; + RF_PlaceSprite(&ob->sprite, ob->x + 160*PIXGLOBAL - 32*PIXGLOBAL, ob->y + 8*PIXGLOBAL, DEMOPLAQUESPR, spritedraw, 3); + } +} + + +/* +============================================================================= + + MINI KEEN + +player->temp1 = dir +player->temp2 = animation stage + +============================================================================= +*/ + +#ifdef KEEN4 +statetype s_keenonfoot1 = {WOLRDKEENRIDE1SPR, WOLRDKEENRIDE1SPR, stepthink, false, false, 30, 0, 0, T_FootFly, NULL, R_Draw, &s_keenonfoot2}; +statetype s_keenonfoot2 = {WOLRDKEENRIDE2SPR, WOLRDKEENRIDE2SPR, stepthink, false, false, 30, 0, 0, T_FootFly, NULL, R_Draw, &s_keenonfoot1}; +statetype s_worldswim = {0, 0, slide, true, false, 6, 16, 16, T_KeenWorldSwim, NULL, R_Draw, &s_worldswim}; +#endif + +#ifdef KEEN5 +statetype s_worldelevate = {-1, -1, think, true, false, 6, 16, 16, T_Elevate, NULL, R_Draw, NULL}; +#endif + +statetype s_worldkeen = {0, 0, stepthink, false, false, 360, 0, 0, T_KeenWorld, NULL, R_Draw, &s_worldkeenwave1}; + +statetype s_worldkeenwave1 = {WORLDKEENWAVE1SPR, WORLDKEENWAVE1SPR, stepthink, false, false, 20, 0, 0, T_KeenWorld, NULL, R_Draw, &s_worldkeenwave2}; +statetype s_worldkeenwave2 = {WORLDKEENWAVE2SPR, WORLDKEENWAVE2SPR, stepthink, false, false, 20, 0, 0, T_KeenWorld, NULL, R_Draw, &s_worldkeenwave3}; +statetype s_worldkeenwave3 = {WORLDKEENWAVE1SPR, WORLDKEENWAVE1SPR, stepthink, false, false, 20, 0, 0, T_KeenWorld, NULL, R_Draw, &s_worldkeenwave4}; +statetype s_worldkeenwave4 = {WORLDKEENWAVE2SPR, WORLDKEENWAVE2SPR, stepthink, false, false, 20, 0, 0, T_KeenWorld, NULL, R_Draw, &s_worldkeenwave5}; +statetype s_worldkeenwave5 = {WORLDKEENWAVE1SPR, WORLDKEENWAVE1SPR, stepthink, false, false, 20, 0, 0, T_KeenWorldWalk, NULL, R_Draw, &s_worldkeen}; + +statetype s_worldkeenwalk = {0, 0, slide, true, false, 4, 24, 24, T_KeenWorldWalk, NULL, R_Draw, &s_worldkeenwalk}; + +Sint16 worldshapes[8] = {WORLDKEENU1SPR-1, WORLDKEENUR1SPR-1, WORLDKEENR1SPR-1, WORLDKEENDR1SPR-1, WORLDKEEND1SPR-1, WORLDKEENDL1SPR-1, WORLDKEENL1SPR-1, WORLDKEENUL1SPR-1}; //-1 to everything because worldanims values are 1-based +Sint16 worldanims[4] = {2, 3, 1, 3}; +#ifdef KEEN4 +Sint16 swimshapes[8] = {WORLDKEENSWIMU1SPR, WORLDKEENSWIMUR1SPR, WORLDKEENSWIMR1SPR, WORLDKEENSWIMDR1SPR, WORLDKEENSWIMD1SPR, WORLDKEENSWIMDL1SPR, WORLDKEENSWIML1SPR, WORLDKEENSWIMUL1SPR}; +#endif +#ifndef KEEN6 +Sint16 tiledir[4] = {dir_South, dir_West, dir_North, dir_East}; +#endif + +/* +====================== += += SpawnWorldKeen += +====================== +*/ + +void SpawnWorldKeen(Sint16 x, Sint16 y) +{ +#ifdef KEEN4 + if (playstate == ex_foot) + { + player->needtoclip = cl_noclip; + player->obclass = keenobj; + player->x = gamestate.worldx; + player->y = gamestate.worldy; + player->active = ac_allways; + player->priority = 3; + player->xdir = 0; + player->ydir = 0; + if (gamestate.worldx < 20*TILEGLOBAL) + { + player->temp1 = 280; + player->xspeed = (30*TILEGLOBAL - player->x)/280 + 1; + player->yspeed = (55*TILEGLOBAL - player->y)/280 + 1; + } + else + { + player->temp1 = 140; + player->xspeed = (Sint16)(16*TILEGLOBAL - player->x)/140 + 1; + player->yspeed = (Sint16)(47*TILEGLOBAL - player->y)/140 + 1; + } + NewState(player, &s_keenonfoot1); + return; + } +#endif + + player->obclass = keenobj; + if (gamestate.worldx == 0) + { + player->x = CONVERT_TILE_TO_GLOBAL(x); + player->y = CONVERT_TILE_TO_GLOBAL(y); + } + else + { + player->x = gamestate.worldx; + player->y = gamestate.worldy; + } + player->active = ac_allways; + player->priority = 1; + player->xdir = 0; + player->ydir = 0; + player->temp1 = dir_West; + player->temp2 = 3; + player->temp3 = 0; + player->shapenum = WORLDKEENL3SPR; + NewState(player, &s_worldkeen); +} + +#ifdef KEEN5 +/* +====================== += += SpawnWorldKeenPort += +====================== +*/ + +void SpawnWorldKeenPort(Uint16 tileX, Uint16 tileY) +{ + player->obclass = keenobj; + player->x = CONVERT_TILE_TO_GLOBAL(tileX); + player->y = CONVERT_TILE_TO_GLOBAL(tileY); + player->active = ac_allways; + player->priority = 1; + player->xdir = 0; + player->ydir = 0; + player->temp1 = dir_West; + player->temp2 = 3; + player->temp3 = 0; + player->shapenum = WORLDKEENL3SPR; + NewState(player, &s_worldkeen); +} +#endif + + +/* +====================== += += CheckEnterLevel += +====================== +*/ + +void CheckEnterLevel(objtype *ob) +{ + Uint16 x, y, info; + + for (y = ob->tiletop; y <= ob->tilebottom; y++) + { + for (x = ob->tileleft; x <= ob->tileright; x++) + { + info = *(mapsegs[2]+mapbwidthtable[y]/2 + x); + if (info > 0xC000 && info <= (0xC000 + 18)) + { + gamestate.worldx = ob->x; + gamestate.worldy = ob->y; + gamestate.mapon = info - 0xC000; + playstate = ex_completed; + SD_PlaySound(SND_ENTERLEVEL); + } + } + } +} + +/* +====================== += += T_KeenWorld += +====================== +*/ + +void T_KeenWorld(objtype *ob) +{ + if (c.dir != dir_None) + { + ob->state = &s_worldkeenwalk; + ob->temp2 = 0; + T_KeenWorldWalk(ob); + } + if (jumpbutton || pogobutton || firebutton) + { + CheckEnterLevel(ob); + } +} + +/* +====================== += += T_KeenWorldWalk += +====================== +*/ + +void T_KeenWorldWalk(objtype *ob) +{ + if (ob->temp3) + { + ob->temp3 -= 4; + if (ob->temp3 < 0) + ob->temp3 = 0; + } + else + { + ob->xdir = c.xaxis; + ob->ydir = c.yaxis; + if (pogobutton || firebutton || jumpbutton) + { + CheckEnterLevel(ob); + } + if (c.dir == dir_None) + { + ob->state = &s_worldkeen; + ob->shapenum = worldshapes[ob->temp1] + 3; + return; + } + ob->temp1 = c.dir; + } + if (++ob->temp2 == 4) + ob->temp2 = 0; + ob->shapenum = worldshapes[ob->temp1] + worldanims[ob->temp2]; + + if (ob->temp2 == 1) + { + SD_PlaySound(SND_WORLDWALK1); + } + else if (ob->temp2 == 3) + { + SD_PlaySound(SND_WORLDWALK2); + } +} + +#ifdef KEEN4 +/* +====================== += += T_FootFly += +====================== +*/ + +void T_FootFly(objtype *ob) +{ + ob->temp1 = ob->temp1 - tics; + xtry = ob->xspeed * tics; + ytry = ob->yspeed * tics; + if (ob->temp1 <= 0) + { + xtry -= ob->xspeed * -ob->temp1; + ytry -= ob->yspeed * -ob->temp1; + ob->priority = 1; + ob->temp1 = dir_West; + ob->temp2 = 3; + ob->temp3 = 0; + player->xdir = 0; + player->ydir = 0; + ob->state = &s_worldkeen; + ob->shapenum = WORLDKEENL3SPR; + ob->needtoclip = cl_midclip; + } +} + +/* +====================== += += T_KeenWorldSwim += +====================== +*/ + +void T_KeenWorldSwim(objtype *ob) +{ + if (ob->temp3) + { + ob->temp3 -= 6; + if (ob->temp3 < 0) + ob->temp3 = 0; + } + else + { + ob->xdir = c.xaxis; + ob->ydir = c.yaxis; + if (c.xaxis || c.yaxis) + ob->temp1 = c.dir; + } + ob->shapenum = swimshapes[ob->temp1] + ob->temp2; + if (++ob->temp2 == 2) + ob->temp2 = 0; + + if (ob->temp2 == 0) + { + SD_PlaySound(SND_SWIM1); + } + else + { + SD_PlaySound(SND_SWIM2); + } +} + +#else // NOT Keen 4 (i.e. Keen 5 & 6): + +/* +====================== += += Teleport += +====================== +*/ + +void Teleport(Uint16 tileX, Uint16 tileY) +{ + Uint16 tile, globalx, globaly, duration, move; + objtype *o; + objtype *ob = player; + + // + // enter the teleporter + // + SD_PlaySound(SND_TELEPORT); + globalx = CONVERT_TILE_TO_GLOBAL(tileX); + globaly = CONVERT_TILE_TO_GLOBAL(tileY); + +#ifdef KEEN6Ev15 + // We need to make the compiler "forget" that duration starts at 0 + // to make sure the while-loop check is performed when entering the + // loop. Can't change compiler settings since we do need that loop + // optimization for the for-loop at the end of this routine. + if (true) + duration = 0; +#else + duration = 0; +#endif + + while (duration < 130) + { + RF_Refresh(); + move = tics*2; + duration += tics; + + if (ob->x == globalx && ob->y == globaly) + break; + + if (ob->y < globaly) + { + ob->y += move; + if (ob->y > globaly) + ob->y = globaly; + } + else if (ob->y > globaly) + { + ob->y -= move; + if (ob->y < globaly) + ob->y = globaly; + } + + if (ob->x < globalx) + { + ob->x += move; + if (ob->x > globalx) + ob->x = globalx; + } + else if (ob->x > globalx) + { + ob->x -= move; + if (ob->x < globalx) + ob->x = globalx; + } + + ob->shapenum = ((TimeCount >> 3) % 3) + WORLDKEENU1SPR; + RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority); + + tile = ((TimeCount >> 2) & TELEPORERTILEMASK) + TELEPORTERTILE1; + RF_MemToMap(&tile, 1, tileX, tileY, 1, 1); + } + + tile = TELEPORTERTILE2; + RF_MemToMap(&tile, 1, tileX, tileY, 1, 1); + + // + // teleport to new location + // + tile = *(mapsegs[2]+mapbwidthtable[tileY]/2 + tileX); + tileX = tile >> 8; + tileY = tile & 0x7F; // BUG? y coordinate is limited to 1..127 + ob->x = CONVERT_TILE_TO_GLOBAL(tileX); + ob->y = CONVERT_TILE_TO_GLOBAL(tileY); + ob->xdir = 0; + ob->ydir = 1; + ob->temp1 = dir_South; + NewState(ob, ob->state); + CenterActor(ob); + + // + // draw flags/signs for new location + // + for (o=player->next; o; o=o->next) + { + if (!o->active && o->obclass == flagobj + && o->tileright >= originxtile-1 && o->tileleft <= originxtilemax+1 + && o->tiletop <= originytilemax+1 && o->tilebottom >= originytile-1) + { + o->needtoreact = true; + o->active = ac_yes; + RF_PlaceSprite(&o->sprite, o->x, o->y, o->shapenum, spritedraw, o->priority); + } + } + UpdateScore(scoreobj); + RF_Refresh(); + RF_Refresh(); + + // + // leave teleporter + // + SD_PlaySound(SND_TELEPORT); + + for (duration = 0; duration < 90; ) + { + RF_Refresh(); + duration += tics; + ob->y += tics*2 + tics; + + ob->shapenum = ((TimeCount >> 3) % 3) + WORLDKEEND1SPR; + RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority); + + tile = ((TimeCount >> 2) & TELEPORERTILEMASK) + TELEPORTERTILE3; + RF_MemToMap(&tile, 1, tileX, tileY, 1, 1); + } + + tile = TELEPORTERTILE4; + RF_MemToMap(&tile, 1, tileX, tileY, 1, 1); + xtry = ytry = 0; + ClipToWalls(ob); +} + +#ifdef KEEN5 + +/* +====================== += += T_Elevate += +====================== +*/ + +void T_Elevate(objtype *ob) +{ + Sint16 i, x, y, tx, ty; + Uint16 tiles[2][2]; + + ytry = ob->ydir * 64 * tics; + if (ob->x != ob->temp2) + { + xtry = ob->xdir * 12 * tics; + if ( (ob->xdir == 1 && ob->x + xtry > ob->temp2) + || (ob->xdir == -1 && ob->x + xtry < ob->temp2) ) + { + xtry = ob->temp2 - ob->x; + } + } + + // + // Keen has no sprite in this state, so we need to update the hitbox manually + // to avoid issues (the screen scrolling routines use left/right/top/bottom) + // + ob->left = ob->x + xtry; + ob->right = ob->left + (TILEGLOBAL-1); + ob->top = ob->y + ytry; + ob->bottom = ob->top + (TILEGLOBAL-1); + + if (ob->ydir == 1) + { + if (ob->y + ytry < ob->temp1) + return; + } + else + { + if (ob->y + ytry > ob->temp1) + return; + } + + // + // the invisible Keen has arrived at its destination + // + ytry = 0; + xtry = 0; + ob->x = ob->temp2; + ob->y = ob->temp1; + ob->priority = 1; + ob->temp1 = 4; + ob->temp2 = 3; + ob->temp3 = 0; + player->xdir = 0; + player->ydir = 0; + ob->state = &s_worldkeen; + ob->shapenum = WORLDKEEND3SPR; + ob->needtoclip = cl_midclip; + tx = CONVERT_GLOBAL_TO_TILE(ob->x); + ty = CONVERT_GLOBAL_TO_TILE(ob->y); + WorldScrollScreen(ob); + UpdateScore(scoreobj); + RF_Refresh(); + RF_Refresh(); + + ob->y -= TILEGLOBAL; + RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority); + + // + // open the elevator door + // + SD_PlaySound(SND_ELEVATORDOOR); + for (i=0; i<=5; i++) + { + for (y=0; y<2; y++) + { + for (x=0; x<2; x++) + { + tiles[y][x] = *(mapsegs[1]+mapbwidthtable[y]/2 + i*2 + x); + } + } + RF_MemToMap(&tiles[0][0], 1, tx, ty-2, 2, 2); + RF_Refresh(); + VW_WaitVBL(8); + } + + // + // make Keen walk out of the elevator + // + for (y=0; y<32; y++) + { + ob->y += 8; // move half a pixel every frame for 32 frames -> move down 16 pixels total + ob->shapenum = (y / 4) % 3 + WORLDKEEND1SPR; + RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority); + RF_Refresh(); + } + ob->needtoclip = cl_midclip; // redundant, but doesn't do any harm +} + +/* +====================== += += Elevator += +====================== +*/ + +void Elevator(Uint16 tileX, Uint16 tileY, Sint16 dir) +{ + Uint16 info, globalx, globaly, duration, move; + Sint16 x, y, i; + Uint16 tiles[2][2]; + objtype *ob = player; + + globalx = CONVERT_TILE_TO_GLOBAL(tileX); + globaly = CONVERT_TILE_TO_GLOBAL(tileY); + + // + // make Keen walk into the elevator + // + for (duration = 0; duration < 130; ) + { + CalcBounds(ob); + WorldScrollScreen(ob); + UpdateScore(scoreobj); + RF_Refresh(); + + move = tics * 2; + duration += tics; + + if (ob->x == globalx && ob->y == globaly) + break; + + if (ob->y < globaly) + { + ob->y += move; + if (ob->y > globaly) + ob->y = globaly; + } + else if (ob->y > globaly) + { + ob->y -= move; + if (ob->y < globaly) + ob->y = globaly; + } + + if (ob->x < globalx) + { + ob->x += move; + if (ob->x > globalx) + ob->x = globalx; + } + else if (ob->x > globalx) + { + ob->x -= move; + if (ob->x < globalx) + ob->x = globalx; + } + + ob->shapenum = ((duration / 8) % 3) + WORLDKEENU1SPR; + RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority); + } + + // + // close the elevator door + // + SD_PlaySound(SND_ELEVATORDOOR); + for (i=5; i >= 0; i--) + { + for (y=0; y<2; y++) + { + for (x=0; x<2; x++) + { + tiles[y][x] = *(mapsegs[1]+mapbwidthtable[y]/2 + i*2 + x); + } + } + RF_MemToMap(&tiles[0][0], 1, tileX+dir, tileY-1, 2, 2); + RF_Refresh(); + VW_WaitVBL(8); + } + + // + // make Keen invisible (and not clipping) and send him to the destination + // + RF_RemoveSprite(&ob->sprite); + info = *(mapsegs[2] + mapbwidthtable[tileY]/2 + tileX); + ob->temp2 = CONVERT_TILE_TO_GLOBAL(info >> 8); + ob->temp1 = CONVERT_TILE_TO_GLOBAL((info & 0x7F) + 1); // BUG? y coordinate is limited to 1..127 + if (ob->temp1 < ob->y) + { + ob->ydir = -1; + } + else + { + ob->ydir = 1; + } + if (ob->temp2 < ob->x) + { + ob->xdir = -1; + } + else + { + ob->xdir = 1; + } + ob->needtoclip = cl_noclip; + ob->state = &s_worldelevate; +} + +#endif //ifdef KEEN5 + +#endif //ifdef KEEN4 ... else ... + +/* +====================== += += CheckWorldInTiles += +====================== +*/ + +void CheckWorldInTiles(objtype *ob) +{ + Uint16 tx, ty, intile; + + if (ob->temp3) + return; + + tx = ob->tilemidx; + ty = CONVERT_GLOBAL_TO_TILE(ob->top + (ob->bottom-ob->top)/2); + intile = tinf[INTILE + *(mapsegs[1]+mapbwidthtable[ty]/2+tx)]; +#if defined KEEN4 + if (intile == INTILE_SHORESOUTH || intile == INTILE_SHORENORTH + || intile == INTILE_SHOREEAST || intile == INTILE_SHOREWEST) + { + if (!gamestate.wetsuit) + { + SD_PlaySound(SND_NOWAY); + CantSwim(); + RF_ForceRefresh(); + xtry = -ob->xmove; + ytry = -ob->ymove; + ob->xdir = ob->ydir = 0; + ClipToWalls(ob); + } + else + { + ob->temp1 = tiledir[intile-INTILE_SHORESOUTH]; + if (ob->state == &s_worldswim) + { + ob->temp1 = opposite[ob->temp1]; + } + switch (ob->temp1) + { + case dir_North: + ob->xdir = 0; + ob->ydir = -1; + break; + case dir_East: + ob->xdir = 1; + ob->ydir = 0; + break; + case dir_South: + ob->xdir = 0; + ob->ydir = 1; + break; + case dir_West: + ob->xdir = -1; + ob->ydir = 0; + break; + } + ob->temp2 = 0; + ob->temp3 = 18; + if (ob->state == &s_worldswim) + { + ChangeState(ob, &s_worldkeenwalk); + } + else + { + ChangeState(ob, &s_worldswim); + } + } + } +#elif defined KEEN5 + switch (intile) + { + case INTILE_TELEPORT: + Teleport(tx, ty); + break; + case INTILE_ELEVATORLEFT: + Elevator(tx, ty, 0); + break; + case INTILE_ELEVATORRIGHT: + Elevator(tx, ty, -1); + break; + } +#elif defined KEEN6 + switch (intile) + { + case INTILE_TELEPORT: + Teleport(tx, ty); + break; + } +#endif +} + +/* +============================================================================= + + FLAGS + +temp1 = x destination for the thrown flag +temp2 = y destination for the thrown flag +temp3 = amount of time passed since flag was thrown (in tics) + +============================================================================= +*/ + +statetype s_flagwave1 = {FLAGFLAP1SPR, FLAGFLAP1SPR, step, false, false, 10, 0, 0, NULL, NULL, R_Draw, &s_flagwave2}; +statetype s_flagwave2 = {FLAGFLAP2SPR, FLAGFLAP2SPR, step, false, false, 10, 0, 0, NULL, NULL, R_Draw, &s_flagwave3}; +statetype s_flagwave3 = {FLAGFLAP3SPR, FLAGFLAP3SPR, step, false, false, 10, 0, 0, NULL, NULL, R_Draw, &s_flagwave4}; +statetype s_flagwave4 = {FLAGFLAP4SPR, FLAGFLAP4SPR, step, false, false, 10, 0, 0, NULL, NULL, R_Draw, &s_flagwave1}; + +#ifndef KEEN5 +statetype s_throwflag0 = {FLAGFLIP1SPR, FLAGFLIP1SPR, think, false, false, 6, 0, 0, TossThink, NULL, R_Draw, &s_throwflag1}; +statetype s_throwflag1 = {FLAGFLIP1SPR, FLAGFLIP1SPR, stepthink, false, false, 12, 0, 0, PathThink, NULL, R_Draw, &s_throwflag2}; +statetype s_throwflag2 = {FLAGFLIP2SPR, FLAGFLIP2SPR, stepthink, false, false, 12, 0, 0, PathThink, NULL, R_Draw, &s_throwflag3}; +statetype s_throwflag3 = {FLAGFLIP3SPR, FLAGFLIP3SPR, stepthink, false, false, 12, 0, 0, PathThink, NULL, R_Draw, &s_throwflag4}; +statetype s_throwflag4 = {FLAGFLIP4SPR, FLAGFLIP4SPR, stepthink, false, false, 12, 0, 0, PathThink, NULL, R_Draw, &s_throwflag5}; +statetype s_throwflag5 = {FLAGFALL1SPR, FLAGFALL1SPR, stepthink, false, false, 12, 0, 0, PathThink, NULL, R_Draw, &s_throwflag6}; +statetype s_throwflag6 = {FLAGFALL1SPR, FLAGFALL1SPR, stepthink, true, false, 1, 0, 0, FlagAlign, NULL, R_Draw, &s_flagwave1}; +#endif + +Sint16 flagx, flagy; +Point flagpath[30]; + +/* +====================== += += SpawnFlag += +====================== +*/ + +void SpawnFlag(Sint16 x, Sint16 y) +{ + GetNewObj(false); + new->needtoclip = cl_noclip; + new->priority = 3; + new->obclass = flagobj; + new->active = ac_yes; +#if defined KEEN4 + new->x = CONVERT_TILE_TO_GLOBAL(x) + 6*PIXGLOBAL; + new->y = CONVERT_TILE_TO_GLOBAL(y) + -30*PIXGLOBAL; +#elif defined KEEN5 + new->x = CONVERT_TILE_TO_GLOBAL(x) + -5*PIXGLOBAL; + new->y = CONVERT_TILE_TO_GLOBAL(y) + -30*PIXGLOBAL; +#elif defined KEEN6 + new->x = CONVERT_TILE_TO_GLOBAL(x) + 6*PIXGLOBAL; // useless! + new->y = CONVERT_TILE_TO_GLOBAL(y) + -30*PIXGLOBAL; // useless! +#if GRMODE == CGAGR + new->x = CONVERT_TILE_TO_GLOBAL(x) + 10*PIXGLOBAL; +#else + new->x = CONVERT_TILE_TO_GLOBAL(x) + 14*PIXGLOBAL; +#endif + new->y = CONVERT_TILE_TO_GLOBAL(y) + -26*PIXGLOBAL; + { + Uint16 tile = *(mapsegs[1]+mapbwidthtable[y]/2 + x) + 1; + RF_MemToMap(&tile, 1, x, y, 1, 1); + } +#endif + new->ticcount = US_RndT() / 16; + NewState(new, &s_flagwave1); +} + +#ifndef KEEN5 +/* +====================== += += SpawnThrowFlag += +====================== +*/ + +void SpawnThrowFlag(Sint16 x, Sint16 y) +{ + Sint16 i; + Sint32 xdist, ydist; + + GetNewObj(false); + new->needtoclip = cl_noclip; + new->priority = 3; + new->obclass = flagobj; + new->active = ac_allways; + new->x = gamestate.worldx - 16*PIXGLOBAL; + new->y = gamestate.worldy - 16*PIXGLOBAL; +#if defined KEEN4 + new->temp1 = CONVERT_TILE_TO_GLOBAL(x) + 6*PIXGLOBAL; + new->temp2 = CONVERT_TILE_TO_GLOBAL(y) + -38*PIXGLOBAL; +#elif defined KEEN6 + flagx = x; + flagy = y; +#if GRMODE == CGAGR + new->temp1 = CONVERT_TILE_TO_GLOBAL(x) + 10*PIXGLOBAL; +#else + new->temp1 = CONVERT_TILE_TO_GLOBAL(x) + 14*PIXGLOBAL; +#endif + new->temp2 = CONVERT_TILE_TO_GLOBAL(y) + -34*PIXGLOBAL; +#endif + xdist = (Sint32)new->temp1 - (Sint32)new->x; + ydist = (Sint32)new->temp2 - (Sint32)new->y; + for (i = 0; i < 30; i++) + { + flagpath[i].x = new->x + (xdist * min(i, 24))/24; + flagpath[i].y = new->y + (ydist * i)/30; + if (i < 10) + { + flagpath[i].y -= i*3*PIXGLOBAL; + } + else if (i < 15) + { + flagpath[i].y -= i*PIXGLOBAL + 20*PIXGLOBAL; + } + else if (i < 20) + { + flagpath[i].y -= (20-i)*PIXGLOBAL + 30*PIXGLOBAL; + } + else + { + flagpath[i].y -= (29-i)*3*PIXGLOBAL; + } + } + NewState(new, &s_throwflag0); +} + +/* +====================== += += TossThink += +====================== +*/ + +void TossThink(objtype *ob) +{ + if (screenfaded) + return; + + SD_StopSound(); + SD_PlaySound(SND_FLAGSPIN); + ob->state = ob->state->nextstate; +} + +/* +====================== += += PathThink += +====================== +*/ + +void PathThink(objtype *ob) +{ + ob->temp3 = ob->temp3 + tics; + if (ob->temp3 > 58) + ob->temp3 = 58; + + ob->x = flagpath[ob->temp3/2].x; + ob->y = flagpath[ob->temp3/2].y; + ob->needtoreact = true; + if (ob->temp1 == 0) + { + SD_PlaySound(SND_FLAGSPIN); + } +} + +/* +====================== += += FlagAlign += +====================== +*/ + +void FlagAlign(objtype *ob) +{ + ob->x = ob->temp1; + ob->y = ob->temp2 + 8*PIXGLOBAL; + SD_PlaySound(SND_FLAGLAND); +#ifdef KEEN6 + { + Uint16 tile = *(mapsegs[1]+mapbwidthtable[flagy]/2 + flagx) + 1; + RF_MemToMap(&tile, 1, flagx, flagy, 1, 1); + } +#endif +} +#endif + +/* +============================================================================= + + NEURAL STUNNER + +============================================================================= +*/ +statetype s_stunray1 = {STUN1SPR, STUN1SPR, slide, false, false, 6, 64, 64, T_Shot, NULL, R_Shot, &s_stunray2}; +statetype s_stunray2 = {STUN2SPR, STUN2SPR, slide, false, false, 6, 64, 64, T_Shot, NULL, R_Shot, &s_stunray3}; +statetype s_stunray3 = {STUN3SPR, STUN3SPR, slide, false, false, 6, 64, 64, T_Shot, NULL, R_Shot, &s_stunray4}; +statetype s_stunray4 = {STUN4SPR, STUN4SPR, slide, false, false, 6, 64, 64, T_Shot, NULL, R_Shot, &s_stunray1}; + +statetype s_stunhit = {STUNHIT1SPR, STUNHIT1SPR, step, false, false, 12, 0, 0, NULL, NULL, R_Draw, &s_stunhit2}; +statetype s_stunhit2 = {STUNHIT2SPR, STUNHIT2SPR, step, false, false, 12, 0, 0, NULL, NULL, R_Draw, NULL}; + +/* +====================== += += SpawnShot += +====================== +*/ + +void SpawnShot(Uint16 x, Uint16 y, Direction dir) +{ + if (!gamestate.ammo) + { + SD_PlaySound(SND_USESWITCH); + return; + } + + gamestate.ammo--; + GetNewObj(true); + new->x = x; + new->y = y; + new->priority = 0; + new->obclass = stunshotobj; + new->active = ac_allways; + SD_PlaySound(SND_KEENFIRE); + switch (dir) + { + case dir_North: + new->xdir = 0; + new->ydir = -1; + break; + case dir_East: + new->xdir = 1; + new->ydir = 0; + break; + case dir_South: + new->xdir = 0; + new->ydir = 1; + break; + case dir_West: + new->xdir = -1; + new->ydir = 0; + break; + default: + Quit("SpawnShot: Bad dir!"); + break; + } + NewState(new, &s_stunray1); + +#ifdef KEEN6 + { + objtype *ob; + + for (ob=player->next; ob; ob=ob->next) + { + if (ob->active + && new->right > ob->left && new->left < ob->right + && new->top < ob->bottom && new->bottom > ob->top + && ob->state->contact) + { + ob->state->contact(ob, new); + return; + } + } + } +#endif +} + +/* +====================== += += ExplodeShot += +====================== +*/ + +void ExplodeShot(objtype *ob) +{ + ob->obclass = inertobj; + ChangeState(ob, &s_stunhit); + SD_PlaySound(SND_SHOTEXPLODE); +} + +/* +====================== += += T_Shot += +====================== +*/ + +void T_Shot(objtype *ob) +{ + objtype *ob2; + + if (ob->tileright >= originxtile && ob->tilebottom >= originytile + && ob->tileleft <= originxtilemax && ob->tiletop <= originytilemax) + { + //object is visible, so do nothing + return; + } + + if (ob->tileright+10 < originxtile + || ob->tileleft-10 > originxtilemax + || ob->tilebottom+6 < originytile + || ob->tiletop-6 > originytilemax) + { + //shot is off-screen by more than half a screen, so remove it + RemoveObj(ob); + return; + } + + //check for collisions with INACTIVE objects + for (ob2 = player->next; ob2; ob2 = ob2->next) + { + if (!ob2->active && ob->right > ob2->left && ob->left < ob2->right + && ob->top < ob2->bottom && ob->bottom > ob2->top) + { + if (ob2->state->contact) + { + ob2->state->contact(ob2, ob); + ob2->needtoreact = true; + ob2->active = ac_yes; + } + + if (ob->obclass == nothing) //BUG: obclass is 'inertobj' for the exploded shot + break; + } + } +} + +/* +====================== += += R_Shot += +====================== +*/ + +void R_Shot(objtype *ob) +{ + Uint16 tile; + + if (ob->hitnorth == 1 && ob->tileleft != ob->tileright) + { + tile = *(mapsegs[1]+mapbwidthtable[ob->tiletop-1]/2+ob->tileright); + if (tinf[NORTHWALL+tile] == 17) + { + ob->hitnorth = 17; + ob->x += 0x100 - (ob->x & 0xFF); + } + } + else if (ob->hitnorth == 17 && ob->tileleft != ob->tileright) + { + ob->x &= 0xFF00; + } + if (ob->hitsouth == 1 && ob->tileleft != ob->tileright) + { + tile = *(mapsegs[1]+mapbwidthtable[ob->tilebottom+1]/2+ob->tileright); + if (tinf[SOUTHWALL+tile] == 17) + { + ob->hitsouth = 17; + ob->x += 0x100 - (ob->x & 0xFF); + } + } + else if (ob->hitsouth == 17 && ob->tileleft != ob->tileright) + { + ob->x &= 0xFF00; + } + if (ob->hitnorth == 17 || ob->hitsouth == 17) + { + ytry = ob->state->ymove * tics * ob->ydir; + ob->y += ytry; + ob->top += ytry; + ob->bottom += ytry; + ob->tiletop = CONVERT_GLOBAL_TO_TILE(ob->top); + ob->tilebottom = CONVERT_GLOBAL_TO_TILE(ob->bottom); + } + else if (ob->hitnorth || ob->hitsouth || ob->hiteast || ob->hitwest) + { + ExplodeShot(ob); + } + RF_PlaceSprite(&ob->sprite, ob->x, ob->y, ob->shapenum, spritedraw, ob->priority); +} + +/* +============================================================================= + + DOOR + +temp1 = height of the door's main section (identical tiles!), in tiles + DoorOpen changes two more tiles at the bottom end of the door + (total door height in tiles is temp1 + 2) + +============================================================================= +*/ + +statetype s_door1 = {0, 0, step, false, false, 10, 0, 0, DoorOpen, NULL, NULL, &s_door2}; +statetype s_door2 = {0, 0, step, false, false, 10, 0, 0, DoorOpen, NULL, NULL, &s_door3}; +statetype s_door3 = {0, 0, step, false, false, 10, 0, 0, DoorOpen, NULL, NULL, NULL}; + +/* +====================== += += DoorOpen += +====================== +*/ + +void DoorOpen(objtype *ob) +{ + Sint16 i; + Uint16 far *map; + Uint16 tiles[50]; + + map = mapsegs[1] + mapbwidthtable[ob->y]/2 + ob->x; + for (i=0; i < ob->temp1+2; i++, map+=mapwidth) + { + tiles[i] = *map + 1; + } + RF_MemToMap(tiles, 1, ob->x, ob->y, 1, ob->temp1+2); +} + +#ifdef KEEN5 +/* +============================================================================= + + CARD DOOR + +temp1 = frame counter + +============================================================================= +*/ +statetype s_carddoor = {0, 0, step, false, false, 15, 0, 0, CardDoorOpen, NULL, NULL, &s_carddoor}; + +/* +====================== += += CardDoorOpen += +====================== +*/ + +void CardDoorOpen(objtype *ob) +{ + Sint16 x, y; + Uint16 far *map; + Uint16 tiles[16], *tileptr; + + tileptr = tiles; + map = mapsegs[1] + mapbwidthtable[ob->y]/2 + ob->x; + for (y=0; y<4; y++, map+=mapwidth) + { + for (x=0; x<4; x++) + { + *tileptr++ = map[x]-4; + } + } + RF_MemToMap(tiles, 1, ob->x, ob->y, 4, 4); + + if (++ob->temp1 == 3) + ob->state = NULL; +} + +#endif \ No newline at end of file