diff --git a/example/ackinfo.c b/example/ackinfo.c new file mode 100644 index 0000000..1e5d83c --- /dev/null +++ b/example/ackinfo.c @@ -0,0 +1,492 @@ +// Function to process and ACK resource file + +#include +#include +#include "ack3d.h" +#include "ackeng.h" +#include "ackext.h" + +// The application MUST have a global variable called ae for use by this +// routine. +extern ACKENG *ae; + +// Globals +int LineNumber; +char LineBuffer[200]; +short LastObjectIndex; +int MapResource; +int PalResource; +int ResScreenBack; +int ResScrollBack; + +// Creates the correct arrays for displaying a rotating background +void ProcessBackDrop(UCHAR *bPtr) { + int i, j, pos; + UCHAR *aPtr; + + for (i = 0; i < 320; i++) { + pos = i + 4; + aPtr = BackArray[i]; + for (j = 0; j < 100; j++) { + *aPtr++ = bPtr[pos]; + pos += 320; + } + } + + for (i = 320; i < 640; i++) { + pos = (i - 320) + 32004; + aPtr = BackArray[i]; + for (j = 0; j < 100; j++) { + *aPtr++ = bPtr[pos]; + pos += 320; + } + } +} + + +// Loads a background image and processes the image into the +// separate slices for use at display time. Currently a background image +// can be 640 columns wide. This number can be made dynamic if needbe and would +// need to be changed in the routine below and in the DrawBackground routine +// in the ACK engine. +int LoadBackDrop(void) { + UCHAR *bPtr; + + if (ResScrollBack) { + bPtr = AckReadiff((char*)ResScrollBack); + if (bPtr == NULL) + return 8; + + ProcessBackDrop(bPtr); + AckFree(bPtr); + } + + return 0; +} + +// Reads a text line from the resource file +int ReadLine(void) { + int len; + char ch; + + len = 0; + while (len < 200) { + if (read(rsHandle, &LineBuffer[len], 1) != 1) + break; + + ch = LineBuffer[len]; + if (ch == 10) + continue; + + if (ch == 13) + break; + + len++; + } + + LineBuffer[len] = '\0'; + + return len; +} + +// Skips to the next parameter in a text line +char *GetNextParm(char *s) { + char ch; + + while (*s) { + ch = *s++; + if (ch == ',') { + while (*s) { + ch = *s; + if (ch != ',' && ch != ' ' && ch != '\t') + return s; + s++; + } + return NULL; + } + } + + return NULL; +} + +// Loads a wall bitmap specified in info file +int LoadWall(void) { + int wnum, rnum, result; + long pos; + char *lb; + + lb = LineBuffer; // Info file buffer + wnum = atoi(lb); // Wall number to load into + + lb = GetNextParm(lb); + + if (lb == NULL) + return -1; + + rnum = atoi(lb); // Resource number + + pos = lseek(rsHandle, 0L, SEEK_CUR); + result = AckLoadWall(ae, (short)wnum, (char *)rnum); + lseek(rsHandle, pos, SEEK_SET); + + return result; +} + +// Loads an object bitmap specified in info file +int LoadObject(void) { + int onum, rnum, result; + long pos; + char *lb; + + lb = LineBuffer; + onum = atoi(lb); // Object bitmap number + + lb = GetNextParm(lb); + + if (lb == NULL) + return -2; + + rnum = atoi(lb); // Resource number + pos = lseek(rsHandle, 0L, SEEK_CUR); + result = AckLoadObject(ae, (short)onum, (char *)rnum); + lseek(rsHandle, pos, SEEK_SET); + + return result; +} + +// Skip any leading spaces in the string +// NOTE: Actually modifies the string passed! +char *SkipSpaces(char *s) { + while (*s == ' ' || *s == '\t' || *s == ',') + strcpy(s, &s[1]); + + return s; +} + +// Creates and object of the desired style +int CreateObject(void) { + short onum, vnum; + short result, oType; + short NumViews, bmPerView; + USHORT flags; + char *lb; + OBJSEQ os; + + lb = LineBuffer; + + if (!strnicmp(lb, "NUMBER:", 7)) { + lb = &lb[7]; + onum = (short)atoi(lb); + if (onum < 1 || onum > MAX_OBJECTS) + return -3; + + result = AckCreateObject(ae, (short)onum); + if (result) + return result; + + LastObjectIndex = onum; + lb = GetNextParm(lb); + if (lb == NULL) + return -4; + + ae->ObjList[onum]->Speed = (short)atoi(lb); + return 0; + } + + onum = LastObjectIndex; // Object number + + oType = 0; + + if (!strnicmp(lb, "CREATE:", 7)) { + oType = NO_CREATE; + lb = &lb[7]; + } + + if (!strnicmp(lb, "DESTROY:", 8)) { + oType = NO_DESTROY; + lb = &lb[8]; + } + + if (!strnicmp(lb, "WALK:", 5)) { + oType = NO_WALK; + lb = &lb[5]; + } + + if (!strnicmp(lb, "ATTACK:", 7)) { + oType = NO_ATTACK; + lb = &lb[7]; + } + + if (!strnicmp(lb, "INTERACT:", 9)) { + oType = NO_INTERACT; + lb = &lb[9]; + } + + if (!oType) + return -5; + + lb = SkipSpaces(lb); + if (lb == NULL) + return -6; + + flags = 0; + strupr(lb); + + if (strstr(lb, "ANIMATE") != NULL) + flags |= OF_ANIMATE; + + if (strstr(lb, "MOVEABLE") != NULL) + flags |= OF_MOVEABLE; + + if (strstr(lb, "PASSABLE") != NULL) + flags |= OF_PASSABLE; + + if (strstr(lb, "MULTIVIEW") != NULL) + flags |= OF_MULTIVIEW; + + if (strstr(lb, "SHOWONCE") != NULL) + flags |= OF_ANIMONCE; + + lb = GetNextParm(lb); + if (lb == NULL) + return-5; + + NumViews = (short)atoi(lb); + if (NumViews < 1) + return -6; + + lb = GetNextParm(lb); + if (lb == NULL) + return -7; + + bmPerView = (short)atoi(lb); + if (bmPerView < 1) + return -7; + + vnum = (short)(NumViews * bmPerView); + if (vnum > MAX_OBJ_BITMAPS) + return -8; + + lb = GetNextParm(lb); + if (lb == NULL) + return -9; + + vnum = 0; + + while (lb != NULL && vnum < MAX_OBJ_BITMAPS) { + os.bitmaps[vnum++] = (short)atoi(lb); + lb = GetNextParm(lb); + } + + os.bmBitmapsPerView = bmPerView; + os.flags = flags; + os.MaxBitmaps = bmPerView; + os.bmSides = NumViews; + + result = (short)AckSetupObject(ae, onum, oType, &os); + + return result; +} + +// Reads the ASCII info file and processes the commands. +int ProcessInfoFile(void) { + int result; + int mode; + long pos; + + LineNumber = 0; + // Position to start of info file within resource file + lseek(rsHandle, rbaTable[0], SEEK_SET); + + mode = result = 0; + + while (!result) { + LineNumber++; + if (!ReadLine()) + continue; + + if (*LineBuffer == ';') + continue; + + if (!strnicmp(LineBuffer, "END:", 4)) + break; + + printf("."); + + switch (mode) { + case 1: // Read walls + if (!strnicmp(LineBuffer, "LOADTYPE:", 9)) { + ae->bmLoadType = (short)atoi(&LineBuffer[9]); // Sets for GIF or BBM + break; + } + + if (!strnicmp(LineBuffer, "ENDBITMAPS:", 11)) + mode = 4; + else + result = LoadWall(); + break; + + case 2: // Object bitmaps + if (!strnicmp(LineBuffer, "LOADTYPE:", 9)) { // Sets for GIF or BBM + ae->bmLoadType = (short)atoi(&LineBuffer[9]); + break; + } + + if (!strnicmp(LineBuffer, "ENDBITMAPS:", 11)) + mode = 5; + else + result = LoadObject(); + break; + + case 3: // Create Object + if (!strnicmp(LineBuffer, "ENDDESC:", 8)) + mode = 5; + else + result = CreateObject(); + break; + + case 4: // Walls topic + if (!strnicmp(LineBuffer, "BITMAPS:", 8)) + mode = 1; + + if (!strnicmp(LineBuffer, "ENDWALLS:", 9)) + mode = 0; + break; + + case 5: // Objects topic + if (!strnicmp(LineBuffer, "BITMAPS:", 8)) + mode = 2; + + if (!strnicmp(LineBuffer, "OBJDESC:", 8)) + mode = 3; + + if (!strnicmp(LineBuffer, "ENDOBJECTS:", 11)) + mode = 0; + break; + + + default: + if (!strnicmp(LineBuffer, "WALLS:", 6)) { + mode = 4; + break; + } + + if (!strnicmp(LineBuffer, "OBJECTS:", 8)) { + mode = 5; + break; + } + + if (!strnicmp(LineBuffer, "MAPFILE:", 8)) { + MapResource = atoi(&LineBuffer[8]); + pos = lseek(rsHandle, 0L, SEEK_CUR); + result = AckReadMapFile(ae, (char*)MapResource); + lseek(rsHandle, pos, SEEK_SET); + break; + } + + if (!strnicmp(LineBuffer, "PALFILE:", 8)) { + PalResource = atoi(&LineBuffer[8]); + break; + } + + if (!strnicmp(LineBuffer, "XPLAYER:", 8)) { + ae->xPlayer = (short)atoi(&LineBuffer[8]); + break; + } + + if (!strnicmp(LineBuffer, "YPLAYER:", 8)) { + ae->yPlayer = (short)atoi(&LineBuffer[8]); + break; + } + + if (!strnicmp(LineBuffer, "PLAYERANGLE:", 12)) { + ae->PlayerAngle = (short)atoi(&LineBuffer[12]); + break; + } + + if (!strnicmp(LineBuffer, "SCREENBACK:", 11)) { + ResScreenBack = atoi(&LineBuffer[11]); + break; + } + + if (!strnicmp(LineBuffer, "SCROLLBACK:", 11)) { + ResScrollBack = atoi(&LineBuffer[11]); + break; + } + + if (!strnicmp(LineBuffer, "TOPCOLOR:", 9)) { + ae->TopColor = (short)atoi(&LineBuffer[9]); + break; + } + + if (!strnicmp(LineBuffer, "BOTTOMCOLOR:", 12)) { + ae->BottomColor = (short)atoi(&LineBuffer[12]); + break; + } + + if (!strnicmp(LineBuffer, "SHADING:", 8)) { + strupr(LineBuffer); + if (strstr(&LineBuffer[8], "OFF") != NULL) + ae->LightFlag = SHADING_OFF; + else + ae->LightFlag = SHADING_ON; + break; + } + + if (!strnicmp(LineBuffer, "FLOORS:", 7)) { + strupr(LineBuffer); + if (strstr(&LineBuffer[7], "OFF") != NULL) + ae->SysFlags |= SYS_SOLID_FLOOR; + else + ae->SysFlags &= ~SYS_SOLID_FLOOR; + break; + } + + if (!strnicmp(LineBuffer, "CEILING:", 8)) { + strupr(LineBuffer); + if (strstr(&LineBuffer[8], "OFF") != NULL) + ae->SysFlags |= SYS_SOLID_CEIL; + else + ae->SysFlags &= ~SYS_SOLID_CEIL; + break; + } + + if (!strnicmp(LineBuffer, "SINGLEBITMAP:", 13)) { + strupr(LineBuffer); + if (strstr(&LineBuffer[13], "ON") != NULL) + ae->SysFlags |= SYS_SINGLE_BMP; + else + ae->SysFlags &= ~SYS_SINGLE_BMP; + break; + } + + if (!strnicmp(LineBuffer, "CEILBITMAP:", 11)) { + ae->CeilBitmap = (short)atoi(&LineBuffer[11]); + break; + } + + if (!strnicmp(LineBuffer, "FLOORBITMAP:", 12)) { + ae->FloorBitmap = (short)atoi(&LineBuffer[12]); + break; + } + + if (!strnicmp(LineBuffer, "RESOLUTION:", 11)) { + Resolution = (short)atoi(&LineBuffer[11]); + break; + } + + if (!strnicmp(LineBuffer, "LOADTYPE:", 9)) { + ae->bmLoadType = (short)atoi(&LineBuffer[9]); // Sets for GIF or BBM + break; + } + + break; + } + fflush(stdout); + } + + printf("\n"); + + return result; +} + diff --git a/example/example.c b/example/example.c new file mode 100644 index 0000000..ba1d96e --- /dev/null +++ b/example/example.c @@ -0,0 +1,227 @@ +/* + * Example ACK-3D Application + * -------------------------- + * + * This is just a bare-bones simple "walkaround" demo application that lets + * you walk through a map. + * + * The ACK-3D demo programs, FDEMO, MALL, and also the Windows demo game + * "Station Escape", all include a fair bit more complexity and a bunch of + * hard-coded magic numbers, and other messyness. + * + * This example program is intended to be _super_ simple to get into. + * Unfortunately, ACK-3D requires a non-trivial amount of game resource file + * loading to be performed to get even a simple level up and running, but + * those details are mostly re-usable and is kept in a separate source file. + * This source file just contains the basics to initialize the engine and + * move a player around a game world. + */ +#include +#include +#include +#include + +#include "ack3d.h" +#include "ackeng.h" +#include "ackext.h" + +#define KEY_RIGHT 77 +#define KEY_UP 72 +#define KEY_LEFT 75 +#define KEY_DOWN 80 +#define KEY_SPACE 57 +#define KEY_ESCAPE 1 + +#define TURN_SPEED 5 +#define TURN_SPEED_DECAY 3 +#define MOVE_SPEED 5 +#define MOVE_SPEED_DECAY 3 +#define BASE_MOVE_AMOUNT 8 +#define BASE_TURN_AMOUNT INT_ANGLE_4 + +// ACK3D bitmap loading routines, such as AckReadiff(), will automatically +// populate (and re-populate) this array with palette found in that image +// file. still, this must be manually set with AckSetPalette() +extern unsigned char colordat[]; + +int ProcessInfoFile(void); + +// this is the main ACK3D engine structure. holds the map, bitmaps, objects, +// and also the current state of the game world (as far as ACK3D is concerned) +ACKENG *ae; + +// this game loop made semi-overcomplicated by rudimentary turn/movement +// acceleration logic +void GameLoop(void) { + int newAngle; + int moveSpeed = 0; + int moveAmount = 0; + int turnSpeed = 0; + int turnAmount = 0; + + AckBuildView(); + AckDisplayScreen(); + + while (1) { + moveAmount = BASE_MOVE_AMOUNT + moveSpeed; + turnAmount = BASE_TURN_AMOUNT + turnSpeed; + + if (AckKeys[KEY_ESCAPE]) { + break; + } + + if (AckKeys[KEY_LEFT]) { + turnSpeed += TURN_SPEED; + ae->PlayerAngle -= turnAmount; + if (ae->PlayerAngle < 0) + ae->PlayerAngle += INT_ANGLE_360; + } + + if (AckKeys[KEY_RIGHT]) { + turnSpeed += TURN_SPEED; + ae->PlayerAngle += turnAmount; + if (ae->PlayerAngle >= INT_ANGLE_360) + ae->PlayerAngle -= INT_ANGLE_360; + } + + if (AckKeys[KEY_UP]) { + moveSpeed += MOVE_SPEED; + + // this function handles collision detection for you reasonably + // well actually. pretty handy + AckMovePOV(ae->PlayerAngle, moveAmount); + } + + if (AckKeys[KEY_DOWN]) { + moveSpeed += MOVE_SPEED; + newAngle = ae->PlayerAngle + INT_ANGLE_180; + if (newAngle >= INT_ANGLE_360) + newAngle -= INT_ANGLE_360; + AckMovePOV(newAngle, moveAmount); + } + + if (AckKeys[KEY_SPACE]) { + AckCheckDoorOpen(ae->xPlayer, ae->yPlayer, ae->PlayerAngle); + } + + // updates object animation and various object state things + AckCheckObjectMovement(); + + // renders the ack3d world to an offscreen buffer. also updates some + // ack3d state (such as door opening/closing) + AckBuildView(); + + // copies offscreen buffer to vga framebuffer + AckDisplayScreen(); + + moveSpeed -= MOVE_SPEED_DECAY; + if (moveSpeed < 0) + moveSpeed = 0; + if (moveSpeed > 16) + moveSpeed = 16; + + turnSpeed -= TURN_SPEED_DECAY; + if (turnSpeed < 0) + turnSpeed = 0; + if (turnSpeed > 16) + turnSpeed = 16; + + } +} + + +int main(int argc, char *argv[]) { + int result; + + printf("Example ACK-3D application\n"); + + ae = (ACKENG*)malloc(sizeof(ACKENG)); + memset(ae, 0, sizeof(ACKENG)); + + // ack3d renderer viewport size. on 486 and lower-spec machines, you + // probably do not want this to be at full 320x200 for performance reasons + // even reducing 320x200 by 20% makes a nice, noticeable difference! + ae->WinStartX = 0; + ae->WinStartY = 0; + ae->WinEndX = 319; + ae->WinEndY = 199; + + // various other flags that can be set too + ae->LightFlag = SHADING_ON; + ae->DoorSpeed = 3; + + printf("Initializing ACK-3D\n"); + + // main ack3d initialization. AckInitialize builds up a bunch of lookup + // tables and other global engine things like that. kit.ovl contains a + // bunch of pre-calculate trig lookup tables. if it was not opened before + // AckInitialize is called, then AckInitialize tries to find a file called + // trig.dat in the current directory. + + result = AckOpenResource("kit.ovl"); + if (result) { + printf("Error opening kit.ovl\n"); + return 1; + } + + result = AckInitialize(ae); + if (result) { + printf("AckInitialize failed\n"); + return 1; + } + AckCloseResource(); + + // the big whammy. pics.dtf has basically all your game assets and takes + // the longest. ProcessInfoFile walks through a ".inf" file that should + // have been packed in pics.dtf at the first slot. that file (which is + // just a text file) has directives to load everything else (map, bitmaps, + // object definitions, etc) + + printf("Processing pics.dtf\n"); + result = AckOpenResource("pics.dtf"); + if (result) { + printf("Error opening pics.dtf\n"); + return 1; + } + + result = ProcessInfoFile(); + if (result) { + printf("Error while processing. Result code %d\n", result); + return 1; + } + + printf("Processing scrolling backdrop\n"); + // processes any (optional) scrolling backdrop that was loaded so it is + // ready to be rendered + LoadBackDrop(); + + AckCloseResource(); + + printf("Finished initializing\n"); + + // the main ack3d rendering functions all assume that the various fields + // in ACKENG have been copied into a whole bunch of global variables. this + // architecture was used for performance reasons. + // ACKENG pointer fields are simply copied, so modifying the actual data + // should be fine during runtime (if you were so inclined) without needing + // to call this function again. + // other flags/number fields from ACKENG are copied, so if you needed to + // change this type of thing during runtime, you would need to call this + // function again for the change to take effect! + AckRegisterStructure(ae); + + AckSetupKeyboard(); + AckSetVGAmode(); + AckSetPalette(colordat); + + GameLoop(); + + AckWrapUp(ae); + AckSetTextMode(); + + if (kbhit()) + getch(); + + return 0; +} + diff --git a/example/kit.ovl b/example/kit.ovl new file mode 100644 index 0000000..9e70eae Binary files /dev/null and b/example/kit.ovl differ diff --git a/example/makefile b/example/makefile new file mode 100644 index 0000000..dd10982 --- /dev/null +++ b/example/makefile @@ -0,0 +1,58 @@ +target_config = debug + +target_name = example + +acklib_incdir = ..\ack_lib +acklib_lib = ..\ack_lib\acklib.lib + +object_files = & + example.obj & + ackinfo.obj + +cc_flags_debug = /d2 /zp1 /4r /fp3 /j +cc_flags_release = /d1+ /zp1 /4r /fp3 /onetx /j +cc_flags = /mf /i=$(acklib_incdir) $(cc_flags_$(target_config)) + +link_flags_debug = debug all +link_flags_release = debug all +link_flags = $(link_flags_$(target_config)) + +asm_flags_debug = /zi +asm_flags_release = /zi +asm_flags = /m /ml $(asm_flags_$(target_config)) + +.NOCHECK +build : $(target_name).exe + +.c.obj: .AUTODEPEND + wcc386 $[. /zq $(cc_flags) + +.asm.obj: .AUTODEPEND + tasm $[. /t $(asm_flags) + +$(target_name).lnk: $(object_files) + %create $^@ + %append $^@ NAME $(target_name).exe + %append $^@ SYSTEM DOS4G + %append $^@ OPTION QUIET + %append $^@ OPTION STACK=16k + %append $^@ LIBRARY $(acklib_lib) + @for %i in ($(object_files)) do %append $^@ FILE %i + +$(target_name).exe: $(object_files) $(target_name).lnk + wlink $(link_flags) @$(target_name).lnk + +clean : .SYMBOLIC + del *.obj + del *.err + del $(target_name).exe + del $(target_name).lnk + +.NOCHECK +run : $(target_name).exe + $(target_name).exe + +.NOCHECK +debug : $(target_name).exe + wd /swap /trap=rsi $(target_name).exe +