/////////////////////////////////////////////////////////////////////////////////////////////////// // Handy constants. const FALSE = 0 const TRUE = !FALSE const NULL = 0 /////////////////////////////////////////////////////////////////////////////////////////////////// // Fixed memory locations const displayEngine = $6000 // main mem (raycaster and tile engine at same location) const expandVec = $2000 // aux mem (only for raycaster) const fontEngine = $BA00 // main mem /////////////////////////////////////////////////////////////////////////////////////////////////// // Resource numbers const RES_NUM_RAYCASTER = 1 const RES_NUM_EXPAND_VEC = 2 const RES_NUM_FONT_ENGINE = 3 const RES_NUM_TILE_ENGINE = 4 /////////////////////////////////////////////////////////////////////////////////////////////////// // Hardware addresses. const keyboard = $C000 const keystrobe = $C010 /////////////////////////////////////////////////////////////////////////////////////////////////// // Memory manager definitions // Resource types const RES_TYPE_CODE = 1 const RES_TYPE_2D_MAP = 2 const RES_TYPE_3D_MAP = 3 const RES_TYPE_TILE = 4 const RES_TYPE_TEXTURE = 5 const RES_TYPE_SCREEN = 6 const RES_TYPE_FONT = 7 const RES_TYPE_MODULE = 8 const RES_TYPE_BYTECODE = 9 const RES_TYPE_FIXUP = 10 // Memory banks const MAIN_MEM = 0 const AUX_MEM = 1 // Command codes const RESET_MEMORY = $10 const REQUEST_MEMORY = $11 const LOCK_MEMORY = $12 const UNLOCK_MEMORY = $13 const SET_MEM_TARGET = $14 const START_LOAD = $15 const QUEUE_LOAD = $16 const FINISH_LOAD = $17 const FREE_MEMORY = $18 const CALC_FREE = $19 const DEBUG_MEM = $1A const CHAIN_LOADER = $1E const FATAL_ERROR = $1F /////////////////////////////////////////////////////////////////////////////////////////////////// // Other constants const callbacks = $300 // 3D mode const OVERMAP_NUM = 11 const OVERMAP_IS_3D = 1 // 2D mode //const OVERMAP_NUM = 1 //const OVERMAP_IS_3D = 0 /////////////////////////////////////////////////////////////////////////////////////////////////// // Predefined functions, for circular calls or out-of-order calls predef setWindow2, initCmds /////////////////////////////////////////////////////////////////////////////////////////////////// // Font engine variables const wndleft = $70 // left edge of the window const wndwdth = $71 // right edge (NOT width) of text window const wndtop = $72 // top of text window const wndbtm = $73 // bottom+1 of text window const cursh = $74 // Cursor H-pos 0-39 const cursv = $75 // Cursor V-pos 0-23 /////////////////////////////////////////////////////////////////////////////////////////////////// // Global variables byte mapNum byte mapIs3D word pFont word pMap word cmdTbl[64] word triggerTbl byte redraw byte frameLoaded = 0 byte textDrawn = FALSE byte needRender = FALSE word skyNum = 9 word groundNum = 10 /////////////////////////////////////////////////////////////////////////////////////////////////// // Definitions used by assembly code asm __defs ; Use hi-bit ASCII for Apple II !convtab "../../include/hiBitAscii.ct" ; Headers !source "../../include/global.i" !source "../../include/plasma.i" !source "../../include/mem.i" !source "../../include/fontEngine.i" ; Optional debug printing support DEBUG = 1 !if DEBUG { !source "../../include/debug.i" } ; General use tmp = $2 pTmp = $4 ; NOTE ABOUT ABSOLUTE ADDRESSES ; You cannot use them: this code, including variable space, can be loaded *anywhere*. ; So don't declare any variables as !byte or !word here. end /////////////////////////////////////////////////////////////////////////////////////////////////// // API to call rendering engine (same API for raycaster and tile engine) asm initDisplay // params: mapNum, pMapData, x, y, dir +asmPlasm 5 jmp $6000 end asm flipToPage1 // no params +asmPlasm 0 jmp $6003 end asm getPos // params: @x, @y +asmPlasm 2 jmp $6006 end asm setPos // params: x, y +asmPlasm 2 jmp $6009 end asm getDir // no params; returns: dir (0-15) +asmPlasm 0 jmp $600C end asm setDir // params: dir (0-15) +asmPlasm 1 jmp $600F end asm advance // no params; return: 0 if same pos, 1 if new pos, 2 if new pos and scripted +asmPlasm 0 jmp $6012 end asm setColor // params: slot (0=sky/1=ground), color (0-15) +asmPlasm 2 jmp $6015 end asm render // no params +asmPlasm 0 jmp $6018 end /////////////////////////////////////////////////////////////////////////////////////////////////// // Simply retrieve the X register. Used to double-check that we're not leaking PLASMA eval // stack entries. asm getXReg +asmPlasm 0 txa ldy #0 rts end /////////////////////////////////////////////////////////////////////////////////////////////////// // Print a string asm puts +asmPlasm 1 sta pTmp sty pTmp+1 ldy #0 lda (pTmp),y tax iny - lda (pTmp),y ora #$80 jsr cout iny dex bne - rts end /////////////////////////////////////////////////////////////////////////////////////////////////// // Print part of a string, until we hit the end or a '%' code. Return how far we got, or -1 for end. asm partialPrintf !zone { +asmPlasm 2 lda evalStkL+1,x ; get string pointer sta pTmp lda evalStkH+1,x sta pTmp+1 ldy #0 lda (pTmp),y ; get length byte sec sbc evalStkL,x ; minus offset sta tmp ; to count of characters left to print bcc .eos ; avoid overrunning beq .eos lda evalStkL,x ; get desired offset into string tay iny ; increment past length byte - lda (pTmp),y ora #$80 cmp #'%' ; stop if we hit % code beq + jsr cout iny dec tmp ; otherwise go until end of string bne - .eos ldy #$FF ; if we hit end of string, return -1 tya rts + dey ; adjust back for length byte tya ; that's the lo byte of return ldy #0 ; hi byte of return is zero rts } end /////////////////////////////////////////////////////////////////////////////////////////////////// // Print a 16-bit hex value asm printHex +asmPlasm 1 tax tya jmp prntax end /////////////////////////////////////////////////////////////////////////////////////////////////// // Print a single character asm printChar +asmPlasm 1 ora #$80 jmp cout end /////////////////////////////////////////////////////////////////////////////////////////////////// // Print a carriage return asm crout +asmPlasm 0 jmp crout end /////////////////////////////////////////////////////////////////////////////////////////////////// // Ring the bell asm beep +asmPlasm 0 jmp bell end /////////////////////////////////////////////////////////////////////////////////////////////////// // Read a string from the keyboard, turn it into a PLASMA string and return a pointer to the string. asm readStr +asmPlasm 0 jsr getln1 txa pha beq + - lda inbuf-1,x and #$7F sta inbuf,x dex bne - + pla sta inbuf,x lda #inbuf rts end /////////////////////////////////////////////////////////////////////////////////////////////////// // Send a command to the memory manager // Params: cmd, mainOrAux, amount asm loader +asmPlasm 3 lda evalStkL+2,x ; command code pha lda evalStkL+1,x ; main or aux lsr ldy evalStkH,x ; address (or other param) lda evalStkL,x tax pla bcs + jsr mainLoader ; ret value in X=lo/Y=hi txa rts + jsr auxLoader ; ditto txa rts end /////////////////////////////////////////////////////////////////////////////////////////////////// // Set up the font engine // Params: pFont asm initFontEngine +asmPlasm 1 ldy evalStkL,x ; font engine likes *lo* byte in Y lda evalStkH,x ; hi byte in X tax jsr setFONT ; Set to write text on both hi-res pages at the same time lda #pHGR3 jsr displayMODE ; Set to normal (non-inverse) text lda #pNORMAL jmp drawMODE end /////////////////////////////////////////////////////////////////////////////////////////////////// // Jump straight to the system monitor // Params: None asm goMon bit setROM jmp $FF69 end /////////////////////////////////////////////////////////////////////////////////////////////////// // Execute a monitor breakpoint // Params: None asm brk bit setROM bit setText bit page1 brk end /////////////////////////////////////////////////////////////////////////////////////////////////// // Use the font engine to clear the current text window // Params: None asm clearWindow +asmPlasm 0 jmp clearWINDOW end /////////////////////////////////////////////////////////////////////////////////////////////////// // Display a string using the font engine. Automatically splits lines to keep words from breaking. // Params: pStr asm displayStr +asmPlasm 1 sta pTmp sty pTmp+1 ldy #0 lda (pTmp),y beq ++ inc pTmp bne + inc pTmp+1 + -- tax ldy #0 - lda (pTmp),y cmp #$20 bcc + cmp #$80 bcc ++ + lda #$C4 jsr $fded brk ++ dex cmp #$20 beq + sta $281,y iny cpx #0 bne - + sty $280 tya sec adc pTmp sta pTmp bcc + inc pTmp+1 + txa pha tya clc adc cursh sec sbc #2 cmp wndwdth bcc + lda #$D jsr printCHAR + ldy #$80 ldx #2 jsr printSTR lda cursh cmp wndleft beq + lda #$20 jsr printCHAR + pla bne -- ++ lda #$d jmp printCHAR end /////////////////////////////////////////////////////////////////////////////////////////////////// // Convert a PLASMA string (starts with length, lo-bit ascii) to an assembly string // (zero-terminated, hi-bit ascii). asm toAsmStr lda evalStkL,x sta pTmp lda evalStkH,x sta pTmp+1 ldy #0 lda (pTmp),y beq + sta tmp - iny lda (pTmp),y ora #$80 dey sta (pTmp),y iny dec tmp bne - lda #0 sta (pTmp),y + rts end /////////////////////////////////////////////////////////////////////////////////////////////////// // General methods /////////////////////////////////////////////////////////////////////////////////////////////////// // Fatal error: print message and stop the system. def fatal(msg) toAsmStr(msg) loader(FATAL_ERROR, MAIN_MEM, msg) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Print a signed decimal word def printDec(n) word n0 if n < 0; printChar('-'); n = -n; fin n0 = n if n0 > 9999; printChar('0' + n/10000); n = n%10000; fin if n0 > 999; printChar('0' + n/1000); n = n%1000; fin if n0 > 99; printChar('0' + n/100); n = n%100; fin if n0 > 9; printChar('0' + n/10); n = n%10; fin printChar('0' + n) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Print a formatted string a'la C printf, with up to four parameters. def printf4(str, arg1, arg2, arg3, arg4) word pos word curArg word p pos = 0 curArg = @arg1 while TRUE pos = partialPrintf(str, pos) if pos < 0 break fin p = str + pos + 2 when ^p is 'x' printHex(*curArg); break is 'd' printDec(*curArg); break is 's' puts(*curArg); break is '%' printChar('%'); break otherwise printHex(^p); fatal("Unknown % code") wend curArg = curArg + 2 pos = pos + 2 loop end def printf1(str, arg1); printf4(str, arg1, 0, 0, 0); end def printf2(str, arg1, arg2); printf4(str, arg1, arg2, 0, 0); end def printf3(str, arg1, arg2, arg3); printf4(str, arg1, arg2, arg3, 0); end /////////////////////////////////////////////////////////////////////////////////////////////////// def parseDec(str) word n word pend word p byte neg neg = FALSE n = 0 p = str + 1 pend = p + ^str while p < pend if p == (str+1) and ^p == '-' neg = TRUE elsif ^p >= '0' and ^p <= '9' n = (n*10) + (^p - '0') else break fin p = p+1 loop if neg; return -n; fin return n end /////////////////////////////////////////////////////////////////////////////////////////////////// // Get a keystroke and convert it to upper case def getUpperKey() byte key while ^keyboard < 128 loop key = ^keyboard & $7F ^keystrobe if key >= $60 key = key - $20 fin return key end /////////////////////////////////////////////////////////////////////////////////////////////////// // Set the sky color (relevant to 3D display only) def setSky(num) skyNum = num setColor(0, skyNum) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Switch to the next sky color (3D only) def nextSky() setSky((skyNum + 1) & $F) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Set the ground color (relevant to 3D display only) def setGround(num) groundNum = num setColor(1, groundNum) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Switch to the next ground color (3D only) def nextGround() setGround((groundNum + 1) & $F) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Switch to text mode and display mem manager debug printout, get a key, switch back to graphics. def debugMem(bank) ^$c051 ^$c054 loader(DEBUG_MEM, bank, 0) getUpperKey() ^$c050 end /////////////////////////////////////////////////////////////////////////////////////////////////// // Establish a keystroke -> command association in the command table def initCmd(key, func) if key >= $60 key = key - $20 fin cmdTbl[key-$20] = func end /////////////////////////////////////////////////////////////////////////////////////////////////// // Load the Frame Image, and lock it. def loadFrameImg() byte img img = mapIs3D+2 if frameLoaded <> img if frameLoaded loader(UNLOCK_MEMORY,MAIN_MEM, $2000) loader(FREE_MEMORY, MAIN_MEM, $2000) fin loader(SET_MEM_TARGET, MAIN_MEM, $2000) loader(QUEUE_LOAD, MAIN_MEM, img<<8 | RES_TYPE_SCREEN) loader(LOCK_MEMORY, MAIN_MEM, $2000) frameLoaded = img fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Load code and data, set up everything to display a 3D map def initMap(x, y, dir) // Set up the command table initCmds() // Reset memory (our module will stay since memory manager locked it upon load) loader(RESET_MEMORY, MAIN_MEM, 0) // Load the font engine and its font loader(SET_MEM_TARGET, MAIN_MEM, fontEngine) loader(QUEUE_LOAD, MAIN_MEM, RES_NUM_FONT_ENGINE<<8 | RES_TYPE_CODE) loader(SET_MEM_TARGET, MAIN_MEM, $9000) pFont = loader(QUEUE_LOAD, MAIN_MEM, 1<<8 | RES_TYPE_FONT) // Queue loading of the raycaster or tile engine and the map data loader(SET_MEM_TARGET, MAIN_MEM, displayEngine) if mapIs3D loader(QUEUE_LOAD, MAIN_MEM, RES_NUM_RAYCASTER<<8 | RES_TYPE_CODE) pMap = loader(QUEUE_LOAD, MAIN_MEM, mapNum<<8 | RES_TYPE_3D_MAP) loader(SET_MEM_TARGET, AUX_MEM, expandVec) loader(QUEUE_LOAD, AUX_MEM, RES_NUM_EXPAND_VEC<<8 | RES_TYPE_CODE) else loader(QUEUE_LOAD, MAIN_MEM, RES_NUM_TILE_ENGINE<<8 | RES_TYPE_CODE) pMap = loader(QUEUE_LOAD, MAIN_MEM, mapNum<<8 | RES_TYPE_2D_MAP) fin // Load everything that we just queued loader(FINISH_LOAD, MAIN_MEM, 1) // 1 = keep open // Load the frame image (and lock it there) loadFrameImg() loader(FINISH_LOAD, MAIN_MEM, 1) // 1 = keep open // Start up the font engine initFontEngine(pFont) // Start up the display engine with map data and starting position. This will also load and // init the script module, if any, which will end up calling us back at the setScriptInfo triggerTbl = NULL setWindow2() puts("Calling initDisplay.\n") initDisplay(mapNum, pMap, x, y, dir) puts("Back from initDisplay.\n") needRender = FALSE end /////////////////////////////////////////////////////////////////////////////////////////////////// // Window for the map name bar def setWindow1() ^wndtop = 1 ^wndbtm = 2 ^wndleft = 4 ^wndwdth = 18 ^cursv = ^wndtop ^cursh = ^wndleft end /////////////////////////////////////////////////////////////////////////////////////////////////// // Window for the large upper right bar def setWindow2() ^wndtop = 3 ^wndbtm = 17 ^wndleft = 22 ^wndwdth = 37 ^cursv = ^wndtop ^cursh = ^wndleft end /////////////////////////////////////////////////////////////////////////////////////////////////// // Window for the mid-size lower right bar def setWindow3() ^wndtop = 18 ^wndbtm = 23 ^wndleft = 23 ^wndwdth = 37 ^cursv = ^wndtop ^cursh = ^wndleft end /////////////////////////////////////////////////////////////////////////////////////////////////// // Check for script(s) attached to the current location, and call them if there are any. def checkScripts() word x word y word p word pNext word script if !triggerTbl; return; fin setWindow2() getPos(@x, @y) p = triggerTbl while TRUE if ^p == $FF return fin pNext = p + p->1 if ^p == y p = p + 2 while p < pNext if x == ^p script = p=>1 script() fin p = p + 3 loop fin p = pNext loop end /////////////////////////////////////////////////////////////////////////////////////////////////// // Advance one step forward (3D maps only) def moveForward() byte val val = advance() // If not blocked, render at the new position. if val > 0 needRender = TRUE fin // If we're on a new map tile, clear text from script(s) on the old tile. if val >= 2 and textDrawn clearWindow() textDrawn = FALSE fin // If there are script(s) on the new tile, run them. if val == 3 checkScripts() fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Adjust player's direction plus or minus n increments def adjustDir(n) setDir((getDir() + n) & 15) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Move backward one step (3D mode) def moveBackward() adjustDir(8) moveForward() adjustDir(8) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Turn left (3D mode) def rotateLeft() adjustDir(-1) needRender = TRUE end /////////////////////////////////////////////////////////////////////////////////////////////////// // Rotate to the right (3D mode) def rotateRight() adjustDir(1) needRender = TRUE end /////////////////////////////////////////////////////////////////////////////////////////////////// // Sidestep to the right (3D mode) def strafeRight() adjustDir(4) moveForward() adjustDir(-4) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Sidestep to the left (3D mode) def strafeLeft() adjustDir(-4) moveForward() adjustDir(4) end /////////////////////////////////////////////////////////////////////////////////////////////////// def moveNorth() setDir(0) moveForward() end def moveEast() setDir(4) moveForward() end def moveSouth() setDir(8) moveForward() end def moveWest() setDir(12) moveForward() end /////////////////////////////////////////////////////////////////////////////////////////////////// // Switch to a new map (2D or 3D) def setMap(is3D, num, x, y, dir) mapIs3D = is3D mapNum = num flipToPage1() initMap(x, y, dir) checkScripts() end /////////////////////////////////////////////////////////////////////////////////////////////////// def kbdTeleport() word x, y byte dir ^$c053 if ^$25 < 23; ^$25 = 23; fin getPos(@x, @y) printf4("\nCurrent: 3D=%d Map=%d X=%d Y=%d ", mapIs3D, mapNum, x, y) printf1("Dir=%d\n", getDir()) puts("3D : ") mapIs3D = parseDec(readStr()) puts("Map: ") mapNum = parseDec(readStr()) puts("X : ") x = parseDec(readStr()) puts("Y : ") y = parseDec(readStr()) puts("Dir: ") dir = parseDec(readStr()) ^$c052 setMap(mapIs3D, mapNum, x, y, dir) end /////////////////////////////////////////////////////////////////////////////////////////////////// def teleport(x, y, dir) printf3("Teleport: x=%d y=%d dir=%d\n", x, y, dir) setPos(x, y) setDir(dir) needRender = TRUE end /////////////////////////////////////////////////////////////////////////////////////////////////// // Get a key and dispatch it to a command. Then do it again, forever. def kbdLoop() word key, func byte xreg, tmp xreg = getXReg() while TRUE tmp = getXReg() if tmp <> xreg printf2("Error: x reg changed from %x to %x\n", xreg, tmp) brk() fin key = getUpperKey() key = key - $20 if key >= 0 and key < $40 func = cmdTbl[key] if func; func(); fin fin if needRender render() needRender = FALSE fin loop end /////////////////////////////////////////////////////////////////////////////////////////////////// // Set initial info for the scripts on this map: the trigger table and the name of the map. This // is called by the init function for the scripts. def setScriptInfo(mapName, trigTbl) puts("In setScriptInfo\n") // Record the trigger table pointer triggerTbl = trigTbl // Display map name setWindow1() //displayStr(mapName) // Back to the main text window. setWindow2() end /////////////////////////////////////////////////////////////////////////////////////////////////// // Called by scripts to display a string. We set the flag noting that something has been // displayed, then use an assembly routine to do the work. def scriptDisplayStr(str) textDrawn = TRUE displayStr(str) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Get a key, and don't return until it's Y or N (or lower-case of those). Returns 1 for Y. def getYN() byte key while TRUE key = getUpperKey() if key == 'Y' return 1 elsif key == 'N' clearWindow() textDrawn = FALSE return 0 fin beep() loop end /////////////////////////////////////////////////////////////////////////////////////////////////// // Set up the command table for 3D mode def initCmds() // Clear the command table byte i for i = 0 to 63 cmdTbl[i] = 0 next // Commands common to both 2D and 3D initCmd('T', @kbdTeleport) // Commands handled differently in 3D vs 2D if mapIs3D initCmd('W', @moveForward) initCmd('A', @rotateLeft) initCmd('D', @rotateRight) initCmd('S', @moveBackward) initCmd('X', @moveBackward) initCmd('Z', @strafeLeft) initCmd('C', @strafeRight) initCmd('I', @moveForward) initCmd('J', @rotateLeft) initCmd('L', @rotateRight) initCmd('K', @moveBackward) initCmd(',', @moveBackward) initCmd('M', @strafeLeft) initCmd('.', @strafeRight) initCmd('Y', @nextSky) initCmd('G', @nextGround) else initCmd('W', @moveNorth) initCmd('D', @moveEast) initCmd('S', @moveSouth) initCmd('X', @moveSouth) initCmd('A', @moveWest) initCmd('I', @moveNorth) initCmd('J', @moveWest) initCmd('L', @moveEast) initCmd('K', @moveSouth) initCmd(',', @moveSouth) fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Load and display the title screen. def loadTitle() puts("Loading Lawless Legends.\n") // Load the title screen puts("Loading title screen.\n") loader(SET_MEM_TARGET, MAIN_MEM, $2000) loader(QUEUE_LOAD, MAIN_MEM, 1<<8 | RES_TYPE_SCREEN) // title screen is fixed at #1 loader(LOCK_MEMORY, MAIN_MEM, $2000) loader(FINISH_LOAD, MAIN_MEM, 1) // 1 = keep open frameLoaded = 1 puts("Title loaded.\n") ^$c050 ^$c057 ^$c054 ^$c052 // Hack for real (not emulated) IIc: sometimes displays only lo-bit graphics // unless we do this. *HUGE* thanks to Brendan Robert for the fix! ^$C07E=0 // disable double-hi-res ^$C05F // disable double-hi-res end /////////////////////////////////////////////////////////////////////////////////////////////////// // Set vectors so that scripts in PLASMA can call back to do things with this engine. def setCallbacks() // $300 callbacks.0 = $4c callbacks:1 = @setScriptInfo // $303 callbacks.3 = $4c callbacks:4 = @scriptDisplayStr // $306 callbacks.6 = $4c callbacks:7 = @getYN // $309 callbacks.9 = $4c callbacks:10 = @setMap // $30C callbacks.12 = $4c callbacks:13 = @setSky // $30F callbacks.15 = $4c callbacks:16 = @setGround // $312 callbacks.18 = $4c callbacks:19 = @teleport end /////////////////////////////////////////////////////////////////////////////////////////////////// // Main code. // loadTitle() setCallbacks() mapIs3D = OVERMAP_IS_3D mapNum = OVERMAP_NUM initMap(4, 4, 0) checkScripts() kbdLoop() done