/* Reconstructed Commander Keen 4-6 Source Code * Copyright (C) 2021 K1n9_Duk3 * * This file is primarily based on: * Catacomb 3-D Source Code * Copyright (C) 1993-2014 Flat Rock Software * * 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. */ // ID_RF.C /* ============================================================================= notes ----- scrolling more than one tile / refresh forces a total redraw two overlapping sprites of equal priority can change drawing order when updated ============================================================================= */ #include "ID_HEADS.H" #pragma hdrstop /* ============================================================================= LOCAL CONSTANTS ============================================================================= */ #define SCREENTILESWIDE 20 #define SCREENTILESHIGH 13 #define SCREENSPACE (SCREENWIDTH*240) #define FREEEGAMEM (0x10000l-3l*SCREENSPACE) // // the update array must have enough space for two screens that can float // up two two tiles each way // // (PORTTILESWIDE+1)*PORTTILESHIGH must be even so the arrays can be cleared // by word width instructions #define UPDATESCREENSIZE (UPDATEWIDE*PORTTILESHIGH+2) #define UPDATESPARESIZE (UPDATEWIDE*2+4) #define UPDATESIZE (UPDATESCREENSIZE+2*UPDATESPARESIZE) #define G_EGASX_SHIFT 7 // global >> ?? = screen x #define G_CGASX_SHIFT 6 // global >> ?? = screen x #define G_SY_SHIFT 4 // global >> ?? = screen y unsigned SX_T_SHIFT; // screen x >> ?? = tile EGA = 1, CGA = 2; #define SY_T_SHIFT 4 // screen y >> ?? = tile #define EGAPORTSCREENWIDE 42 #define CGAPORTSCREENWIDE 84 #define PORTSCREENHIGH 224 #define UPDATESCREENSIZE (UPDATEWIDE*PORTTILESHIGH+2) #define UPDATESPARESIZE (UPDATEWIDE*2+4) #define UPDATESIZE (UPDATESCREENSIZE+2*UPDATESPARESIZE) #define MAXSCROLLEDGES 6 /* ============================================================================= LOCAL TYPES ============================================================================= */ typedef struct spriteliststruct { int screenx,screeny; int width,height; unsigned grseg,sourceofs,planesize; drawtype draw; unsigned tilex,tiley,tilewide,tilehigh; int priority,updatecount; struct spriteliststruct **prevptr,*nextsprite; } spritelisttype; typedef struct { int screenx,screeny; int width,height; } eraseblocktype; typedef struct { unsigned current; // foreground tiles have high bit set int count; #ifdef KEEN6 unsigned soundtile; unsigned visible; int sound; #endif } tiletype; typedef struct animtilestruct { unsigned x,y,tile; tiletype *chain; unsigned far *mapplane; struct animtilestruct **prevptr,*nexttile; } animtiletype; /* ============================================================================= GLOBAL VARIABLES ============================================================================= */ unsigned tics; long lasttimecount; boolean compatability; // crippled refresh for wierdo SVGAs unsigned mapwidth,mapheight,mapbyteswide,mapwordswide ,mapbytesextra,mapwordsextra; unsigned mapbwidthtable[MAXMAPHEIGHT]; // // Global : Actor coordinates are in this, at 1/16 th of a pixel, to allow // for fractional movement and acceleration. // // Tiles : Tile offsets from the upper left corner of the current map. // // Screen : Graphics level offsets from map origin, x in bytes, y in pixels. // originxscreen is the same spot as originxtile, just with extra precision // so graphics don't need to be done in tile boundaries. // unsigned originxglobal,originyglobal; unsigned originxtile,originytile; unsigned originxscreen,originyscreen; unsigned originmap; unsigned originxmin,originxmax,originymin,originymax; unsigned masterofs; // // Table of the offsets from bufferofs of each tile spot in the // view port. The extra wide tile should never be drawn, but the space // is needed to account for the extra 0 in the update arrays. Built by // RF_Startup // unsigned blockstarts[UPDATEWIDE*UPDATEHIGH]; unsigned updatemapofs[UPDATEWIDE*UPDATEHIGH]; unsigned uwidthtable[PORTTILESHIGH]; // lookup instead of multiply byte update[2][UPDATESIZE]; byte *updateptr,*baseupdateptr, // current start of update window *updatestart[2], *baseupdatestart[2]; /* ============================================================================= LOCAL VARIABLES ============================================================================= */ static char scratch[20],str[80]; tiletype allanims[MAXANIMTYPES]; unsigned numanimchains; void (*refreshvector) (void); unsigned screenstart[3] = {0,SCREENSPACE,SCREENSPACE*2}; unsigned xpanmask; // prevent panning to odd pixels unsigned screenpage; // screen currently being displayed unsigned otherpage; spritelisttype spritearray[MAXSPRITES],*prioritystart[PRIORITIES], *spritefreeptr; animtiletype animarray[MAXANIMTILES],*animhead,*animfreeptr; int animfreespot; eraseblocktype eraselist[2][MAXSPRITES],*eraselistptr[2]; int hscrollblocks,vscrollblocks; int hscrolledge[MAXSCROLLEDGES],vscrolledge[MAXSCROLLEDGES]; /* ============================================================================= LOCAL PROTOTYPES ============================================================================= */ void RFL_NewTile (unsigned updateoffset); void RFL_MaskForegroundTiles (void); void RFL_UpdateTiles (void); void RFL_BoundScroll (int x, int y); void RFL_CalcOriginStuff (long x, long y); void RFL_ClearScrollBlocks (void); void RFL_InitSpriteList (void); void RFL_InitAnimList (void); void RFL_CheckForAnimTile (unsigned x, unsigned y); void RFL_AnimateTiles (void); void RFL_RemoveAnimsOnX (unsigned x); void RFL_RemoveAnimsOnY (unsigned y); void RFL_EraseBlocks (void); void RFL_UpdateSprites (void); /* ============================================================================= GRMODE INDEPENDANT ROUTINES ============================================================================= */ /* ===================== = = RF_Startup = ===================== */ static char *ParmStrings[] = {"comp",""}; void RF_Startup (void) { int i,x,y; unsigned *blockstart; #ifndef KEEN // // Keen 4-6 store the compatability setting in the game's config file. // The setting is loaded from that file AFTER RF_Startup is executed, // making this check useless (unless the config file doesn't exist). // Instead, US_Startup now checks for that parameter after the config // file has been read. // if (grmode == EGAGR) for (i = 1;i < _argc;i++) if (US_CheckParm(_argv[i],ParmStrings) == 0) { compatability = true; break; } #endif for (i=0;i = ===================== */ void RF_NewMap (void) { int i,x,y; unsigned spot,*table; mapwidth = mapheaderseg[mapon]->width; mapbyteswide = 2*mapwidth; mapheight = mapheaderseg[mapon]->height; mapwordsextra = mapwidth-PORTTILESWIDE; mapbytesextra = 2*mapwordsextra; // // make a lookup table for the maps left edge // if (mapheight > MAXMAPHEIGHT) Quit ("RF_NewMap: Map too tall!"); spot = 0; for (i=0;isoundtile = tile; anim->sound = soundtiles.sounds[i]; break; } } } #endif //=========================================================================== /* ========================== = = RF_MarkTileGraphics = = Goes through mapplane[0/1] and marks all background/foreground tiles = needed, then follows all animation sequences to make sure animated = tiles get all the stages. Every unique animating tile is given an = entry in allanims[], so every instance of that tile will animate at the = same rate. The info plane for each animating tile will hold a pointer = into allanims[], therefore you can't have both an animating foreground = and background tile in the same spot! = ========================== */ void RF_MarkTileGraphics (void) { unsigned size; int tile,next,anims,change; unsigned far *start,far *end,far *info; unsigned i,tilehigh; char str[80],str2[10]; memset (allanims,0,sizeof(allanims)); numanimchains = 0; size = mapwidth*mapheight; // // background plane // start = mapsegs[0]; info = mapsegs[2]; end = start+size; do { tile = *start++; if (tile>=0) // <0 is a tile that is never drawn { CA_MarkGrChunk(STARTTILE16+tile); if (tinf[ANIM+tile]) { // this tile will animated if (tinf[SPEED+tile]) { if (!tinf[ANIM+tile]) { strcpy (str,"RF_MarkTileGraphics: Background anim of 0:"); itoa (tile,str2,10); strcat (str,str2); Quit (str); } for (i=0;i=MAXANIMTYPES) Quit ("RF_MarkTileGraphics: Too many unique animated tiles!"); allanims[i].current = tile; allanims[i].count = tinf[SPEED+tile]; #ifdef KEEN6 allanims[i].visible = 0; allanims[i].sound = -1; #endif *info = (unsigned)&allanims[i]; numanimchains++; } #ifdef KEEN6 RFL_CheckTileSound(&allanims[i], tile); #endif anims = 0; change = (signed char)(tinf[ANIM+tile]); next = tile+change; while (change && next != tile) { #ifdef KEEN6 RFL_CheckTileSound(&allanims[i], next); #endif CA_MarkGrChunk(STARTTILE16+next); change = (signed char)(tinf[ANIM+next]); next += change; if (++anims > 20) { strcpy (str,"RF_MarkTileGraphics: Unending background animation:"); itoa (next,str2,10); strcat (str,str2); Quit (str); } } } } nextback: info++; } while (start=0) // <0 is a tile that is never drawn { CA_MarkGrChunk(STARTTILE16M+tile); if (tinf[MANIM+tile]) { // this tile will animated if (tinf[MSPEED+tile]) { if (!tinf[MANIM+tile]) { strcpy (str,"RF_MarkTileGraphics: Foreground anim of 0:"); itoa (tile,str2,10); strcat (str,str2); Quit (str); } tilehigh = tile | 0x8000; // foreground tiles have high bit for (i=0;i=MAXANIMTYPES) Quit ("RF_MarkTileGraphics: Too many unique animated tiles!"); allanims[i].current = tilehigh; allanims[i].count = tinf[MSPEED+tile]; #ifdef KEEN6 allanims[i].visible = 0; allanims[i].sound = -1; #endif *info = (unsigned)&allanims[i]; numanimchains++; } #ifdef KEEN6 RFL_CheckTileSound(&allanims[i], tilehigh); #endif anims = 0; change = (signed char)(tinf[MANIM+tile]); next = tile+change; while (change && next != tile) { #ifdef KEEN6 RFL_CheckTileSound(&allanims[i], next | 0x8000); // foreground tiles have high bit #endif CA_MarkGrChunk(STARTTILE16M+next); change = (signed char)(tinf[MANIM+next]); next += change; if (++anims > 20) { strcpy (str,"RF_MarkTileGraphics: Unending foreground animation:"); itoa (next,str2,10); strcat (str,str2); Quit (str); } } } } nextfront: info++; } while (startcurrent != 0; anim++) anim->visible = 0; } #endif } /* ==================== = = RFL_CheckForAnimTile = ==================== */ void RFL_CheckForAnimTile (unsigned x, unsigned y) { unsigned tile,offset,speed,lasttime,thistime,timemissed; unsigned far *map; animtiletype *anim,*next; // the info plane of each animating tile has a near pointer into allanims[] // which gives the current state of all concurrently animating tiles offset = mapbwidthtable[y]/2+x; // // background // map = mapsegs[0]+offset; tile = *map; if (tinf[ANIM+tile] && tinf[SPEED+tile]) { if (!animfreeptr) Quit ("RF_CheckForAnimTile: No free spots in tilearray!"); anim = animfreeptr; animfreeptr = animfreeptr->nexttile; next = animhead; // stick it at the start of the list animhead = anim; if (next) next->prevptr = &anim->nexttile; anim->nexttile = next; anim->prevptr = &animhead; anim->x = x; anim->y = y; anim->tile = tile; anim->mapplane = map; anim->chain = (tiletype *)*(mapsegs[2]+offset); #ifdef KEEN6 anim->chain->visible++; #endif } // // foreground // map = mapsegs[1]+offset; tile = *map; if (tinf[MANIM+tile] && tinf[MSPEED+tile]) { if (!animfreeptr) Quit ("RF_CheckForAnimTile: No free spots in tilearray!"); anim = animfreeptr; animfreeptr = animfreeptr->nexttile; next = animhead; // stick it at the start of the list animhead = anim; if (next) next->prevptr = &anim->nexttile; anim->nexttile = next; anim->prevptr = &animhead; anim->x = x; anim->y = y; anim->tile = tile; anim->mapplane = map; anim->chain = (tiletype *)*(mapsegs[2]+offset); #ifdef KEEN6 anim->chain->visible++; #endif } } /* ==================== = = RFL_RemoveAnimsOnX = ==================== */ void RFL_RemoveAnimsOnX (unsigned x) { animtiletype *current,*next; current = animhead; while (current) { if (current->x == x) { #ifdef KEEN6 current->chain->visible--; #endif *(void **)current->prevptr = current->nexttile; if (current->nexttile) current->nexttile->prevptr = current->prevptr; next = current->nexttile; current->nexttile = animfreeptr; animfreeptr = current; current = next; } else current = current->nexttile; } } /* ==================== = = RFL_RemoveAnimsOnY = ==================== */ void RFL_RemoveAnimsOnY (unsigned y) { animtiletype *current,*next; current = animhead; while (current) { if (current->y == y) { #ifdef KEEN6 current->chain->visible--; #endif *(void **)current->prevptr = current->nexttile; if (current->nexttile) current->nexttile->prevptr = current->prevptr; next = current->nexttile; current->nexttile = animfreeptr; animfreeptr = current; current = next; } else current = current->nexttile; } } /* ==================== = = RFL_RemoveAnimsInBlock = ==================== */ void RFL_RemoveAnimsInBlock (unsigned x, unsigned y, unsigned width, unsigned height) { animtiletype *current,*next; current = animhead; while (current) { if (current->x - x < width && current->y - y < height) { #ifdef KEEN6 current->chain->visible--; #endif *(void **)current->prevptr = current->nexttile; if (current->nexttile) current->nexttile->prevptr = current->prevptr; next = current->nexttile; current->nexttile = animfreeptr; animfreeptr = current; current = next; } else current = current->nexttile; } } /* ==================== = = RFL_AnimateTiles = ==================== */ void RFL_AnimateTiles (void) { animtiletype *current; unsigned updateofs,tile,x,y; tiletype *anim; // // animate the lists of tiles // anim = &allanims[0]; while (anim->current) { anim->count-=tics; while ( anim->count < 1) { if (anim->current & 0x8000) { tile = anim->current & 0x7fff; tile += (signed char)tinf[MANIM+tile]; anim->count += tinf[MSPEED+tile]; tile |= 0x8000; } else { tile = anim->current; tile += (signed char)tinf[ANIM+tile]; anim->count += tinf[SPEED+tile]; } anim->current = tile; #ifdef KEEN6 if (anim->visible && anim->current == anim->soundtile && anim->sound != -1) { SD_PlaySound(anim->sound); } #endif } anim++; } // // traverse the list of animating tiles // current = animhead; while (current) { tile =current->chain->current; if ( tile != current->tile) { // tile has animated // // remove tile from master screen cache, // change a tile to its next state, set the structure up for // next animation, and post an update region to both update pages // current->tile = tile; *(current->mapplane) = tile & 0x7fff; // change in map x = current->x-originxtile; y = current->y-originytile; if (x>=PORTTILESWIDE || y>=PORTTILESHIGH) Quit ("RFL_AnimateTiles: Out of bounds!"); updateofs = uwidthtable[y] + x; RFL_NewTile(updateofs); // puts "1"s in both pages } current = current->nexttile; } } //=========================================================================== /* ========================= = = RFL_InitSpriteList = = Call to clear out the entire sprite list and return all of them to = the free list. = ========================= */ void RFL_InitSpriteList (void) { int i; spritefreeptr = &spritearray[0]; for (i=0;i>G_T_SHIFT; originytile = originyglobal>>G_T_SHIFT; originxscreen = originxtile<>G_P_SHIFT) & 15; pansx = panx & 8; pany = pansy = (originyglobal>>G_P_SHIFT) & 15; panadjust = panx/8 + ylookup[pany]; #endif #if GRMODE == CGAGR panx = (originxglobal>>G_P_SHIFT) & 15; pansx = panx & 12; pany = pansy = (originyglobal>>G_P_SHIFT) & 15; panadjust = pansx/4 + ylookup[pansy]; #endif } /* ================= = = RFL_ClearScrollBlocks = ================= */ void RFL_ClearScrollBlocks (void) { hscrollblocks = vscrollblocks = 0; } /* ================= = = RF_SetScrollBlock = = Sets a horizontal or vertical scroll block = a horizontal block is ----, meaning it blocks up/down movement = ================= */ void RF_SetScrollBlock (int x, int y, boolean horizontal) { if (horizontal) { hscrolledge[hscrollblocks] = y; if (hscrollblocks++ == MAXSCROLLEDGES) Quit ("RF_SetScrollBlock: Too many horizontal scroll blocks"); } else { vscrolledge[vscrollblocks] = x; if (vscrollblocks++ == MAXSCROLLEDGES) Quit ("RF_SetScrollBlock: Too many vertical scroll blocks"); } } /* ================= = = RFL_BoundScroll = = Bound a given x/y movement to scroll blocks = ================= */ void RFL_BoundScroll (int x, int y) { int check,newxtile,newytile; originxglobal += x; originyglobal += y; newxtile= originxglobal >> G_T_SHIFT; newytile = originyglobal >> G_T_SHIFT; if (x>0) { newxtile+=SCREENTILESWIDE; for (check=0;check0) { newytile+=SCREENTILESHIGH; for (check=0;checkoriginxmax) orgx=originxmax; if (orgyoriginymax) orgy=originymax; originxtile = orgx>>G_T_SHIFT; originytile = orgy>>G_T_SHIFT; for (check=0;check=originxtile && edge <=originxtile+10) { orgx = (edge+1)*TILEGLOBAL; break; } if (edge>=originxtile+11 && edge <=originxtile+20) { orgx = (edge-20)*TILEGLOBAL; break; } } for (check=0;check=originytile && edge <=originytile+6) { orgy = (edge+1)*TILEGLOBAL; break; } if (edge>=originytile+7 && edge <=originytile+13) { orgy = (edge-13)*TILEGLOBAL; break; } } RFL_CalcOriginStuff (orgx,orgy); } //=========================================================================== /* ===================== = = RF_ClearBlock = = Posts erase blocks to clear a certain area of the screen to the master = screen, to erase text or something draw directly to the screen = = Parameters in pixels, but erasure is byte bounded = ===================== */ void RF_ClearBlock (int x, int y, int width, int height) { eraseblocktype block; #if GRMODE == EGAGR block.screenx = x/8+originxscreen; block.screeny = y+originyscreen; block.width = (width+(x&7)+7)/8; block.height = height; memcpy (eraselistptr[0]++,&block,sizeof(block)); memcpy (eraselistptr[1]++,&block,sizeof(block)); #endif #if GRMODE == CGAGR block.screenx = x/4+originxscreen; block.screeny = y+originyscreen; block.width = (width+(x&3)+3)/4; block.height = height; memcpy (eraselistptr[0]++,&block,sizeof(block)); #endif } //=========================================================================== /* ===================== = = RF_RedrawBlock = = Causes a number of tiles to be redrawn to the master screen and updated = = Parameters in pixels, but erasure is tile bounded = ===================== */ void RF_RedrawBlock (int x, int y, int width, int height) { int xx,yy,xl,xh,yl,yh; xl=(x+panx)/16; xh=(x+panx+width+15)/16; yl=(y+pany)/16; yh=(y+pany+height+15)/16; for (yy=yl;yy<=yh;yy++) for (xx=xl;xx<=xh;xx++) RFL_NewTile (yy*UPDATEWIDE+xx); } //=========================================================================== /* ===================== = = RF_CalcTics = ===================== */ void RF_CalcTics (void) { long newtime,oldtimecount; // // calculate tics since last refresh for adaptive timing // if (lasttimecount > TimeCount) TimeCount = lasttimecount; // if the game was paused a LONG time if (DemoMode) // demo recording and playback needs { // to be constant // // take DEMOTICS or more tics, and modify Timecount to reflect time taken // oldtimecount = lasttimecount; while (TimeCountMAXTICS) { TimeCount -= (tics-MAXTICS); tics = MAXTICS; } } } //=========================================================================== /* ===================== = = RF_FindFreeBuffer = = Finds the start of unused, non visable buffer space = ===================== */ unsigned RF_FindFreeBuffer (void) { unsigned spot,i,j; boolean ok; for (i=0;i<3;i++) { spot = screenstart[i]+SCREENSPACE; ok = true; for (j=0;j<3;j++) if (spot == screenstart[j]) { ok = false; break; } if (ok) return spot; } return 0; // never get here... } /* ============================================================================= EGA specific routines ============================================================================= */ #if GRMODE == EGAGR /* ===================== = = RF_NewPosition EGA = ===================== */ void RF_NewPosition (unsigned x, unsigned y) { int mx,my; byte *page0ptr,*page1ptr; unsigned updatenum; RFL_BoundNewOrigin (x,y); // // clear out all animating tiles // RFL_InitAnimList (); // // set up the new update arrays at base position // updatestart[0] = baseupdatestart[0]; updatestart[1] = baseupdatestart[1]; updateptr = updatestart[otherpage]; page0ptr = updatestart[0]+PORTTILESWIDE; // used to stick "0"s after rows page1ptr = updatestart[1]+PORTTILESWIDE; updatenum = 0; // start at first visable tile for (my=0;my1 || absdy>1) { // // scrolled more than one tile, so start from scratch // RF_NewPosition(originxglobal,originyglobal); return; } if (!absdx && !absdy) return; // the screen has not scrolled an entire tile // // adjust screens and handle SVGA crippled compatability mode // screenmove = deltay*16*SCREENWIDTH + deltax*TILEWIDTH; for (i=0;i<3;i++) { screenstart[i]+= screenmove; if (compatability && screenstart[i] > (0x10000l-SCREENSPACE) ) { // // move the screen to the opposite end of the buffer // screencopy = screenmove>0 ? FREEEGAMEM : -FREEEGAMEM; oldscreen = screenstart[i] - screenmove; newscreen = oldscreen + screencopy; screenstart[i] = newscreen + screenmove; VW_ScreenToScreen (oldscreen,newscreen, PORTTILESWIDE*2,PORTTILESHIGH*16); if (i==screenpage) VW_SetScreen(newscreen+oldpanadjust,oldpanx & xpanmask); } } bufferofs = screenstart[otherpage]; displayofs = screenstart[screenpage]; masterofs = screenstart[2]; // // float the update regions // move = deltax; if (deltay==1) move += UPDATEWIDE; else if (deltay==-1) move -= UPDATEWIDE; updatestart[0]+=move; updatestart[1]+=move; // // draw the new tiles just scrolled on to the master screen, and // mark them as needing to be copied to each screen next refreshes // Make sure a zero is at the end of each row in update // if (deltax) { if (deltax==1) { RFL_NewRow (1); // new right row RFL_RemoveAnimsOnX (originxtile-1); } else { RFL_NewRow (3); // new left row RFL_RemoveAnimsOnX (originxtile+PORTTILESWIDE); } update0 = updatestart[0]+PORTTILESWIDE; update1 = updatestart[1]+PORTTILESWIDE; for (yy=0;yyupdatecount<2) { if (!sprite->updatecount) memcpy (eraselistptr[otherpage]++,sprite,sizeof(eraseblocktype)); memcpy (eraselistptr[screenpage]++,sprite,sizeof(eraseblocktype)); } if (priority != sprite->priority) { // sprite mvoed to another priority, so unlink the old one and // relink it in the new priority next = sprite->nextsprite; // cut old links if (next) next->prevptr = sprite->prevptr; *sprite->prevptr = next; goto linknewspot; } } else { // this is a brand new sprite, so allocate a block from the array if (!spritefreeptr) Quit ("RF_PlaceSprite: No free spots in spritearray!"); sprite = spritefreeptr; spritefreeptr = spritefreeptr->nextsprite; linknewspot: next = prioritystart[priority]; // stick it in new spot if (next) next->prevptr = &sprite->nextsprite; sprite->nextsprite = next; prioritystart[priority] = sprite; sprite->prevptr = &prioritystart[priority]; } // // write the new info to the sprite // spr = &spritetable[spritenumber-STARTSPRITES]; block = (spritetype _seg *)grsegs[spritenumber]; if (!block) { strcpy (str,"RF_PlaceSprite: Placed an uncached sprite:"); itoa (spritenumber,str2,10); strcat (str,str2); Quit (str); } globaly+=spr->orgy; globalx+=spr->orgx; pixx = globalx >> G_SY_SHIFT; if (nopan) shift = 0; else shift = (pixx&7)/2; sprite->screenx = pixx >> (G_EGASX_SHIFT-G_SY_SHIFT); sprite->screeny = globaly >> G_SY_SHIFT; sprite->width = block->width[shift]; sprite->height = spr->height; sprite->grseg = spritenumber; sprite->sourceofs = block->sourceoffset[shift]; sprite->planesize = block->planesize[shift]; sprite->draw = draw; sprite->priority = priority; sprite->tilex = sprite->screenx >> SX_T_SHIFT; sprite->tiley = sprite->screeny >> SY_T_SHIFT; sprite->tilewide = ( (sprite->screenx + sprite->width -1) >> SX_T_SHIFT ) - sprite->tilex + 1; sprite->tilehigh = ( (sprite->screeny + sprite->height -1) >> SY_T_SHIFT ) - sprite->tiley + 1; sprite->updatecount = 2; // draw on next two refreshes // save the sprite pointer off in the user's pointer so it can be moved // again later *user = sprite; } //=========================================================================== /* ===================== = = RF_RemoveSprite EGA = ===================== */ void RF_RemoveSprite (void **user) { spritelisttype *sprite,*next; sprite = (spritelisttype *)*user; if (!sprite) return; // // post an erase block to both pages by copying screenx,screeny,width,height // both pages may not need to be erased if the sprite just changed last frame // if (sprite->updatecount<2) { if (!sprite->updatecount) memcpy (eraselistptr[otherpage]++,sprite,sizeof(eraseblocktype)); memcpy (eraselistptr[screenpage]++,sprite,sizeof(eraseblocktype)); } // // unlink the sprite node // next = sprite->nextsprite; if (next) // if (!next), sprite is last in chain next->prevptr = sprite->prevptr; *sprite->prevptr = next; // // add it back to the free list // sprite->nextsprite = spritefreeptr; spritefreeptr = sprite; // // null the users pointer, so next time that actor gets placed, it will // allocate a new block // *user = 0; } //=========================================================================== /* ==================== = = RFL_EraseBlocks EGA = = Write mode 1 should be set = ==================== */ void RFL_EraseBlocks (void) { eraseblocktype *block,*done; int screenxh,screenyh; unsigned pos,xtl,ytl,xth,yth,x,y; byte *updatespot; unsigned updatedelta; unsigned erasecount; #ifdef PROFILE erasecount = 0; #endif block = otherpage ? &eraselist[1][0] : &eraselist[0][0]; done = eraselistptr[otherpage]; while (block != done) { // // clip the block to the current screen view // block->screenx -= originxscreen; block->screeny -= originyscreen; if (block->screenx < 0) { block->width += block->screenx; if (block->width<1) goto next; block->screenx = 0; } if (block->screeny < 0) { block->height += block->screeny; if (block->height<1) goto next; block->screeny = 0; } screenxh = block->screenx + block->width; screenyh = block->screeny + block->height; if (screenxh > EGAPORTSCREENWIDE) { block->width = EGAPORTSCREENWIDE-block->screenx; screenxh = block->screenx + block->width; } if (screenyh > PORTSCREENHIGH) { block->height = PORTSCREENHIGH-block->screeny; screenyh = block->screeny + block->height; } if (block->width<1 || block->height<1) goto next; // // erase the block by copying from the master screen // pos = ylookup[block->screeny]+block->screenx; VW_ScreenToScreen (masterofs+pos,bufferofs+pos, block->width,block->height); // // put 2s in update where the block was, to force sprites to update // xtl = block->screenx >> SX_T_SHIFT; xth = (block->screenx+block->width-1) >> SX_T_SHIFT; ytl = block->screeny >> SY_T_SHIFT; yth = (block->screeny+block->height-1) >> SY_T_SHIFT; updatespot = updateptr + uwidthtable[ytl] + xtl; updatedelta = UPDATEWIDE - (xth-xtl+1); for (y=ytl;y<=yth;y++) { for (x=xtl;x<=xth;x++) *updatespot++ = 2; updatespot += updatedelta; // down to next line } #ifdef PROFILE erasecount++; #endif next: block++; } eraselistptr[otherpage] = otherpage ? &eraselist[1][0] : &eraselist[0][0]; #ifdef PROFILE strcpy (scratch,"\tErase:"); itoa (erasecount,str,10); strcat (scratch,str); write (profilehandle,scratch,strlen(scratch)); #endif } /* ==================== = = RFL_UpdateSprites EGA = = NOTE: Implement vertical clipping! = ==================== */ void RFL_UpdateSprites (void) { spritelisttype *sprite; int portx,porty,x,y,xtl,xth,ytl,yth; int priority; unsigned dest; byte *updatespot,*baseupdatespot; unsigned updatedelta; unsigned updatecount; unsigned height,sourceofs; #ifdef PROFILE updatecount = 0; #endif for (priority=0;prioritynextsprite) { // // see if the sprite has any visable area in the port // portx = sprite->screenx - originxscreen; porty = sprite->screeny - originyscreen; xtl = portx >> SX_T_SHIFT; xth = (portx + sprite->width-1) >> SX_T_SHIFT; ytl = porty >> SY_T_SHIFT; yth = (porty + sprite->height-1) >> SY_T_SHIFT; if (xtl<0) xtl = 0; if (xth>=PORTTILESWIDE) xth = PORTTILESWIDE-1; if (ytl<0) ytl = 0; if (yth>=PORTTILESHIGH) yth = PORTTILESHIGH-1; if (xtl>xth || ytl>yth) continue; // // see if it's visable area covers any non 0 update tiles // updatespot = baseupdatespot = updateptr + uwidthtable[ytl] + xtl; updatedelta = UPDATEWIDE - (xth-xtl+1); if (sprite->updatecount) { sprite->updatecount--; // the sprite was just placed, goto redraw; // so draw it for sure } for (y=ytl;y<=yth;y++) { for (x=xtl;x<=xth;x++) if (*updatespot++) goto redraw; updatespot += updatedelta; // down to next line } continue; // no need to update redraw: // // set the tiles it covers to 3, because those tiles are being // updated // updatespot = baseupdatespot; for (y=ytl;y<=yth;y++) { for (x=xtl;x<=xth;x++) *updatespot++ = 3; updatespot += updatedelta; // down to next line } // // draw it! // height = sprite->height; sourceofs = sprite->sourceofs; if (porty<0) { height += porty; // clip top off sourceofs -= porty*sprite->width; porty = 0; } else if (porty+height>PORTSCREENHIGH) { height = PORTSCREENHIGH - porty; // clip bottom off } dest = bufferofs + ylookup[porty] + portx; switch (sprite->draw) { case spritedraw: VW_MaskBlock(grsegs[sprite->grseg], sourceofs, dest,sprite->width,height,sprite->planesize); break; case maskdraw: VW_InverseMask(grsegs[sprite->grseg], sourceofs, dest,sprite->width,height); break; } #ifdef PROFILE updatecount++; #endif } } #ifdef PROFILE strcpy (scratch,"\tSprites:"); itoa (updatecount,str,10); strcat (scratch,str); write (profilehandle,scratch,strlen(scratch)); #endif } /* ===================== = = RF_Refresh EGA = = All routines will draw at the port at bufferofs, possibly copying from = the port at masterofs. The EGA version then page flips, while the = CGA version updates the screen from the buffer port. = = Screenpage is the currently displayed page, not the one being drawn = Otherpage is the page to be worked with now = ===================== */ void RF_Refresh (void) { byte *newupdate; updateptr = updatestart[otherpage]; RFL_AnimateTiles (); // DEBUG // // update newly scrolled on tiles and animated tiles from the master screen // EGAWRITEMODE(1); EGAMAPMASK(15); RFL_UpdateTiles (); RFL_EraseBlocks (); // // Update is all 0 except where sprites have changed or new area has // been scrolled on. Go through all sprites and update the ones that cover // a non 0 update tile // EGAWRITEMODE(0); RFL_UpdateSprites (); // // if the main program has a refresh hook set, call their function before // displaying the new page // if (refreshvector) refreshvector(); // // display the changed screen // VW_SetScreen(bufferofs+panadjust,panx & xpanmask); // // prepare for next refresh // // Set the update array to the middle position and clear it out to all "0"s // with an UPDATETERMINATE at the end // updatestart[otherpage] = newupdate = baseupdatestart[otherpage]; asm mov ax,ds asm mov es,ax asm xor ax,ax asm mov cx,(UPDATESCREENSIZE-2)/2 asm mov di,[newupdate] asm rep stosw asm mov [WORD PTR es:di],UPDATETERMINATE screenpage ^= 1; otherpage ^= 1; bufferofs = screenstart[otherpage]; displayofs = screenstart[screenpage]; // // calculate tics since last refresh for adaptive timing // RF_CalcTics (); } #endif // GRMODE == EGAGR /* ============================================================================= CGA specific routines ============================================================================= */ #if GRMODE == CGAGR /* ===================== = = RF_NewPosition CGA = ===================== */ void RF_NewPosition (unsigned x, unsigned y) { int mx,my; byte *spotptr; unsigned updatenum; RFL_BoundNewOrigin (x,y); // // clear out all animating tiles // RFL_InitAnimList (); // // set up the new update arrays at base position // updateptr = baseupdateptr; spotptr = updateptr + PORTTILESWIDE; // used to stick "0"s after rows updatenum = 0; // start at first visable tile for (my=0;my1 || absdy>1) { // // scrolled more than one tile, so start from scratch // RF_NewPosition(originxglobal,originyglobal); return; } if (!absdx && !absdy) return; // the screen has not scrolled an entire tile // // float screens // screenmove = deltay*16*SCREENWIDTH + deltax*TILEWIDTH; bufferofs += screenmove; masterofs += screenmove; // // float the update regions // move = deltax; if (deltay==1) move += UPDATEWIDE; else if (deltay==-1) move -= UPDATEWIDE; updateptr+=move; // // draw the new tiles just scrolled on to the master screen, and // mark them as needing to be copied to each screen next refreshes // Make sure a zero is at the end of each row in update // if (deltax) { if (deltax==1) { RFL_NewRow (1); // new right row RFL_RemoveAnimsOnX (originxtile-1); } else { RFL_NewRow (3); // new left row RFL_RemoveAnimsOnX (originxtile+PORTTILESWIDE); } spotptr = updateptr+PORTTILESWIDE; for (yy=0;yyupdatecount) // may not have been drawn at all yet memcpy (eraselistptr[0]++,sprite,sizeof(eraseblocktype)); if (priority != sprite->priority) { // sprite moved to another priority, so unlink the old one and // relink it in the new priority next = sprite->nextsprite; // cut old links if (next) next->prevptr = sprite->prevptr; *sprite->prevptr = next; goto linknewspot; } } else { // this is a brand new sprite, so allocate a block from the array if (!spritefreeptr) Quit ("RF_PlaceSprite: No free spots in spritearray!"); sprite = spritefreeptr; spritefreeptr = spritefreeptr->nextsprite; linknewspot: next = prioritystart[priority]; // stick it in new spot if (next) next->prevptr = &sprite->nextsprite; sprite->nextsprite = next; prioritystart[priority] = sprite; sprite->prevptr = &prioritystart[priority]; } // // write the new info to the sprite // spr = &spritetable[spritenumber-STARTSPRITES]; block = (spritetype _seg *)grsegs[spritenumber]; if (!block) { strcpy (str,"RF_PlaceSprite: Placed an uncached sprite!"); itoa (spritenumber,str2,10); strcat (str,str2); Quit (str); } globaly+=spr->orgy; globalx+=spr->orgx; sprite->screenx = globalx >> G_CGASX_SHIFT; sprite->screeny = globaly >> G_SY_SHIFT; sprite->width = block->width[0]; sprite->height = spr->height; sprite->grseg = spritenumber; sprite->sourceofs = block->sourceoffset[0]; sprite->planesize = block->planesize[0]; sprite->draw = draw; sprite->priority = priority; sprite->tilex = sprite->screenx >> SX_T_SHIFT; sprite->tiley = sprite->screeny >> SY_T_SHIFT; sprite->tilewide = ( (sprite->screenx + sprite->width -1) >> SX_T_SHIFT ) - sprite->tilex + 1; sprite->tilehigh = ( (sprite->screeny + sprite->height -1) >> SY_T_SHIFT ) - sprite->tiley + 1; sprite->updatecount = 1; // draw on next refresh // save the sprite pointer off in the user's pointer so it can be moved // again later *user = sprite; } //=========================================================================== /* ===================== = = RF_RemoveSprite CGA = ===================== */ void RF_RemoveSprite (void **user) { spritelisttype *sprite,*next; sprite = (spritelisttype *)*user; if (!sprite) return; // // post an erase block to erase the old position by copying // screenx,screeny,width,height // if (!sprite->updatecount) { memcpy (eraselistptr[0]++,sprite,sizeof(eraseblocktype)); } // // unlink the sprite node // next = sprite->nextsprite; if (next) // if (!next), sprite is last in chain next->prevptr = sprite->prevptr; *sprite->prevptr = next; // // add it back to the free list // sprite->nextsprite = spritefreeptr; spritefreeptr = sprite; // // null the users pointer, so next time that actor gets placed, it will // allocate a new block // *user = 0; } /* ==================== = = RFL_EraseBlocks CGA = = Write mode 1 should be set = ==================== */ void RFL_EraseBlocks (void) { eraseblocktype *block,*done; int screenxh,screenyh; unsigned pos,xtl,ytl,xth,yth,x,y; byte *updatespot; unsigned updatedelta; block = &eraselist[0][0]; done = eraselistptr[0]; while (block != done) { // // clip the block to the current screen view // block->screenx -= originxscreen; block->screeny -= originyscreen; if (block->screenx < 0) { block->width += block->screenx; if (block->width<1) goto next; block->screenx = 0; } if (block->screeny < 0) { block->height += block->screeny; if (block->height<1) goto next; block->screeny = 0; } screenxh = block->screenx + block->width; screenyh = block->screeny + block->height; if (screenxh > CGAPORTSCREENWIDE) { block->width = CGAPORTSCREENWIDE-block->screenx; screenxh = block->screenx + block->width; } if (screenyh > PORTSCREENHIGH) { block->height = PORTSCREENHIGH-block->screeny; screenyh = block->screeny + block->height; } if (block->width<1 || block->height<1) goto next; // // erase the block by copying from the master screen // pos = ylookup[block->screeny]+block->screenx; block->width = (block->width + (pos&1) + 1)& ~1; pos &= ~1; // make sure a word copy gets used VW_ScreenToScreen (masterofs+pos,bufferofs+pos, block->width,block->height); // // put 2s in update where the block was, to force sprites to update // xtl = block->screenx >> SX_T_SHIFT; xth = (block->screenx+block->width-1) >> SX_T_SHIFT; ytl = block->screeny >> SY_T_SHIFT; yth = (block->screeny+block->height-1) >> SY_T_SHIFT; updatespot = updateptr + uwidthtable[ytl] + xtl; updatedelta = UPDATEWIDE - (xth-xtl+1); for (y=ytl;y<=yth;y++) { for (x=xtl;x<=xth;x++) *updatespot++ = 2; updatespot += updatedelta; // down to next line } next: block++; } eraselistptr[0] = &eraselist[0][0]; } /* ==================== = = RFL_UpdateSprites CGA = = NOTE: Implement vertical clipping! = ==================== */ void RFL_UpdateSprites (void) { spritelisttype *sprite; int portx,porty,x,y,xtl,xth,ytl,yth; int priority; unsigned dest; byte *updatespot,*baseupdatespot; unsigned updatedelta; unsigned updatecount; unsigned height,sourceofs; #ifdef PROFILE updatecount = 0; #endif for (priority=0;prioritynextsprite) { // // see if the sprite has any visable area in the port // portx = sprite->screenx - originxscreen; porty = sprite->screeny - originyscreen; xtl = portx >> SX_T_SHIFT; xth = (portx + sprite->width-1) >> SX_T_SHIFT; ytl = porty >> SY_T_SHIFT; yth = (porty + sprite->height-1) >> SY_T_SHIFT; if (xtl<0) xtl = 0; if (xth>=PORTTILESWIDE) xth = PORTTILESWIDE-1; if (ytl<0) ytl = 0; if (yth>=PORTTILESHIGH) yth = PORTTILESHIGH-1; if (xtl>xth || ytl>yth) continue; // // see if it's visable area covers any non 0 update tiles // updatespot = baseupdatespot = updateptr + uwidthtable[ytl] + xtl; updatedelta = UPDATEWIDE - (xth-xtl+1); if (sprite->updatecount) { sprite->updatecount--; // the sprite was just placed, goto redraw; // so draw it for sure } for (y=ytl;y<=yth;y++) { for (x=xtl;x<=xth;x++) if (*updatespot++) goto redraw; updatespot += updatedelta; // down to next line } continue; // no need to update redraw: // // set the tiles it covers to 3, because those tiles are being // updated // updatespot = baseupdatespot; for (y=ytl;y<=yth;y++) { for (x=xtl;x<=xth;x++) *updatespot++ = 3; updatespot += updatedelta; // down to next line } // // draw it! // height = sprite->height; sourceofs = sprite->sourceofs; if (porty<0) { height += porty; // clip top off sourceofs -= porty*sprite->width; porty = 0; } else if (porty+height>PORTSCREENHIGH) { height = PORTSCREENHIGH - porty; // clip bottom off } dest = bufferofs + ylookup[porty] + portx; switch (sprite->draw) { case spritedraw: VW_MaskBlock(grsegs[sprite->grseg], sourceofs, dest,sprite->width,height,sprite->planesize); break; case maskdraw: VW_InverseMask(grsegs[sprite->grseg], sourceofs, dest,sprite->width,height); break; } #ifdef PROFILE updatecount++; #endif } } } /* ===================== = = RF_Refresh CGA = = All routines will draw at the port at bufferofs, possibly copying from = the port at masterofs. The EGA version then page flips, while the = CGA version updates the screen from the buffer port. = = Screenpage is the currently displayed page, not the one being drawn = Otherpage is the page to be worked with now = ===================== */ void RF_Refresh (void) { long newtime,oldtimecount; RFL_AnimateTiles (); // // update newly scrolled on tiles and animated tiles from the master screen // RFL_UpdateTiles (); RFL_EraseBlocks (); // // Update is all 0 except where sprites have changed or new area has // been scrolled on. Go through all sprites and update the ones that cover // a non 0 update tile // RFL_UpdateSprites (); // // if the main program has a refresh hook set, call their function before // displaying the new page // if (refreshvector) refreshvector(); // // update everything to the screen // VW_CGAFullUpdate (); // // calculate tics since last refresh for adaptive timing // RF_CalcTics (); } #endif // GRMODE == CGAGR