/////////////////////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2015 The 8-Bit Bunch. Licensed under the Apache License, Version 1.1 // (the "License"); you may not use this file except in compliance with the License. // You may obtain a copy of the License at . // Unless required by applicable law or agreed to in writing, software distributed under // the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF // ANY KIND, either express or implied. See the License for the specific language // governing permissions and limitations under the License. /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // Handy constants. const FALSE = 0 const TRUE = 1 const NULL = 0 /////////////////////////////////////////////////////////////////////////////////////////////////// // Fixed memory locations const seed = $4E // Incremented continuously by keyboard read routine const displayEngine = $6000 // main mem (raycaster and tile engine at same location) const expandVec = $2000 // aux mem (only for raycaster) const fontEngine = $E000 // main mem const heapStart = $F000 // main mem const heapSize = $800 /////////////////////////////////////////////////////////////////////////////////////////////////// // 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 /////////////////////////////////////////////////////////////////////////////////////////////////// // 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 const RES_TYPE_PORTRAIT = 11 // 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 CHECK_MEM = $1B const CHAIN_LOADER = $1E const FATAL_ERROR = $1F const HEAP_SET = $20 const HEAP_ADD_TYPE = $21 const HEAP_ALLOC = $22 const HEAP_INTERN = $23 const HEAP_COLLECT = $24 /////////////////////////////////////////////////////////////////////////////////////////////////// // Other constants const callbacks = $300 include "playtype.plh" //include "heaptest.plh" word global // the global heap object, from which all live objects must be reachable /////////////////////////////////////////////////////////////////////////////////////////////////// // Predefined functions, for circular calls or out-of-order calls predef setWindow2, initCmds /////////////////////////////////////////////////////////////////////////////////////////////////// // Global variables byte mapNum byte mapIs3D word mapNameHash = 0 word totalMapWidth word totalMapHeight word skyNum = 9 word groundNum = 10 byte portraitNum = 1 word triggerOriginX, triggerOriginY word triggerTbl word cmdTbl[64] byte frameLoaded = 0 byte textDrawn = FALSE byte needRender = FALSE // Queue setMap / teleport, since otherwise script might be replaced while executing byte q_mapIs3D byte q_mapNum = 0 word q_x word q_y byte q_dir /////////////////////////////////////////////////////////////////////////////////////////////////// // 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 = 0 ; General use tmp = $2 pTmp = $4 ; 16-bit random number seed - incremented by ROM kbd routine seed = $4E magic = $2227 ; there are 2048 magic values that work; this one caught my eye. ; 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-17) +asmPlasm 2 jmp $6015 end asm render // no params +asmPlasm 0 jmp $6018 end /////////////////////////////////////////////////////////////////////////////////////////////////// asm pushAuxStr // params: none; ret: $200 (inbuf) stx tmp ; save PLASMA's X reg eval stack index tsx lda $103,x ; get PLASMA's Y-reg value from its place in the stack tay ; Create the following subroutine, used to copy chars from aux to main: ;0010- 8D 03 C0 STA $C003 ;0013- B1 F4 LDA ($F4),Y ;0015- 8D 02 C0 STA $C002 ;0018- 60 RTS lda #$8D sta $10 sta $15 ldx #2 stx $16 inx stx $11 lda #$C0 sta $12 sta $17 lda #$B1 sta $13 lda #$F4 sta $14 lda #$60 sta $18 ; Get string length and save it iny ; advance to next code byte bne + inc $F5 ; next pg if necessary tsx inc $105,x ; also PLASMA's copy of the page pointer + jsr $10 sta $200 ldx #0 ; Copy entire string .lup cpx $200 beq ++ iny ; advance to next code byte bne + inc $F5 ; next pg if necessary stx tmp+1 tsx inc $105,x ; also PLASMA's copy of the page pointer ldx tmp+1 + jsr $10 ; now fetch the byte from aux mem inx sta $200,x ; and stuff it in the string / input buffer bne .lup ; always taken ++ tsx tya sta $103,x ; modify PLASMA's Y reg so it picks up code execution just after the string ldx tmp ; retrieve PLASMA's eval stack ptr dex ; make room for ret value lda #0 sta evalStkL,x ; return value lo (<$200) lda #2 sta evalStkH,x ; and hi (>$200) rts end /////////////////////////////////////////////////////////////////////////////////////////////////// asm blitPortrait // params: srcData, dstScreenPtr +asmPlasm 2 ; Save the dest pointer sta pTmp sty pTmp+1 ; Save the source pointer lda evalStkL+1,x sta tmp lda evalStkH+1,x sta tmp+1 ; Create the following subroutine, used to copy pixels from aux to main: ; 0010- 8D 03 C0 STA $C003 ; 0013- B1 02 LDA ($02),Y ; 0015- 91 04 STA ($04),Y ; 0017- 88 DEY ; 0018- 10 F9 BPL $0013 ; 001A- 8D 02 C0 STA $C002 ; 001D- 60 RTS lda #$8D sta $10 sta $1A ldx #2 stx $14 stx $1B inx stx $11 lda #$C0 sta $12 sta $1C lda #$B1 sta $13 lda #$91 sta $15 inx stx $16 lda #$88 sta $17 lda #$10 sta $18 lda #$F9 sta $19 lda #$60 sta $1D ; Outer copy loop ldx #128 ; line count .loop ldy #17 ; byte count minus 1 jsr $10 ; copy pixel bytes ; Advance to next row of data lda tmp clc adc #18 sta tmp bcc + inc tmp+1 + ; Fun code to advance to the next hi-res line ldy pTmp+1 iny iny iny iny cpy #$40 bcc .ok2 tya sbc #$20 ; carry already set tay lda pTmp eor #$80 bmi .ok iny cpy #$24 bcc .ok ldy #$20 adc #$27 ; carry was set, so actually adding $28 .ok sta pTmp .ok2 sty pTmp+1 ; Loop until we've done all rows. dex bne .loop rts 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 /////////////////////////////////////////////////////////////////////////////////////////////////// // Calculate 16-bit hash of a string asm hashString +asmPlasm 1 sta pTmp sty pTmp+1 ldy #0 sty tmp+1 lda (pTmp),y tax iny - clc adc (pTmp),y bcc + inc tmp+1 + asl rol tmp+1 bcc + ora #1 + asl rol tmp+1 bcc + ora #1 + asl rol tmp+1 bcc + ora #1 + iny dex bne - ldy tmp+1 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 /////////////////////////////////////////////////////////////////////////////////////////////////// // Get a character from the keyboard asm rdkey +asmPlasm 0 jmp rdkey 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, wordParam asm mmgr +asmPlasm 2 lda evalStkL+1,x ; command code pha ldy evalStkH,x ; address (or other param)... hi byte in Y lda evalStkL,x tax ; ...lo byte in X pla jsr mainLoader ; ret value in X=lo/Y=hi txa ; to A=lo/Y=hi for asmPlasm rts end // Aux version of memory manager command asm auxMmgr +asmPlasm 2 lda evalStkL+1,x ; command code pha ldy evalStkH,x ; address (or other param) lda evalStkL,x tax pla jsr auxLoader ; ret value in X=lo/Y=hi txa ; to A=lo/Y=hi for asmPlasm rts end /////////////////////////////////////////////////////////////////////////////////////////////////// // Jump straight to the system monitor // Params: None asm goMon jmp $FF69 end /////////////////////////////////////////////////////////////////////////////////////////////////// // Execute a monitor breakpoint // Params: None asm brk bit setText bit page1 brk end /////////////////////////////////////////////////////////////////////////////////////////////////// // Set up the font engine // Params: pFont asm setFont +asmPlasm 1 bit setLcRW+lcBank2 bit setLcRW+lcBank2 jmp SetFont end /////////////////////////////////////////////////////////////////////////////////////////////////// // Use the font engine to clear the current text window asm setWindow +asmPlasm 4 bit setLcRW+lcBank2 bit setLcRW+lcBank2 jmp SetWindow end /////////////////////////////////////////////////////////////////////////////////////////////////// // Use the font engine to clear the current text window // Params: None asm clearWindow +asmPlasm 0 bit setLcRW+lcBank2 bit setLcRW+lcBank2 jmp ClearWindow end /////////////////////////////////////////////////////////////////////////////////////////////////// // Use the font engine to copy the current text window to hi-res page 2 // Params: None asm copyWindow +asmPlasm 0 bit setLcRW+lcBank2 bit setLcRW+lcBank2 jmp CopyWindow end /////////////////////////////////////////////////////////////////////////////////////////////////// // Display a character using the font engine. // Params: ch asm displayChar +asmPlasm 1 bit setLcRW+lcBank2 bit setLcRW+lcBank2 jmp DisplayChar end /////////////////////////////////////////////////////////////////////////////////////////////////// // Display a string using the font engine. // Params: pStr asm displayStr +asmPlasm 1 bit setLcRW+lcBank2 bit setLcRW+lcBank2 jmp DisplayStr end /////////////////////////////////////////////////////////////////////////////////////////////////// // Display a string using the font engine but not its parser. // Params: pStr asm rawDisplayStr +asmPlasm 1 bit setLcRW+lcBank2 bit setLcRW+lcBank2 sta pTmp sty pTmp+1 ldy #0 lda (pTmp),y sta tmp - cpy tmp bcc + rts + iny lda (pTmp),y ora #$80 cmp #"^" bne + iny lda (pTmp),y and #$1F ora #$80 + sty tmp+1 jsr DisplayChar ldy tmp+1 bne - end /////////////////////////////////////////////////////////////////////////////////////////////////// // Random number generator // Adapted from http://codebase64.org/doku.php?id=base:small_fast_16-bit_prng asm rand16 +asmPlasm 0 lda seed beq .lowZero ; $0000 and $8000 are special values to test for ; Do a normal shift asl seed lda seed+1 rol bcc .noEor .doEor: ; high byte is in .A eor #>magic sta seed+1 tay ; for asmPlasm, return hi byte in Y, lo byte in A lda seed eor # 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 'c' printChar(*curArg); break is 'd' printDec(*curArg); break is 's' puts(*curArg); break is 'x' printHex(*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 /////////////////////////////////////////////////////////////////////////////////////////////////// def parseDecWithDefault(str, default) if ^str == 0 return default fin return parseDec(str) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Get a keystroke and convert it to upper case def getUpperKey() byte key word oldSeed oldSeed = *seed // for now, make the seed stay consistent, for randomized heap testing key = rdkey() & $7F *seed = oldSeed 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) needRender = TRUE end /////////////////////////////////////////////////////////////////////////////////////////////////// // Switch to the next sky color (3D only) def nextSky() setSky((skyNum + 1) % 18) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Set the ground color (relevant to 3D display only) def setGround(num) groundNum = num setColor(1, groundNum) needRender = TRUE end /////////////////////////////////////////////////////////////////////////////////////////////////// // Switch to the next ground color (3D only) def nextGround() setGround((groundNum + 1) % 18) 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 mmgr(UNLOCK_MEMORY,$2000) mmgr(FREE_MEMORY, $2000) fin mmgr(SET_MEM_TARGET, $2000) mmgr(QUEUE_LOAD, img<<8 | RES_TYPE_SCREEN) mmgr(LOCK_MEMORY, $2000) frameLoaded = img fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Window for the map name bar def setWindow1() setWindow(8, 18, 35, 119) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Window for the large upper right bar def setWindow2() setWindow(24, 136, 154, 266) displayChar('N'-$40) // Set normal mode - clear all special modes end /////////////////////////////////////////////////////////////////////////////////////////////////// // Window for the mid-size lower right bar def setWindow3() setWindow(144, 184, 154, 266) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Window for the map area (used for clearing it) def setMapWindow() if mapIs3D setWindow(24, 153, 14, 140) else setWindow(24, 169, 14, 140) fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Show some faked-up data for player characters def fakeChars() setWindow3() rawDisplayStr("^LName^T065Life^T090Gun^L\n") rawDisplayStr("Black Bart^T06512^T0904\n") rawDisplayStr("Wyld Bill^T0658^T0902\n") rawDisplayStr("Lucy Lawls^T0659^T0906") if mapIs3D; copyWindow(); fin setWindow2() end /////////////////////////////////////////////////////////////////////////////////////////////////// // Load code and data, set up everything to display a 3D map def initMap(x, y, dir) word pFont word pMap // Set up the command table initCmds() // Reset memory (our module will stay since memory manager locked it upon load) mmgr(RESET_MEMORY, 0) // Load the font engine and its font mmgr(SET_MEM_TARGET, fontEngine) mmgr(QUEUE_LOAD, RES_NUM_FONT_ENGINE<<8 | RES_TYPE_CODE) mmgr(SET_MEM_TARGET, $9000) pFont = mmgr(QUEUE_LOAD, 1<<8 | RES_TYPE_FONT) // Queue loading of the raycaster or tile engine and the map data mmgr(SET_MEM_TARGET, displayEngine) if mapIs3D mmgr(QUEUE_LOAD, RES_NUM_RAYCASTER<<8 | RES_TYPE_CODE) pMap = mmgr(QUEUE_LOAD, mapNum<<8 | RES_TYPE_3D_MAP) auxMmgr(SET_MEM_TARGET, expandVec) auxMmgr(QUEUE_LOAD, RES_NUM_EXPAND_VEC<<8 | RES_TYPE_CODE) else mmgr(QUEUE_LOAD, RES_NUM_TILE_ENGINE<<8 | RES_TYPE_CODE) pMap = mmgr(QUEUE_LOAD, mapNum<<8 | RES_TYPE_2D_MAP) fin // Load everything that we just queued mmgr(FINISH_LOAD, 1) // 1 = keep open // Load the frame image (and lock it there) loadFrameImg() mmgr(FINISH_LOAD, 1) // 1 = keep open // Tell the font engine where to find its font setFont(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() initDisplay(mapNum, pMap, x, y, dir) needRender = FALSE // Display some fake character data. fakeChars() 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) x = x - triggerOriginX y = y - triggerOriginY 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 if !mapIs3D render() if textDrawn and mapIs3D; copyWindow(); fin needRender = FALSE else needRender = TRUE fin fin // If we're on a new map tile, clear text from script(s) on the old tile. if val >= 2 and textDrawn clearWindow() if textDrawn and mapIs3D; copyWindow(); fin 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() word x, y getPos(@x, @y) if y > 4 setDir(0) moveForward() else beep() fin end def moveEast() word x, y getPos(@x, @y) if x < totalMapWidth-5 setDir(4) moveForward() else beep() fin end def moveSouth() word x, y getPos(@x, @y) if y < totalMapHeight-5 setDir(8) moveForward() else beep() fin end def moveWest() word x, y getPos(@x, @y) if x > 4 setDir(12) moveForward() else beep() fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Switch to a new map (2D or 3D) and establish position on it def setMap(is3D, num, x, y, dir) if is3D == mapIs3D and num == mapNum setPos(x, y) setDir(dir) needRender = TRUE else flipToPage1() setWindow1(); clearWindow() displayChar('Y'-$40) // center mode displayStr("Traveling...") mapNameHash = 0 // because we replaced the title text displayChar('N'-$40) // normal mode setMapWindow(); clearWindow() setWindow2(); clearWindow() mapIs3D = is3D mapNum = num initMap(x, y, dir) fin // Don't check scripts, because we often land on an "Exit to wilderness?" script //NO:checkScripts() end /////////////////////////////////////////////////////////////////////////////////////////////////// def kbdTeleport() byte d3, num word x, y byte dir flipToPage1() ^$c053 if ^$25 < 23; ^$25 = 23; fin getPos(@x, @y) printf3("\nCurrent: X=%d Y=%d Facing=%d\n", x, y, getDir()) printf1("3D [%d]: ", mapIs3D) d3 = parseDecWithDefault(readStr(), mapIs3D) if d3 > 1; d3 = 1; fin printf1("Map [%d]: ", mapNum) num = parseDecWithDefault(readStr(), mapNum) printf1("X [%d] : ", x) x = parseDecWithDefault(readStr(), x) printf1("Y [%d] : ", y) y = parseDecWithDefault(readStr(), y) printf1("Facing [%d]: ", dir) dir = parseDecWithDefault(readStr(), dir) ^$c052 setMap(d3, num, x, y, dir) end /////////////////////////////////////////////////////////////////////////////////////////////////// def showPos() word x, y byte dir flipToPage1() ^$c053 if ^$25 < 23; ^$25 = 23; fin getPos(@x, @y) printf2("\nX=%d Y=%d ", x, y) if mapIs3D printf3("Facing=%d Sky=%d Ground=%d", getDir(), skyNum, groundNum) fin puts("\nHit any key.\n") getUpperKey() ^$c052 end /////////////////////////////////////////////////////////////////////////////////////////////////// def queue_setMap(is3D, num, x, y, dir) q_mapIs3D = is3D q_mapNum = num q_x = x q_y = y q_dir = dir end /////////////////////////////////////////////////////////////////////////////////////////////////// def queue_teleport(x, y, dir) queue_setMap(mapIs3D, mapNum, x, y, dir) 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 q_mapNum setMap(q_mapIs3D, q_mapNum, q_x, q_y, q_dir) q_mapNum = 0 fin if needRender render() if textDrawn and mapIs3D; copyWindow(); fin needRender = FALSE fin loop end /////////////////////////////////////////////////////////////////////////////////////////////////// // Set initial info for the scripts on this map: the name of the map, its trigger table, and the // maximum extent (width, height). This is called by the init function for the scripts. def setScriptInfo(mapName, trigTbl, wdt, hgt) word newNameHash // Grab the trigger table origins (used so the table can be more compact) triggerOriginX = trigTbl=>0 triggerOriginY = trigTbl=>2 // Record the trigger table pointer triggerTbl = trigTbl + 4 // Record the maximum width and height totalMapWidth = wdt totalMapHeight = hgt // Display map name newNameHash = hashString(mapName) if newNameHash <> mapNameHash setWindow1() clearWindow() displayChar('Y'-$40) // center mode displayStr(mapName) displayChar('N'-$40) // normal mode if mapIs3D; copyWindow(); fin mapNameHash = newNameHash fin // 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 flipToPage1() displayStr(str) end def scriptDisplayStrNL(str) scriptDisplayStr(str) displayStr("\n") 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() if textDrawn and mapIs3D; clearWindow(); fin textDrawn = FALSE return 0 fin beep() loop end /////////////////////////////////////////////////////////////////////////////////////////////////// // Display a portrait drawing (typically called from scripts) def setPortrait(portraitNum) word srcData byte cx, cy flipToPage1() // We're going to switch windows. Save the cursor pos in the text window. //FIXME: This is from old font engine, need to change for new eng. //cx = ^cursh //cy = ^cursv // Now clear out the map area setMapWindow() clearWindow() // Restore the cursor position setWindow2() //FIXME: This is from old font engine, need to change for new eng. //^cursh = cx //^cursv = cy // Load the portrait image and display it srcData = auxMmgr(QUEUE_LOAD, portraitNum<<8 | RES_TYPE_PORTRAIT) mmgr(FINISH_LOAD, 0) // 0 = close if mapIs3D blitPortrait(srcData, $2182) // start at 3rd text line else blitPortrait(srcData, $2202) // start at 4th text line fin needRender = FALSE // suppress display of map for this frame auxMmgr(FREE_MEMORY, srcData) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Clear the displayed portrait drawing def clrPortrait() render() if textDrawn and mapIs3D; copyWindow(); fin needRender = FALSE end /////////////////////////////////////////////////////////////////////////////////////////////////// // Test out portrait drawing def testPortrait() setPortrait(portraitNum) portraitNum = portraitNum + 1 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) initCmd('P', @showPos) initCmd('/', @testPortrait) // 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 mmgr(UNLOCK_MEMORY, $2000) mmgr(FREE_MEMORY, $2000) mmgr(SET_MEM_TARGET, $2000) mmgr(QUEUE_LOAD, 1<<8 | RES_TYPE_SCREEN) // title screen is fixed at #1 mmgr(LOCK_MEMORY, $2000) mmgr(FINISH_LOAD, 1) // 1 = keep open frameLoaded = 1 ^$C050 // graphics ^$C057 // hi-res ^$C054 // page 1 ^$C052 // full screen // 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 // Wait for a key. getUpperKey() 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 = @pushAuxStr // $306 callbacks.6 = $4c callbacks:7 = @scriptDisplayStr // $309 callbacks.9 = $4c callbacks:10 = @scriptDisplayStrNL // $30C callbacks.12 = $4c callbacks:13 = @getYN // $30F callbacks.15 = $4c callbacks:16 = @queue_setMap // $312 callbacks.18 = $4c callbacks:19 = @setSky // $315 callbacks.21 = $4c callbacks:22 = @setGround // $318 callbacks.24 = $4c callbacks:25 = @queue_teleport // $31B callbacks.27 = $4c callbacks:28 = @setPortrait // $31E callbacks.30 = $4c callbacks:31 = @clrPortrait // $321 callbacks.33 = $4c callbacks:34 = @moveBackward // $324 callbacks.36 = $4c callbacks:37 = @getUpperKey // $327 callbacks.39 = $4c callbacks:40 = @clearWindow end /////////////////////////////////////////////////////////////////////////////////////////////////// // Call like this: addToList(player + items, itemToAdd) def addToList(addTo, p) p=>p_nextObj = *addTo *addTo = p end /////////////////////////////////////////////////////////////////////////////////////////////////// // Call like this: removeFromList(player + items, itemToRemove) def removeFromList(pList, toRemove) word p p = *pList while p and p <> toRemove pList = p + p_nextObj p = *pList loop if p *pList = p=>p_nextObj p=>p_nextObj = NULL else fatal("InvalUnlink") fin end include "playtype.pla" //include "heaptest.pla" /////////////////////////////////////////////////////////////////////////////////////////////////// // Set up the small-object heap def initHeap() mmgr(SET_MEM_TARGET, heapStart) mmgr(REQUEST_MEMORY, heapSize) mmgr(LOCK_MEMORY, heapStart) mmgr(HEAP_SET, heapStart) addTypes() end /////////////////////////////////////////////////////////////////////////////////////////////////// // Main code. // initHeap() addToList(global + p_players, new_Player_Hue_Hauser()) addToList(global + p_enemyGroups, new_EnemyGroup_Dirt_Bags()) loadTitle() setCallbacks() // Start map/loc per Seth. Need to have this in a script in the futurecheckScripts() mapIs3D = 0 mapNum = 1 initMap(6, 123, 12) kbdLoop() done