/////////////////////////////////////////////////////////////////////////////////////////////////// // 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. /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // Fixed memory locations const seed = $4E // Incremented continuously by keyboard read routine const displayEngine = $6000 // main mem (raycaster and tile engine at same location) /////////////////////////////////////////////////////////////////////////////////////////////////// // Other constants const CHAR_WND_HEALTH_X = 112 const ANIM_PAUSE_MAX = 150 const CLOCK_ADV_2D_HOURS = 1 const CLOCK_ADV_2D_MINS = 0 const CLOCK_ADV_2D_SECS = 0 const CLOCK_ADV_3D_HOURS = 0 const CLOCK_ADV_3D_MINS = 0 const CLOCK_ADV_3D_SECS = 30 const CLOCK_ADV_COMBAT_HOURS = 0 const CLOCK_ADV_COMBAT_MINS = 10 const CLOCK_ADV_COMBAT_SECS = 0 // Raycaster tables - must match values in render.i const RAY_TABLE_START = $A800 const RAY_TABLE_SIZE = $1781 // Max gold const GOLD_MAX = 20000 include "globalDefs.plh" include "playtype.plh" include "gen_images.plh" include "gen_modules.plh" include "gen_enemies.plh" include "gen_players.plh" include "gen_items.plh" include "combat.plh" include "party.plh" include "store.plh" include "diskops.plh" include "godmode.plh" include "automap.plh" include "questlog.plh" include "story.plh" include "util3d.plh" /////////////////////////////////////////////////////////////////////////////////////////////////// // Data structures include "playtype.pla" export 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()#0 predef initCmds()#0 predef nextAnimFrame()#0 predef checkEncounter(x, y, force)#0 predef doCombat(mapCode, backUpOnFlee)#1 predef clearPortrait()#0 predef showMapName(mapName)#0 predef doRender()#0 predef pause(count)#1 predef printf1(str, arg1)#0 predef printf2(str, arg1, arg2)#0 predef playerDeath()#0 predef saveGame()#1 predef setStat(player, statName, val)#0 predef startGame(firstTime, ask)#0 predef showAnimFrame()#0 predef showParty()#0 predef textureControl(flg)#0 predef clearEncounterZones()#0 predef adjustPartyStat(statName, val)#0 predef printMem()#1 /////////////////////////////////////////////////////////////////////////////////////////////////// // Global variables export byte mapNum = -1 export byte mapIs3D = 0 export word totalMapWidth export word totalMapHeight export word pCurMap word mapNameHash = 0 export byte needRender = FALSE byte needShowParty = FALSE byte renderLoaded = FALSE byte texturesLoaded = FALSE byte textDrawn = FALSE byte anyInteraction = FALSE byte textClearCountdown = 0 byte forceRawScrDisp = FALSE export byte isPlural = 0 byte inScript = FALSE export byte isFloppyVer export byte isJace byte scriptModule = 0 byte prevScriptModule = 0 export word skyNum = 9 export word groundNum = 10 export byte portraitNum = 0 word triggerOriginX, triggerOriginY word triggerTbl byte cmdKey // last command key pressed word cmdTbl[96] // ASCII $00..$5F byte frameLoaded = 0 word curEngine = NULL word pModUtil3d = NULL word util3d = NULL export word pResourceIndex = NULL export word pGlobalTileset = NULL export byte curMapPartition = 0 export word typeHash = 0 export byte curHeapPct = 0 byte lastMoveDir = $FF // Queue setMap / teleport, since otherwise script might be replaced while executing byte q_mapIs3D = 0 byte q_mapNum = 1 word q_x = 0 word q_y = 0 byte q_dir = 0 // Queue of player death byte q_playerDeath = FALSE // Script tracking const MAX_MAP_SCRIPTS = 4 byte nMapScripts = 0 word mapScripts[MAX_MAP_SCRIPTS] // For decimal conversion and display tabbing byte decimalBuf[7] byte fontPosBuf[4] byte tabBuf[5] // Animation and lamp tracking word curPortrait = NULL byte curPortraitNum = 0 byte preEnginePortraitNum = 0 word curFullscreenImg = NULL byte animDirCt byte anyAnims = TRUE word animPauseCt byte showingLamp = TRUE export byte lampFrame = 0 export word lampDir = 1 byte storyMode = FALSE // Time and clock export byte prevClockColor, prevClockHour, prevClockMinute export byte nextSignificantMinute byte snoozeX0, snoozeX1, snoozeY word timeEventFunc byte mapEmuSound = 0 export byte[] S_GAME1_FILENAME = "GAME.1.SAVE" // Context for lambda functions (in lieu of closures, for now at least) export word ctx // Shared string constants export byte[] S_INTELLIGENCE = "Intelligence" export byte[] S_STRENGTH = "Strength" export byte[] S_AGILITY = "Agility" export byte[] S_STAMINA = "Stamina" export byte[] S_CHARISMA = "Charisma" export byte[] S_SPIRIT = "Spirit" export byte[] S_LUCK = "Luck" export byte[] S_HEALTH = "Health" export byte[] S_MAX_HEALTH = "Max health" export byte[] S_AIMING = "Aiming" export byte[] S_HAND_TO_HAND = "Hand-to-hand" export byte[] S_DODGING = "Dodging" export byte[] S_GOLD = "Gold" export byte[] S_TIME = "Time" export byte[] S_XP = "XP" export byte[] S_SP = "SP" export byte[] S_BANK_BAL = "Bank bal" export byte[] S_PACK_SIZE = "Pack size" export byte[] S_ENTER = "Enter" export byte[] S_LEAVE = "Leave" export byte[] S_USE = "Use" export byte[] S_HIS = "his" export byte[] S_HER = "her" export byte[] S_THEIR = "their" word lastTick = 0 export byte recordMode = 0 export word recordSeed export byte diskLimit = 0 export word playerUsing = NULL /////////////////////////////////////////////////////////////////////////////////////////////////// // 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" !source "../../include/sound.i" !source "../../include/prorwts.i" ; Optional debug printing support DEBUG = 0 ; General use tmp = $2 pTmp = $4 ysav = $34 ysav1 = $35 ; 16-bit random number seed - incremented by ROM kbd routine seed = $4E MAGIC = $7FED ; largest prime < $8000 ; NOTE ABOUT ABSOLUTE CODE ADDRESSING (e.g. STA .var, JMP .label, etc.) ; We cannot use it: this code will be preceded by stubs for the PLASMA routines, hence ; absolute addressing must be done carefully, adding ABS_OFFSET below. ; ; So don't JMP to labels, declare any variables as !byte or !word here, etc. without ; accounting for that. ; ; The way to account for that is to use ABS_OFFSET, and only within this module. This ; method works because this module is the only one loaded at an absolute address. ; ; See examples below. ABS_OFFSET = (_DEFCNT*5) - 13 ; 13 for plasma's module header (stripped by packer) end /////////////////////////////////////////////////////////////////////////////////////////////////// // After scriptDisplayStr is called, we clear the PLASMA string pool. That way, many long strings // can be used in a single function. export asm scriptDisplayStr(str)#0 lda .callScriptDisplay + ABS_OFFSET + 2 ; first time init? beq + .callScriptDisplay jsr 0 ; self-modified below lda framePtr sta outerFramePtr lda framePtr+1 sta outerFramePtr+1 rts + ; first-time init lda evalStkL,x sta .callScriptDisplay + ABS_OFFSET + 1 lda evalStkH,x sta .callScriptDisplay + ABS_OFFSET + 2 inx rts end /////////////////////////////////////////////////////////////////////////////////////////////////// // API to call rendering engine (same API for raycaster and tile engine) asm initDisplay(mapPartition, mapNum, pMapData, x, y, dir)#0 +asmPlasmNoRet 6 jmp $6000 end export asm flipToPage1()#0 +asmPlasmNoRet 0 jmp $6003 end export asm getPos(px, py)#0 +asmPlasmNoRet 2 jmp $6006 end asm setPos(x, y)#0 +asmPlasmNoRet 2 jmp $6009 end export asm getDir()#1 // returns: dir (0-15) +asmPlasmRet 0 jmp $600C end export asm setDir(dir)#0 +asmPlasmNoRet 1 jmp $600F end asm advance(nSteps)#1 // returns: 0 if same pos, 1 if new pos, 2 if new pos and scripted +asmPlasmRet 1 jmp $6012 end asm setColor(slot, color)#0 // params: slot (0=sky/1=ground), color (0-17) +asmPlasmNoRet 2 jmp $6015 end asm render(intrOnKbd)#0 +asmPlasmNoRet 1 jmp $6018 end asm _texControl(doLoad)#0 +asmPlasmNoRet 1 jmp $601B end asm getMapScript()#1 +asmPlasmRet 0 jmp $601E end asm setAvatar(tileNum)#0 // tile number (in the global tileset) +asmPlasmNoRet 1 jmp $6021 end asm copyTile(fromX, fromY, toX, toY)#0 +asmPlasmNoRet 4 jmp $6024 end /////////////////////////////////////////////////////////////////////////////////////////////////// // Memory copy - Non-overlapping regions only! export asm memcpy(pSrc, pDst, len, auxWr)#0 +asmPlasmNoRet 4 lda evalStkL+3,x ; source ptr sta tmp lda evalStkH+3,x sta tmp+1 lda evalStkL+2,x ; dest ptr sta pTmp lda evalStkH+2,x sta pTmp+1 ldy evalStkL,x ; auxWr sei ; prevent interrupts while possibly in aux sta clrAuxWr,y lda evalStkH+1,x ; len hi pha lda evalStkL+1,x ; len lo tax ldy #0 .pglup: pla sec sbc #1 bcc .part pha - lda (tmp),y sta (pTmp),y iny bne - inc tmp+1 inc pTmp+1 bne .pglup ; always taken .part: txa beq .done - lda (tmp),y sta (pTmp),y iny dex bne - .done sta clrAuxWr cli ; inerrupts ok now that we're back in main rts end /////////////////////////////////////////////////////////////////////////////////////////////////// export asm memset(pDst, val, len)#0 +asmPlasmNoRet 3 ldy #0 lda evalStkL+2,x ; dest ptr sta pTmp lda evalStkH+2,x sta pTmp+1 lda evalStkL+1,x ; value sta tmp lda evalStkL,x ; len lo pha lda evalStkH,x ; len hi beq + tax lda tmp - sta (pTmp),y iny bne - inc pTmp+1 dex bne - + pla beq + tax lda tmp - sta (pTmp),y iny dex bne - + rts end /////////////////////////////////////////////////////////////////////////////////////////////////// export asm readAuxByte(ptr)#1 +asmPlasmRet 1 sta pTmp sty pTmp+1 ldy #12 - lda .rdauxb-1+ABS_OFFSET, y sta $10-1,y dey bne - sei ; prevent interrupts while in aux mem sta setAuxRd jmp $10 .rdauxb lda (pTmp),y sta clrAuxRd cli rts end /////////////////////////////////////////////////////////////////////////////////////////////////// // String building for display with the font engine. Includes plurality processing to handily // handle things like "Dirt bag(s)" and "his/their" export asm buildString()#0 +asmPlasmNoRet 0 lda cswl sta prevCSWL+ABS_OFFSET lda cswh sta prevCSWL+1+ABS_OFFSET lda #addToString+ABS_OFFSET sta cswh lda #0 sta inbuf rts addToString: sty ysav1 inc inbuf ldy inbuf and #$7F sta inbuf,y ldy ysav1 rts prevCSWL !word 0 end // Complete string building (including plural processing), and return pointer // to the string (in the input buffer) export asm finishString(isPlural)#1 !zone { +asmPlasmRet 1 ldy prevCSWL+ABS_OFFSET ; put the cout vector back to default sty cswl ldy prevCSWL+1+ABS_OFFSET ; put the cout vector back to default sty cswh ldy #0 ; dest offset in Y (will be incremented before store) cpy inbuf beq .done1 ; failsafe: handle zero-length string tax ; test for isPlural == 0 beq + lda #$40 ; for setting V later + sta tmp ; save isPlural flag clv ; V flag for prev-is-alpha ldx #0 ; source offset in X (will be incremented before load) .fetch inx lda inbuf,x ; get next input char iny and #$7F ; clear hi-bit for final string, in case it gets interned sta inbuf,y ; by default copy the char to output ora #$80 cmp #"(" ; plural processing triggered by parentheses bne .notpar bvc .notpar ; but only parens directly after alpha char, e.g. preserving "Happy (and safe)." dey ; undo copy of the paren stx tmp+1 ; save position in input bit tmp ; set copy flag (V) initially to same as isPlural flag .findsl ; see if there's a slash within the parens inx cpx inbuf bcs .done ; failsafe: handle missing end-paren lda inbuf,x ora #$80 ; normalize hi-bit for comparisons below cmp #"/" bne + php pla eor #$40 ; flip V flag, meaning singular text is before slash, plural after. pha plp + cmp #")" ; scan until ending paren bne .findsl ; loop to scan next char ldx tmp+1 ; get back to start of parens ; copy mode flag is now in V: if slash present, single=copy, plural=nocopy ; if no slash: single=nocopy, plural=copy .plup inx lda inbuf,x ora #$80 cmp #"/" bne + php pla eor #$40 ; flip from copying to not-copying, or vice-versa pha plp bcs .plup ; always taken + cmp #")" beq .notpar ; stop at closing paren bvc .plup ; if not in copy mode, skip copy iny and #$7F ; clear hi-bit for final string, in case it gets interned sta inbuf,y ; else do copy bne .plup ; always taken .notpar bit fixedRTS; set prev-is-alpha flag cmp #"A" ; if >= ASCII "A", consider it alpha bcs .next clv ; clear prev-is-alpha flag .next cpx inbuf ; compare src offset to length bcc .fetch ; loop while less than .done sty inbuf ; save new length .done1 lda #inbuf rts } end /////////////////////////////////////////////////////////////////////////////////////////////////// export asm blit(isAux, srcData, dstScreenPtr, nLines, lineSize)#0 +asmPlasmNoRet 5 ; Save line size sta ysav ; Save nLines lda evalStkL+1,x pha ; Save the dest pointer lda evalStkL+2,x sta pTmp lda evalStkH+2,x sta pTmp+1 ; Save the source pointer lda evalStkL+3,x sta tmp lda evalStkH+3,x sta tmp+1 ; Save aux/main flag lda evalStkL+4,x lsr ; to carry bit bcc + ldy #15 ; put aux copy routine in zero page - ldx .cpaux + ABS_OFFSET,y stx $10,y dey bpl - + pla ; get line count tax -- ldy ysav ; get byte count dey bcc + jsr $10 ; copy pixel bytes (aux version) bcs ++ + - lda (tmp),y sta (pTmp),y dey bpl - ++ php lda tmp ; advance to next row of data clc adc ysav sta tmp bcc + inc tmp+1 + jsr NextScreenLine ; and next screen line plp dex bne -- ; Loop until we've done all rows. rts .cpaux sei ; avoid interrupts while reading aux sta setAuxRd - lda (tmp),y sta (pTmp),y dey bpl - sta clrAuxRd cli rts end /////////////////////////////////////////////////////////////////////////////////////////////////// asm vline(dstScreenPtr, val, nLines)#0 +asmPlasmNoRet 3 ; Save number of lines pha ; Save value lda evalStkL+1,x sta tmp ; Save the dest pointer lda evalStkL+2,x sta pTmp lda evalStkH+2,x sta pTmp+1 pla ; line count tax - ldy #0 lda tmp sta (pTmp),y jsr NextScreenLine dex bne - rts end /////////////////////////////////////////////////////////////////////////////////////////////////// // Simply retrieve the X register. Used to double-check that we're not leaking PLASMA eval // stack entries. asm getXReg()#1 +asmPlasmRet 0 txa ldy #0 rts end /////////////////////////////////////////////////////////////////////////////////////////////////// // Calculate 16-bit hash of a buffer. Note: ignores anything beyond 256 bytes. export asm hashBuffer(ptr, len)#1 +asmPlasmRet 2 lda evalStkL+1,x ; first arg is buffer pointer sta pTmp lda evalStkH+1,x sta pTmp+1 lda evalStkL,x ; second arg is length tax ldy #0 sty tmp tya - clc adc (pTmp),y bcc + inc tmp + lsr ror tmp bcc + ora #$80 + lsr ror tmp bcc + ora #$80 + iny dex bne - ldy tmp rts end /////////////////////////////////////////////////////////////////////////////////////////////////// // Print a string to the current character output vector export asm puts(str)#0 +asmPlasmNoRet 1 sta pTmp lda #'!' ldx #1 tya beq + ; safety: print '!' instead of null string sty pTmp+1 ldy #0 lda (pTmp),y tax beq ++ ; handle empty string iny - lda (pTmp),y + ora #$80 +safeCout iny dex bne - ++ rts end /////////////////////////////////////////////////////////////////////////////////////////////////// // Get a character from the keyboard export asm rdkey()#1 +asmPlasmRet 0 +safeRdkey ldy #0 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(str, pos)#1 !zone { +asmPlasmRet 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 + +safeCout 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 export asm printHex(num)#0 +asmPlasmNoRet 1 pha tya +safePrbyte pla jmp _safePrbyte end /////////////////////////////////////////////////////////////////////////////////////////////////// // Print a single character export asm printChar(ch)#0 +asmPlasmNoRet 1 ora #$80 jmp _safeCout end /////////////////////////////////////////////////////////////////////////////////////////////////// // Print a carriage return export asm crout()#0 +asmPlasmNoRet 0 lda #$8D jmp _safeCout end /////////////////////////////////////////////////////////////////////////////////////////////////// // Send a command to the memory manager export asm mmgr(cmd, wordParam)#1 +asmPlasmRet 2 jsr .setmmgr+ABS_OFFSET jsr mainLoader ; ret value in X=lo/Y=hi txa ; to A=lo/Y=hi for asmPlasm rts .setmmgr 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 rts end // Aux version of memory manager command export asm auxMmgr(cmd, wordParam)#1 +asmPlasmRet 2 jsr .setmmgr+ABS_OFFSET jsr auxLoader ; ret value in X=lo/Y=hi txa ; to A=lo/Y=hi for asmPlasm rts end /////////////////////////////////////////////////////////////////////////////////////////////////// // Get mem mgr's flag telling if this is the floppy version or not asm getFloppyFlg()#1 +asmPlasmRet 0 lda floppyFlg ldy #0 rts end /////////////////////////////////////////////////////////////////////////////////////////////////// // Generate a sound on the Apple II speaker export asm genSound(dnnoise, dnvelo, dndelay, upnoise, upvelo, updelay, dur)#0 +asmPlasmNoRet 7 jmp genSound end /////////////////////////////////////////////////////////////////////////////////////////////////// // Use the font engine to clear the current text window. // Parameters: top, bottom, left, right export asm setWindow(top, bottom, left, right)#0 +asmPlasmNoRet 4 jmp SetWindow end /////////////////////////////////////////////////////////////////////////////////////////////////// // Get the font engine's current text window // Returns: top, bottom, left, right export asm getWindow()#4 bit setLcRW+lcBank2 jmp GetWindow end /////////////////////////////////////////////////////////////////////////////////////////////////// // Get the cursor position - returns X, Y export asm getCursor()#2 bit setLcRW+lcBank2 jmp GetCursor end /////////////////////////////////////////////////////////////////////////////////////////////////// // Read a keyboard string up to 40 chars using the Font Engine. Returns a zero-terminated string // in the input buffer at $200, with the length stored in $2FF. asm rawGetStr()#1 +asmPlasmRet 0 jmp GetStr end /////////////////////////////////////////////////////////////////////////////////////////////////// // Use the font engine to clear the current text window export asm clearWindow()#0 +asmPlasmNoRet 0 jmp ClearWindow end /////////////////////////////////////////////////////////////////////////////////////////////////// // Use the font engine to copy the current text window to hi-res page 2 (or vice-versa) // If flip=0, copies page 1 to page 2 // If flip=$60, copies page 2 to page 1 export asm copyWindow(flip)#0 +asmPlasmNoRet 1 tay jmp CopyWindow end /////////////////////////////////////////////////////////////////////////////////////////////////// // Display a character using the font engine. export asm displayChar(ch)#0 +asmPlasmNoRet 1 jmp DisplayChar end /////////////////////////////////////////////////////////////////////////////////////////////////// // Display a string using the font engine. asm internalDisplayStr(str)#0 +asmPlasmNoRet 1 jmp DisplayStr end /////////////////////////////////////////////////////////////////////////////////////////////////// // Calculate string width using the font engine. export asm calcWidth(pStr)#1 +asmPlasmRet 1 jmp CalcWidth end /////////////////////////////////////////////////////////////////////////////////////////////////// // Get the address of the given hi-res screen line export asm getScreenLine(n)#1 +asmPlasmRet 1 jmp GetScreenLine end /////////////////////////////////////////////////////////////////////////////////////////////////// // Display a string using the font engine but not its parser. Also, interpret "^A" as ctrl chars. export asm rawDisplayStr(pStr)#0 +asmPlasmNoRet 1 sta pTmp sty pTmp+1 ldy #0 lda (pTmp),y sta tmp - cpy tmp bcs ++ iny lda (pTmp),y ora #$80 cmp #"^" bne + iny lda (pTmp),y cmp #"^" beq + and #$1F ora #$80 + sty tmp+1 jsr DisplayChar ldy tmp+1 bne - ; always taken ++rts end /////////////////////////////////////////////////////////////////////////////////////////////////// // Random number generator. A standard linear congruential prng featuring a full cycle (i.e. // every value from 1..MAGIC-1 is part of the sequence). asm internal_rand16()#1 !zone { +asmPlasmRet 0 ldx #6 ; start check = 1, plus 5 loops to mul by 32 = 6 total ldy seed lda seed+1 and #$7F ; in case kbd routine has advanced past MAGIC sta seed+1 bne .chk ; zero check - hi byte tya bne .chk ; zero check - lo byte lda #1 ; force 0 seed to go to 1 bne .ret ; always taken .shift asl tay rol seed+1 .chk cpy #MAGIC bcc .next sta seed+1 tya sbc #proRWTS sta $46 jsr $44 lda status ldy #0 bit setLcRW+lcBank2 bit setLcRW+lcBank2 sta clrAuxZP cli ; interrupts ok again rts end /////////////////////////////////////////////////////////////////////////////////////////////////// // The following only used for speed testing //asm readNoSlotClock(dstBuf)#0 // param: dstBuf (will receive 8 BCD bytes) //!zone { // +asmPlasmNoRet 1 // ; record dst ptr // sta tmp // sty tmp+1 // ; obtain a pointer to our little table of magic values // lda #$60 // sta pTmp //.base = *+2 // jsr pTmp // tsx // dex // dex // txs // pla // sta pTmp // pla // sta pTmp+1 // ldy #<(.tbl - .base) // ; record state of slot ROM, then turn on C3 ROM // sei // lda $CFFF // pha // sta $C300 // lda $C304 // ldx #8 //.wr1: // lda (pTmp),y // sec // ror //.wr2: // bcs + // bit $C300 // bcc ++ //+ bit $C301 //++ lsr // bne .wr2 // iny // dex // bne .wr1 // ldy #7 //.rd1: // ldx #8 //.rd2: // lda $C304 // lsr // ror pTmp // dex // bne .rd2 // lda pTmp // sta (tmp),y // dey // bpl .rd1 // ; restore slot ROM state // pla // bmi + // sta $CFFF //+ cli // rts //.tbl !byte $C5,$3A,$A3,$5C,$C5,$3A,$A3,$5C //} ; end zone //end // //def getTick() // byte timeBuf[8] // word tick // readNoSlotClock(@timeBuf) // tick = (timeBuf[7] & $F) + ((timeBuf[7] >> 4) * 10) // tick = tick + ((timeBuf[6] & $F) * 100) + ((timeBuf[6] >> 4) * 1000) // return tick + (((timeBuf[5] & $F) % 5) * 6000) //end // //def tickDiff(tStart, tEnd) // word diff // diff = tEnd - tStart // if diff >= 0; return diff; fin // return diff + 30000 //end // //def prtick(str)#0 // if lastTick // printf2("%s:%d ", str, tickDiff(lastTick, getTick())) // fin // lastTick = getTick() //end /////////////////////////////////////////////////////////////////////////////////////////////////// // General methods /////////////////////////////////////////////////////////////////////////////////////////////////// // Generate a sound that indicates being blocked or failing export def beep()#0 genSound(0, 600, 60200, 0, 0, 60200, 30) end export def rand16()#1 word result if recordMode *seed = recordSeed //printf1("%x:", recordSeed) result = internal_rand16() recordSeed = *seed //printf1("%x ", recordSeed) return result fin return internal_rand16() end /////////////////////////////////////////////////////////////////////////////////////////////////// // Fatal error: print message and stop the system. // Doesn't really return a value, but can be handy for chaining. export def fatal(msg)#1 return mmgr(FATAL_ERROR, msg) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Return the max of two signed 16-bit numbers export def max(a, b)#1 return a > b ?? a :: b end /////////////////////////////////////////////////////////////////////////////////////////////////// // Return the min of two signed 16-bit numbers export def min(a, b)#1 return a < b ?? a :: b end /////////////////////////////////////////////////////////////////////////////////////////////////// // Return the absolute value of a number export def abs(n)#1 return n < 0 ?? -n :: n end /////////////////////////////////////////////////////////////////////////////////////////////////// // Convert a lower-case character to upper-case (or return unchanged if it's not lower-case) export def charToUpper(c)#1 return (c >= 'a' and c <= 'z') ?? (c - $20) :: c end /////////////////////////////////////////////////////////////////////////////////////////////////// def recordChar(ch)#0 if ch < $20 printChar('^') ch = ch + $40 fin printChar(ch) end /////////////////////////////////////////////////////////////////////////////////////////////////// def recordDisplay(str)#0 word i puts("DISP:") for i = 1 to ^str recordChar(^(str + i) & $7F) next crout end /////////////////////////////////////////////////////////////////////////////////////////////////// export def displayStr(str)#0 if recordMode; recordDisplay(str); fin internalDisplayStr(str) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Read a string from the keyboard using the font manager, and intern it to the heap. export def getStringResponse()#1 word p rawGetStr() rawDisplayStr("\n") // so Outlaw user doesn't have to remember to make a newline if recordMode puts("STRING:"); puts($200); crout fin return mmgr(HEAP_INTERN, $200) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Convert signed decimal to string in decimalBuf (@decimalBuf returned) def convertDec(n)#1 word n0 word p p = @decimalBuf + 1 if n < 0; ^p = '-'; p=p+1; n = -n; fin n0 = n if n0 > 9999; ^p = '0' + n/10000; p=p+1; n = n%10000; fin if n0 > 999; ^p = '0' + n/1000; p=p+1; n = n%1000; fin if n0 > 99; ^p = '0' + n/100; p=p+1; n = n%100; fin if n0 > 9; ^p = '0' + n/10; p=p+1; n = n%10; fin ^p = '0' + n; p=p+1 decimalBuf[0] = p - @decimalBuf - 1 // record final length of string return @decimalBuf end /////////////////////////////////////////////////////////////////////////////////////////////////// // Convert byte to 3-char string in decimalBuf, suitable for font engine, e.g. ^T065 // (@fontPosBuf returned so as to not disturb decimalBuf) def convert3Dec(n)#1 fontPosBuf[0] = 3 fontPosBuf[1] = '0' + (n / 100); n = n%100 fontPosBuf[2] = '0' + (n / 10); n = n%10 fontPosBuf[3] = '0' + n return @fontPosBuf end /////////////////////////////////////////////////////////////////////////////////////////////////// // Print a formatted string a'la C printf, with up to three parameters. export def printf3(str, arg1, arg2, arg3)#0 word pos word curArg word p if !str printChar('!') // Safety valve for NULL string pointer return fin pos = 0 curArg = @arg1 while TRUE pos = partialPrintf(str, pos) if pos < 0 break fin p = str + pos + 2 when ^p is 'd' // %d = decimal puts(convertDec(*curArg)); break is 's' // %s = string puts(*curArg); break is 'D' // %D = 3-char decimal suitable for font engine ctrl codes puts(convert3Dec(*curArg)); break is 'c' // %c = character printChar(*curArg); break is 'x' // %x = hex with '$' printHex(*curArg); break is '%' // %% = perfect printChar('%'); break otherwise printHex(^p); fatal("Unknown % code") wend curArg = curArg + 2 pos = pos + 2 loop end export def printf1(str, arg1)#0; printf3(str, arg1, 0, 0); end export def printf2(str, arg1, arg2)#0; printf3(str, arg1, arg2, 0); end // Like printf, but displays text using font engine export def displayf3(str, arg1, arg2, arg3)#0 buildString() printf3(str, arg1, arg2, arg3) displayStr(finishString(isPlural)) end export def displayf1(str, arg1)#0; displayf3(str, arg1, 0, 0); end export def displayf2(str, arg1, arg2)#0; displayf3(str, arg1, arg2, 0); end // Like printf, but buffers string in $200 export def sprintf3(str, arg1, arg2, arg3)#1 buildString() printf3(str, arg1, arg2, arg3) return finishString(isPlural) end export def sprintf1(str, arg1); return sprintf3(str, arg1, 0, 0); end export def sprintf2(str, arg1, arg2); return sprintf3(str, arg1, arg2, 0); end // Like printf, but displays text using font engine export def rawDisplayf1(str, arg1)#0; rawDisplayStr(sprintf3(str, arg1, 0, 0)); end export def rawDisplayf2(str, arg1, arg2)#0; rawDisplayStr(sprintf3(str, arg1, arg2, 0)); end export def rawDisplayf3(str, arg1, arg2, arg3)#0; rawDisplayStr(sprintf3(str, arg1, arg2, arg3)); end /////////////////////////////////////////////////////////////////////////////////////////////////// // Set the cursor position in the font engine export def setCursor(x, y)#0 rawDisplayf2("^V%D^T%D", y, x) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def parseDec(str)#1 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 /////////////////////////////////////////////////////////////////////////////////////////////////// // Used in record mode to print out a keystroke. Also handles caret -> ctrl translation during // playback of recordings. export def recordKey()#1 byte key key = ^kbd & $7F ^kbdStrobe if recordMode if key == '^' while ^kbd < 128; loop key = (^kbd & $7F) - $40 ^kbdStrobe fin puts("KEY:") recordChar(key) puts(" SEED:$") printHex(recordSeed) crout fin return key end /////////////////////////////////////////////////////////////////////////////////////////////////// // Get a keystroke and convert it to upper case export def getUpperKey()#1 byte key // Make sure same text displays on both hi-res while animating if anyAnims and textDrawn and mapIs3D and texturesLoaded copyWindow(0) fin // Now wait for a key, and animate while doing so. while ^kbd < 128 if recordMode pause(30000) else // pause() will terminate on keypress, returning the count it did *seed = *seed + pause(30000) fin loop key = recordKey return charToUpper(key) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Read a single char from the keyboard, and intern it (as a string) to the heap. export def getCharResponse()#1 if needShowParty; showParty(); fin ^$200 = 1 ^$201 = getUpperKey() return mmgr(HEAP_INTERN, $200) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Select the num'th entry in the list for which the selector returns TRUE. A NULL selector // is considered to always be TRUE. export def select(p, sel, num)#1 while p if !sel if !num; break; fin num-- elsif sel(p) if !num; break; fin num-- fin p = p=>p_nextObj loop return p end /////////////////////////////////////////////////////////////////////////////////////////////////// export def first(p, sel)#1 return select(p, sel, 0) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def index(p, num)#1 return select(p, NULL, num) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def sum(p, sel, func)#1 word sum sum = 0 while p if !sel sum = sum + func(p) elsif sel(p) sum = sum + func(p) fin p = p=>p_nextObj loop return sum end /////////////////////////////////////////////////////////////////////////////////////////////////// export def forSome(p, sel, do)#0 while p if !sel do(p)#0 elsif sel(p) do(p)#0 fin p = p=>p_nextObj loop end /////////////////////////////////////////////////////////////////////////////////////////////////// export def forEach(p, do)#0 forSome(p, NULL, do) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Pause for a specified count period, advancing the animation periodically. Terminates early // if a key is pressed. In either case, returns the number of counts waited. export def pause(count)#1 word i, prevSeed if recordMode; prevSeed = recordSeed; fin for i = 0 to count if ^kbd >= 128; break; fin animPauseCt-- if animPauseCt < 0 nextAnimFrame() // also resets animPauseCt fin next if recordMode; recordSeed = prevSeed; fin return i end /////////////////////////////////////////////////////////////////////////////////////////////////// // Calculate markup on a price, where ratio is an 8.8 fixed-point number. Some approx ratios: // $0000 = 0% // $0026 = 15% // $0080 = 50% // $0100 = 100% // $0180 = 150% export def addRatio(start, ratio)#1 word markup markup = 0 while ratio > 255 markup = markup + start ratio = ratio - 256 loop return markup + mulRatio(start, ratio) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def percentToRatio(pct)#1 return addRatio(pct, 656) // Scale 0..100 to 0..255 end /////////////////////////////////////////////////////////////////////////////////////////////////// export def addPercent(start, pct)#1 return addRatio(start, percentToRatio(pct)) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def encodeDice(nDice, dieSize, add)#1 // ndice=0..15, dieSize=0..15, add=0..255 return (nDice << 12) | (dieSize << 8) | add end /////////////////////////////////////////////////////////////////////////////////////////////////// export def rollDice(encoded)#1 byte nDice, dieSize word result nDice = (encoded >> 12) & $F // must mask off replicated hi-bits dieSize = (encoded >> 8) & $F result = encoded & $FF // initial add while nDice result = result + (rand16() % dieSize) + 1 nDice-- loop return result end /////////////////////////////////////////////////////////////////////////////////////////////////// // Look up the partition for a resource. // sectioNum: 0=version num (do not use here), 1=map2d, 2=map3d, 3=portrait, 4=story export def lookupResourcePart(sectionNum, resourceNum)#1 word ptr byte n, i // Special case: main frame img laods before resource index if !pResourceIndex; return 1; fin // Skip to the requested section (starting just after version num) ptr = pResourceIndex while sectionNum > 0 ptr = ptr + readAuxByte(ptr) + 1 sectionNum-- loop // And grab the number from that section's table n = readAuxByte(ptr) if resourceNum > n; fatal("lkup1"); fin n = readAuxByte(ptr + resourceNum) // If resource is on the current map's disk, prefer that if n & (1<<(curMapPartition-1)) return curMapPartition fin // Otherwise return the first disk it's on for i = 0 to 7 if n & (1< skyNum skyNum = num setColor(0, skyNum) needRender = TRUE showingLamp = mapIs3D and texturesLoaded and (skyNum == 0 or skyNum == 8) if showingLamp and util3d; util3d=>util3d_nextLampFrame(); fin // to update image fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Set the ground color (relevant to 3D display only) export def setGround(num)#0 if num <> groundNum groundNum = num setColor(1, groundNum) needRender = TRUE fin end /////////////////////////////////////////////////////////////////////////////////////////////////// export def heapCollect()#0 word pct mmgr(CHECK_MEM, 0) global=>w_heapSize = mmgr(HEAP_COLLECT, 0) - HEAP_BOTTOM curHeapPct = min(99, max(0, ((global=>w_heapSize / 10) * 100) / (HEAP_SIZE / 10))) end /////////////////////////////////////////////////////////////////////////////////////////////////// def resetAnimPause()#0 anyAnims = TRUE // for now; might get cleared if we discover otherwise on advance animDirCt = 1 animPauseCt = ANIM_PAUSE_MAX showingLamp = mapIs3D and texturesLoaded and (skyNum == 0 or skyNum == 8) lampFrame = 0 lampDir = 1 end /////////////////////////////////////////////////////////////////////////////////////////////////// // Load the Frame Image, and lock it. export def loadFrameImg(img)#0 // Skip redundant reload if frameLoaded == img; return; fin // Free prev img and/or portrait (if any) if curFullscreenImg // must clear img directly, to avoid loading main frame img only to replace it auxMmgr(FREE_MEMORY, curFullscreenImg) curFullscreenImg = NULL fin clearPortrait() // Make room in aux mem by throwing out textures textureControl(FALSE) // Load the image data into aux mem if img auxMmgr(START_LOAD, lookupResourcePart(4, img)) if img == 1 auxMmgr(SET_MEM_TARGET, $4000) // well above where expander loads at startup fin curFullscreenImg = auxMmgr(QUEUE_LOAD, img<<8 | RES_TYPE_SCREEN) auxMmgr(FINISH_LOAD, 0) resetAnimPause() // And show the first frame of the screen image ^EMUSIG_FULL_COLOR showAnimFrame() else curFullscreenImg = NULL anyAnims = FALSE fin frameLoaded = img // Do not render over the image needRender = FALSE end /////////////////////////////////////////////////////////////////////////////////////////////////// // Window for the map name bar export def setWindow1()#0 setWindow(8, 17, 35, 119) // Top, Bottom, Left, Right mapNameHash = 0 // on the assumption that it's being set because somebody's going to print there end /////////////////////////////////////////////////////////////////////////////////////////////////// // Window for the large upper right bar export def setWindow2()#0 setWindow(24, 132, 154, 267) // Top, Bottom, Left, Right displayChar('N'-$40) // Set normal mode - clear all special modes (like underline, etc.) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Window for the mid-size lower right bar export def setWindow3()#0 setWindow(144, 181, 154, 267) // Top, Bottom, Left, Right end /////////////////////////////////////////////////////////////////////////////////////////////////// // Clear map window, then set a text area within it. export def useMapWindow()#0 byte bottom1, bottom2 if frameLoaded == 3 // don't check mapIs3D, since we might be in an engine bottom1 = 154; bottom2 = 150 else bottom1 = 169; bottom2 = 168 fin setWindow(24, bottom1, 14, 140) // Top, Bottom, Left, Right clearWindow() setWindow(24, bottom2, 14, 140) // Top, Bottom, Left, Right end /////////////////////////////////////////////////////////////////////////////////////////////////// def clearTextWindow()#0 if textDrawn or textClearCountdown setWindow2(); clearWindow() if mapIs3D and texturesLoaded; copyWindow(0); fin textDrawn = FALSE textClearCountdown = 0 snoozeX1 = -1 fin end /////////////////////////////////////////////////////////////////////////////////////////////////// export def soundPlayEmu(numAndFlgs)#0 if mapEmuSound == $FF // if in initMap... mapEmuSound = numAndFlgs // ... then record the music for resume after combat fin ^EMUSOUND_PLAY = numAndFlgs end /////////////////////////////////////////////////////////////////////////////////////////////////// def hline(addr, startByte, midByte, midSize, endByte)#0 ^addr = startByte memset(addr+1, midByte, midSize) ^(addr+midSize+1) = endByte end /////////////////////////////////////////////////////////////////////////////////////////////////// // Window that covers the entire inner area (destroys frame image, so be sure to loadMainFrame // afterwards) export def setBigWindow()#0 // Draw border (if not already drawn) if frameLoaded hline(getScreenLine(BIGWIN_TOP-4)+1, 0, 0, 36, 0) hline(getScreenLine(BIGWIN_TOP-3)+1, $F8, $FF, 36, $8F) hline(getScreenLine(BIGWIN_TOP-2)+1, $FC, $FF, 36, $9F) hline(getScreenLine(BIGWIN_TOP-1)+1, $8C, 0, 36, $98) vline(getScreenLine(BIGWIN_TOP)+1, $8C, 174) vline(getScreenLine(BIGWIN_TOP)+38, $98, 174) hline(getScreenLine(BIGWIN_BOTTOM-1)+1, $8C, 0, 36, $98) hline(getScreenLine(BIGWIN_BOTTOM)+1, $FC, $FF, 36, $9F) hline(getScreenLine(BIGWIN_BOTTOM+1)+1, $F8, $FF, 36, $8F) hline(getScreenLine(BIGWIN_BOTTOM+2)+1, 0, 0, 36, 0) frameLoaded = 0 // since we just destroyed it textDrawn = FALSE textClearCountdown = 0 ^EMUSIG_FULL_TEXT fin setWindow(BIGWIN_TOP, BIGWIN_BOTTOM, BIGWIN_LEFT, BIGWIN_RIGHT) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def rightJustifyStr(str, rightX)#0 word space space = rightX - calcWidth(str) if (space > 0) rawDisplayStr("^T") // do not use printf variants, since it might overwrite str rawDisplayStr(convert3Dec(space)) fin rawDisplayStr(str) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def rightJustifyNum(num, rightX)#0 rightJustifyStr(convertDec(num), rightX) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def centerStr(str, windowWidth)#0 word x x = (windowWidth - calcWidth(str)) >> 1 if (x >= 0) rawDisplayStr("^T") // do not use printf variants, since it might overwrite str rawDisplayStr(convert3Dec(x)) fin rawDisplayStr(str) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Display the party data on the screen def showPartyLine(p)#0 if p <> global=>p_players; displayChar('\n'); fin if p->b_skillPoints; rawDisplayStr("^I"); fin // inverse for chars needing lvl-up rawDisplayf1("%s^N", p=>s_name) rightJustifyStr(sprintf2("%d/%d", p=>w_health, p=>w_maxHealth), CHAR_WND_HEALTH_X) end export def showParty()#0 word p, cursX, cursY cursX, cursY = getCursor() setWindow3() clearWindow() // Display header (or LEVEL UP message if any player has un-applied skill pts) if first(global=>p_players, &(p) p->b_skillPoints) rawDisplayStr("^Y^I LEVEL U)P ^N\n") else rawDisplayStr("^LName") rightJustifyStr(@S_HEALTH, CHAR_WND_HEALTH_X) // begin underline mode rawDisplayStr("^N\n") fin // Display each character's name and health forEach(global=>p_players, @showPartyLine) // Finish up if mapIs3D and texturesLoaded; copyWindow(0); fin setWindow2() setCursor(cursX, cursY) needShowParty = FALSE end /////////////////////////////////////////////////////////////////////////////////////////////////// def getArgCount(pFunc)#1 word pBytecode // skip over JMP to plasma interp, get addr in aux mem pBytecode = pFunc=>3 // Check if the function starts with ENTER op if readAuxByte(pBytecode) == $58 return readAuxByte(pBytecode+2) fin // Zero-arg functions sometimes omit ENTER altogether. return 0 end /////////////////////////////////////////////////////////////////////////////////////////////////// def setTextCountdown()#0 textClearCountdown = 3 if mapIs3D and texturesLoaded copyWindow(0) fin textDrawn = FALSE end /////////////////////////////////////////////////////////////////////////////////////////////////// // Send an event to the scripts on the current map square export def scriptEvent(event, param)#0 byte i, argCount word script if !nMapScripts or q_playerDeath; return; fin if inScript; return; fin // avoid doing scripted events inside other scripts inScript = TRUE setWindow2() textDrawn = FALSE for i = 0 to nMapScripts-1 script = mapScripts[i] argCount = getArgCount(script) if argCount == 0 and event == @S_ENTER // zero-param scripts are assumed to be strictly 'enter' handlers script() elsif argCount == 1 script(event) elsif argCount == 2 script(event, param) fin next inScript = FALSE if textDrawn; setTextCountdown; fin clearPortrait() if needShowParty; showParty(); fin if global=>p_players=>w_health == 0; q_playerDeath = TRUE; fin end /////////////////////////////////////////////////////////////////////////////////////////////////// export def loadMainFrameImg()#0 if curFullscreenImg auxMmgr(FREE_MEMORY, curFullscreenImg) curFullscreenImg = NULL fin loadFrameImg(mapIs3D+2) if mapIs3D ^EMUSIG_3D_MAP else ^EMUSIG_2D_MAP fin if curFullscreenImg auxMmgr(FREE_MEMORY, curFullscreenImg) // we don't allow animated main frames, so save memory curFullscreenImg = NULL fin end /////////////////////////////////////////////////////////////////////////////////////////////////// def loadUtil3d()#0 if !pModUtil3d and mapIs3D mmgr(START_LOAD, 1) // code is in partition 1 pModUtil3d = mmgr(QUEUE_LOAD, MOD_UTIL3D<<8 | RES_TYPE_MODULE) mmgr(FINISH_LOAD, 0) util3d = pModUtil3d() initCmds() // rebuild the command table with new function ptrs fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Load code and data, set up everything to display a 2D or 3D map def initMap(x, y, dir)#0 //AUTOMAP_CHECK// word pDiskOps // If we have a renderer loaded, let it know to flush automap marks textureControl(FALSE) // Reset memory (our module will stay since memory manager locked it upon load) mmgr(RESET_MEMORY, 0) // Reset all pointers to non-locked memory, since the stuff might go away pModUtil3d = NULL util3d = NULL pCurMap = NULL curEngine = NULL pGlobalTileset = NULL curPortrait = NULL curPortraitNum = 0 curFullscreenImg = NULL renderLoaded = FALSE // leave it this way until all scripts done, else scriptDisplayStr renders // Load the frame image, then raycaster or tile engine loadMainFrameImg() mmgr(START_LOAD, 1) mmgr(SET_MEM_TARGET, displayEngine) if mapIs3D mmgr(QUEUE_LOAD, CODE_RENDER<<8 | RES_TYPE_CODE) // Reserve memory for the raycaster's tables mmgr(SET_MEM_TARGET, RAY_TABLE_START) mmgr(REQUEST_MEMORY, RAY_TABLE_SIZE) else mmgr(QUEUE_LOAD, CODE_TILE_ENGINE<<8 | RES_TYPE_CODE) fin pGlobalTileset = mmgr(QUEUE_LOAD, 1<<8 | RES_TYPE_TILESET) // even in 3d, need tiles for lamp/etc. //AUTOMAP_CHECK// pDiskOps = mmgr(QUEUE_LOAD, MOD_DISKOPS<<8 | RES_TYPE_MODULE) mmgr(FINISH_LOAD, 0) if mapIs3D; loadUtil3d(); fin //AUTOMAP_CHECK// pDiskOps()=>diskops_checkAutomap() //AUTOMAP_CHECK// mmgr(FREE_MEMORY, pDiskOps) // Set up the command table initCmds() // must be after loading util3d // Load the map curMapPartition = lookupResourcePart(mapIs3D+1, mapNum) if !curMapPartition; fatal("lkup2"); fin mmgr(START_LOAD, curMapPartition) pCurMap = mmgr(QUEUE_LOAD, mapNum<<8 | (RES_TYPE_2D_MAP+mapIs3D)) mmgr(FINISH_LOAD, 0) // Clear all the windows to the background color (hi-bit set) // except window3 because showParty does it for us setWindow1(); clearWindow() setWindow2(); clearWindow() // Clear the list of encounter zones from any previous maps clearEncounterZones // 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() skyNum = 9 // default groundNum = 10 // default timeEventFunc = NULL mapEmuSound = $FF // special value to mark init initDisplay(curMapPartition, mapNum, pCurMap, x, y, dir) if mapEmuSound == $FF // if no music assigned... mapEmuSound = 0 // ...then record that fact. fin texturesLoaded = TRUE needRender = FALSE textDrawn = FALSE textClearCountdown = 0 curEngine = NULL curPortrait = NULL curPortraitNum = 0 curFullscreenImg = NULL prevClockColor = 99 snoozeX1 = -1 if timeEventFunc; timeEventFunc(global->b_hour); fin if mapIs3D if util3d util3d=>util3d_showCompassDir(dir) util3d=>util3d_showClock() fin elsif global->b_curAvatar <> 0 setAvatar(global->b_curAvatar) doRender() fin // Init the floppy flag after the first full load, which will have certainly // hit some disk besides 1 if there is such a disk. isFloppyVer = getFloppyFlg // Assume there might be animations until we learn otherwise resetAnimPause // Populate script handlers for the current square, so that leave handlers will trigger right. nMapScripts = scanScripts(x-triggerOriginX, y-triggerOriginY, triggerTbl, @mapScripts) // Display the party characters showParty() // All done. renderLoaded = TRUE end /////////////////////////////////////////////////////////////////////////////////////////////////// export def scriptSetAvatar(avatarTileNum)#0 global->b_curAvatar = avatarTileNum if renderLoaded; setAvatar(avatarTileNum); fin end /////////////////////////////////////////////////////////////////////////////////////////////////// export def textureControl(flg)#0 if renderLoaded flipToPage1 if flg <> texturesLoaded if flg _texControl(1) if mapIs3D; loadUtil3d; fin showingLamp = mapIs3D and (skyNum == 0 or skyNum == 8) else _texControl(0) if !mapIs3D pCurMap = NULL // tile engine frees all maps, even current one fin if pModUtil3d and mapIs3D and !inScript // e.g. strafe -> script -> combat: need strafe to stay in mem! mmgr(FREE_MEMORY, pModUtil3d) pModUtil3d = NULL util3d = NULL fin showingLamp = FALSE needRender = FALSE fin fin texturesLoaded = flg else texturesLoaded = FALSE fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Display a portrait drawing (typically called from scripts) export def clearPortrait()#0 if curPortrait auxMmgr(FREE_MEMORY, curPortrait) curPortrait = NULL curPortraitNum = 0 needRender = TRUE fin if curFullscreenImg auxMmgr(FREE_MEMORY, curFullscreenImg) curFullscreenImg = NULL loadMainFrameImg() needRender = TRUE fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Perform rendering, copy if necessary, clear appropriate flags def doRender()#0 if curPortrait; clearPortrait(); fin textureControl(TRUE) if showingLamp; util3d=>util3d_nextLampFrame(); fin render(0) // Don't interrupt on kbd - must finish the render needRender = FALSE end /////////////////////////////////////////////////////////////////////////////////////////////////// def advTime(hours, mins, secs)#0 byte redrawClock, runScript, prevHour, add word player if secs global->b_second = global->b_second + secs while global->b_second >= 60 global->b_second = global->b_second - 60 mins++ loop fin if mins or hours redrawClock = FALSE runScript = FALSE global->b_minute = global->b_minute + mins if global->b_minute >= nextSignificantMinute; redrawClock = TRUE; fin while global->b_minute >= 60 global->b_minute = global->b_minute - 60 hours++ loop if hours redrawClock = TRUE prevHour = global->b_hour global->b_hour = global->b_hour + hours while global->b_hour >= 24 global->b_hour = global->b_hour - 24 loop runScript = TRUE // Heal over time every 12 hours if (prevHour / 12) <> (global->b_hour / 12) player = global=>p_players while player add = (rand16 % (player->b_stamina+1))/3 setStat(player, @S_HEALTH, player=>w_health + add) player = player=>p_nextObj loop if needShowParty; showParty(); fin fin fin if mapIs3D and redrawClock and renderLoaded and texturesLoaded and util3d util3d=>util3d_showClock() fin if runScript and timeEventFunc timeEventFunc(global->b_hour) fin fin 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)#0 if needRender and renderLoaded and texturesLoaded and !curPortrait and !curFullscreenImg doRender() flipToPage1() needRender = FALSE fin if textClearCountdown; clearTextWindow(); fin if forceRawScrDisp rawDisplayStr(str) else displayStr(str) fin textDrawn = TRUE anyInteraction = TRUE end /////////////////////////////////////////////////////////////////////////////////////////////////// export def promptAnyKey(clearAfter)#0 scriptDisplayStr("\n(press any key)") getUpperKey if clearAfter; clearWindow; fin end /////////////////////////////////////////////////////////////////////////////////////////////////// def snooze()#1 word cursX, cursY cursX, cursY = getCursor() if cursX == snoozeX1 and cursY == snoozeY if mapIs3D advTime(0, 14 - (global->b_minute % 15), 60 - global->b_second) // next 15 min mark else advTime(0, 59 - global->b_minute, 60 - global->b_second) // start of next hour fin fin nextAnimFrame() cursX, cursY = getCursor() if cursX == snoozeX1 and cursY == snoozeY rawDisplayf1("^T%D", snoozeX0) else if cursY; rawDisplayStr("\n"); fin if cursX; rawDisplayStr("\n"); fin rawDisplayStr("The time: ") snoozeX0, snoozeY = getCursor() fin rawDisplayf1("^C%d:", global->b_hour == 0 ?? 12 :: global->b_hour == 12 ?? 12 :: global->b_hour % 12) rawDisplayf2("%s%d ", global->b_minute < 10 ?? "0" :: "", global->b_minute) rawDisplayf1("%s.", global->b_hour < 12 ?? "am" :: "pm") snoozeX1, snoozeY = getCursor() setTextCountdown return 0 end /////////////////////////////////////////////////////////////////////////////////////////////////// export def moveInternal(facingDir, moveDir, beepOK, shouldAdvTime)#1 byte val, i word x, y setDir(moveDir) val = advance(global->b_moveMode) lastMoveDir = moveDir setDir(facingDir) // If not blocked, render at the new position. if val == 0 if beepOK and !inScript // don't beep for scripted moves clearTextWindow rawDisplayStr("Blocked!\n") beep setTextCountdown fin else if !mapIs3D doRender() else needRender = TRUE fin fin // Advance time if requested if shouldAdvTime if mapIs3D for i = 1 to global->b_moveMode advTime(CLOCK_ADV_3D_HOURS, CLOCK_ADV_3D_MINS, CLOCK_ADV_3D_SECS) next else advTime(CLOCK_ADV_2D_HOURS, CLOCK_ADV_2D_MINS, CLOCK_ADV_2D_SECS) fin fin // If we're on a new map tile, run leave handlers from old tile. if val >= 2 scriptEvent(@S_LEAVE, NULL) nMapScripts = 0 // If there are script(s) on the new tile, run them. getPos(@x, @y) nMapScripts = scanScripts(x-triggerOriginX, y-triggerOriginY, triggerTbl, @mapScripts) if nMapScripts scriptEvent(@S_ENTER, NULL) elsif global=>p_encounterZones checkEncounter(x, y, FALSE) fin fin return val end /////////////////////////////////////////////////////////////////////////////////////////////////// // Advance one step forward (works for either 3D or 2D maps) def moveForward()#1 byte dir dir = getDir() return moveInternal(dir, dir, TRUE, TRUE) // beep ok, adv time end /////////////////////////////////////////////////////////////////////////////////////////////////// // Move backward two quarter-steps (3D mode), or one step (2D mode). This is often used when exiting a // building or fleeing combat, so we don't want to generate any random encounters. export def moveWayBackward()#1 byte facingDir, moveDir facingDir = getDir() if lastMoveDir <> $FF moveDir = (lastMoveDir + 8) & 15 // reverse of last move moveInternal(facingDir, moveDir, FALSE, TRUE) // no beep, but do adv time if global->b_moveMode == 1 moveInternal(facingDir, moveDir, FALSE, TRUE) // no beep, but do adv time fin fin lastMoveDir = $FF // avoid problems if moveWayBackward called repeatedly return 0 end /////////////////////////////////////////////////////////////////////////////////////////////////// // Move to the north/east/south/west (2D mode) def moveNorth()#1 word x, y getPos(@x, @y) if y > 4 setDir(0) moveForward() else beep() fin return 0 end def moveEast()#1 word x, y getPos(@x, @y) if x < totalMapWidth-5 setDir(4) moveForward() else beep() fin return 0 end def moveSouth()#1 word x, y getPos(@x, @y) if y < totalMapHeight-5 setDir(8) moveForward() else beep() fin return 0 end def moveWest()#1 word x, y getPos(@x, @y) if x > 4 setDir(12) moveForward() else beep() fin return 0 end /////////////////////////////////////////////////////////////////////////////////////////////////// // Switch to a new map (2D or 3D) and establish position on it export def setMap(is3D, num, x, y, dir)#0 if is3D == mapIs3D and num == mapNum setPos(x, y) setDir(dir) needRender = TRUE else flipToPage1() showMapName("Traveling...") useMapWindow() mapIs3D = is3D mapNum = num initMap(x, y, dir) saveGame fin // Don't send enter event, because we often land on an "Exit to wilderness?" script //NO:scriptEvent(S_ENTER, NULL) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def callGlobalFunc(moduleNum, arg1, arg2, arg3)#1 word pModule, pFunc, ret // First load the module if renderLoaded; flipToPage1(); fin mmgr(START_LOAD, moduleNum <= LAST_REQ_GS_MOD ?? 1 :: curMapPartition) pModule = mmgr(QUEUE_LOAD, moduleNum<<8 | RES_TYPE_MODULE) mmgr(FINISH_LOAD, 0) // Call the function, passing it the number of args it expects pFunc = pModule() when getArgCount(pFunc) is 0; ret = pFunc(); break is 1; ret = pFunc(arg1); break is 2; ret = pFunc(arg1, arg2); break is 3; ret = pFunc(arg1, arg2, arg3); break otherwise fatal("maxGlobParams") wend // Unload the module and we're done. mmgr(FREE_MEMORY, pModule) return ret end /////////////////////////////////////////////////////////////////////////////////////////////////// export def queue_setMap(is3D, num, x, y, dir)#0 byte part part = lookupResourcePart(is3d+1, num) if diskLimit and part > diskLimit clearWindow callGlobalFunc(GS_DISK_LIMIT, 1, 0, 0) return fin q_mapIs3D = is3D q_mapNum = num q_x = x q_y = y q_dir = dir end /////////////////////////////////////////////////////////////////////////////////////////////////// export def queue_teleport(x, y, dir)#0 queue_setMap(mapIs3D, mapNum, x, y, dir) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Get a key and dispatch it to a command. Then do it again, forever. def kbdLoop()#0 word func, tmp byte xreg xreg = getXReg() while TRUE // If the asm routines all work correctly, by the time we get to the top of this loop // the X register should always have the same value. tmp = getXReg() if tmp <> xreg; printHex(xreg<<8 | tmp); fatal("xRegChg"); fin cmdKey = getUpperKey() if cmdKey >= 0 and cmdKey < $60 func = cmdTbl[cmdKey] if func if textClearCountdown if textClearCountdown == 1; clearTextWindow(); fin textClearCountdown-- fin func() fin fin if q_playerDeath; playerDeath(); continue; fin if q_mapNum; setMap(q_mapIs3D, q_mapNum, q_x, q_y, q_dir); q_mapNum = 0; fin if needRender; doRender(); fin if prevScriptModule if prevScriptModule <> scriptModule tmp = mmgr(FIND_IN_MEM, prevScriptModule<<8 | RES_TYPE_MODULE) if tmp; mmgr(FREE_MEMORY, tmp); fin fin prevScriptModule = 0 fin loop end /////////////////////////////////////////////////////////////////////////////////////////////////// def hashString(str)#1 return hashBuffer(str+1, ^str) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def showMapName(mapName)#0 word newNameHash newNameHash = hashString(mapName) if newNameHash <> mapNameHash setWindow1() clearWindow() centerStr(mapName, 84) if mapIs3D and texturesLoaded; copyWindow(0); fin mapNameHash = newNameHash fin 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. export def setScriptInfo(mapName, moduleNum, timeFn, trigTbl, wdt, hgt)#0 word tmp // Record the time event function, if any timeEventFunc = timeFn // Record new script, and prepare to free old one. While theoretically there could be a // circumstance where the old prev is still un-freed, it's rare enough that it'd be hard to // test freeing it here correctly. So we leave freeing to the main keyboard loop. if scriptModule <> moduleNum prevScriptModule = scriptModule scriptModule = moduleNum fin // 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 global=>s_mapName = mmgr(HEAP_INTERN, mapName) showMapName(mapName) // Get ready for new encounter zones clearEncounterZones // Back to the main text window. setWindow2() // If there's a time script, run it so it can set sky color, etc. if timeFn; timeFn(global->b_hour); fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Called by scripts to swap a map tile. We set a flag noting we need to re-render, then use an // assembly routine to do the work. export def scriptCopyTile(fromX, fromY, toX, toY)#0 needRender = TRUE copyTile(fromX, fromY, toX, toY) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Get a key, and don't return until it's Y or N (or lower-case of those). Returns 1 for Y. export def getYN()#1 byte key while TRUE key = getUpperKey() if key == 'Y' return 1 elsif key == 'N' if frameLoaded clearTextWindow() fin break fin beep() loop return 0 end /////////////////////////////////////////////////////////////////////////////////////////////////// // Show the current animation frame def showAnimFrame()#0 byte top if curPortrait if storyMode blit(1, curPortrait + 2, getScreenLine(0)+20, 128, 18) else // Blit portrait to the appropriate area on the screen top = 32 // start at 4th text line in 2D if frameLoaded == 3 // 3D-mode frame? Note: don't check mapIs3D, because we might be in an engine top = 24 // start at 4th text line in 3D fin blit(1, curPortrait + 2, getScreenLine(top)+2, 128, 18) fin needRender = FALSE // suppress display of map for this frame if showingLamp and util3d; util3d=>util3d_nextLampFrame(); fin elsif curFullscreenImg blit(1, curFullscreenImg + 2, getScreenLine(0), 192, 40) // the +2 is to skip anim hdr offset needRender = FALSE // suppress display of map for this frame elsif texturesLoaded if mapIs3D if showingLamp and util3d; util3d=>util3d_nextLampFrame(); fin render($FF) // it's only animation, so do interrupt if a key is pressed if showingLamp and util3d; util3d=>util3d_nextLampFrame(); fin else render(0) // 2d map view fin fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Advance to next frame of current animation, if any def nextAnimFrame()#0 word flags, param byte dir, randNum animPauseCt = ANIM_PAUSE_MAX if !anyAnims; return; fin // Choose a new direction based on the flags. Do this the first time, and once every 3-7 frames. animDirCt = animDirCt - 1 if animDirCt <= 0 animDirCt = (rand16() % 5) + 3 fin // Advance animations. // First part is whether to switch directions on fwd/back anims. // Second part is how many frames to advance random anims. param = ((animDirCt==1) & 1) | (((rand16() % 10)+1)<<8) flags = auxMmgr(ADVANCE_ANIMS, param) | mmgr(ADVANCE_ANIMS, param) if (flags >> 8) // An animation was changed -- display it showAnimFrame() elsif (flags & $FF) == 0 // No animations in memory; turn off future checking anyAnims = FALSE fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Display a portrait drawing (typically called from scripts) export def setPortrait(portraitNum)#0 word srcData byte part, cx, cy, cursX, cursY if portraitNum == curPortraitNum; return; fin clearPortrait() // We're going to switch windows. Save the cursor pos in the text window. cursX, cursY = getCursor() // Make room by unloading the textures (only if renderer is loaded) textureControl(FALSE) // Now clear out the map area useMapWindow() // Restore the cursor position setWindow2() setCursor(cursX, cursY) // Load the portrait image and display it part = lookupResourcePart(3, portraitNum) if !part; fatal("lkup3"); fin // Commented out below, because it prevents cycling thru all portraits (in god mode) // NO: if part > 1; part = curMapPartition; fin // Look on disk 1 or current disk only mmgr(START_LOAD, part) curPortrait = auxMmgr(QUEUE_LOAD, portraitNum<<8 | RES_TYPE_PORTRAIT) curPortraitNum = portraitNum mmgr(FINISH_LOAD, 0) resetAnimPause // And show the first frame showAnimFrame() // Do not render over the portrait needRender = FALSE end /////////////////////////////////////////////////////////////////////////////////////////////////// export def countList(p)#1 return sum(p, NULL, &(p) 1) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Call like this: addToList(@player=>p_items, itemToAdd) export def addToList(addTo, p)#0 // Get to the end of the list while *addTo addTo = (*addTo) + p_nextObj loop p=>p_nextObj = *addTo *addTo = p end /////////////////////////////////////////////////////////////////////////////////////////////////// // Call like this: removeFromList(@player=>items, itemToRemove) export def removeFromList(pList, toRemove)#0 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("unlink") fin end /////////////////////////////////////////////////////////////////////////////////////////////////// export def saveMapPos()#0 global->b_mapIs3D = mapIs3D global->b_mapNum = mapNum getPos(@global=>w_mapX, @global=>w_mapY) global->b_mapDir = getDir() end /////////////////////////////////////////////////////////////////////////////////////////////////// def showMoveMode()#0 rawDisplayStr("Move mode:\n") if global->b_moveMode == 1; rawDisplayStr("^I"); fin rawDisplayStr("Default^N ") if global->b_moveMode == 2; rawDisplayStr("^I"); fin rawDisplayStr("Fast^N ") if global->b_moveMode == 4; rawDisplayStr("^I"); fin rawDisplayStr("Classic^N\n") setTextCountdown end /////////////////////////////////////////////////////////////////////////////////////////////////// def restoreMapPos()#0 mapIs3D = global->b_mapIs3D mapNum = global->b_mapNum initMap(global=>w_mapX, global=>w_mapY, global->b_mapDir) end /////////////////////////////////////////////////////////////////////////////////////////////////// def loadEngine(moduleNum)#1 if curEngine; fatal("dblEng"); fin preEnginePortraitNum = curPortraitNum clearPortrait() textureControl(FALSE) // also flips to page 1 mmgr(START_LOAD, 1) // code is in partition 1 curEngine = mmgr(QUEUE_LOAD, moduleNum<<8 | RES_TYPE_MODULE) mmgr(FINISH_LOAD, 0) return curEngine() // return function table end /////////////////////////////////////////////////////////////////////////////////////////////////// def returnFromEngine(render)#0 if curEngine mmgr(FREE_MEMORY, curEngine) curEngine = NULL loadMainFrameImg() if preEnginePortraitNum setPortrait(preEnginePortraitNum) else clearPortrait() fin textureControl(TRUE) mapNameHash = 0; showMapName(global=>s_mapName) clearTextWindow() if render doRender() else needRender = TRUE fin if mapIs3D prevClockColor = 99 if util3d util3d=>util3d_showCompassDir(getDir()) util3d=>util3d_showClock() fin fin showParty() setWindow2() // in case we're mid-script fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Load the automap engine and display the map def showAutomap()#1 loadEngine(MOD_AUTOMAP)=>automap_show(9999, 9999) // 9999 = no quest target returnFromEngine(TRUE) return 0 end /////////////////////////////////////////////////////////////////////////////////////////////////// // Load the Party engine and show data for the given player def showPlayerSheet(num)#1 word sNameToUse, oldFlg, pItemutilModule if num+1 > countList(global=>p_players); beep; return 0; fin sNameToUse = loadEngine(MOD_PARTY)=>party_showPlayerSheet(num) returnFromEngine(TRUE) // General 'use' handled here in case it triggers graphical effects if sNameToUse anyInteraction = FALSE scriptEvent(@S_USE, sNameToUse) if !anyInteraction scriptDisplayStr("Nothing happens.") setTextCountdown fin fin playerUsing = NULL return 0 end def showPlayer1()#1 return showPlayerSheet(0) end def showPlayer2()#1 return showPlayerSheet(1) end def showPlayer3()#1 return showPlayerSheet(2) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Level up the first character that's applicable def levelUp()#1 word player byte n player = global=>p_players n = 0 while player if player->b_skillPoints showPlayerSheet(n) return 0 fin player = player=>p_nextObj n++ loop beep return 0 end /////////////////////////////////////////////////////////////////////////////////////////////////// export def addEncounterZone(code, x, y, dist, chance)#0 word p p = mmgr(HEAP_ALLOC, TYPE_ENCOUNTER_ZONE) p=>s_name = mmgr(HEAP_INTERN, code) p=>w_encX = x p=>w_encY = y p=>w_encMaxDist = dist p=>w_encChance = chance addToList(@global=>p_encounterZones, p) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def clearEncounterZones()#0 global=>p_encounterZones = NULL end /////////////////////////////////////////////////////////////////////////////////////////////////// // Called by user-defined map scripts to initiate a combat encounter. export def scriptCombat(mapCode)#1 return doCombat(mapCode, TRUE) end /////////////////////////////////////////////////////////////////////////////////////////////////// def playerDeath()#0 setWindow2; clearWindow callGlobalFunc(GS_DEATH, 0, 0, 0) startGame(FALSE, FALSE) // don't ask, just load end /////////////////////////////////////////////////////////////////////////////////////////////////// def resumeMapMusic()#0 if mapEmuSound > 0 and mapEmuSound <> $FF soundPlayEmu(mapEmuSound) fin end /////////////////////////////////////////////////////////////////////////////////////////////////// def doCombat(mapCode, backUpOnFlee)#1 word result // Give a clue as to what's happening if needRender; doRender; fin flipToPage1 setWindow2() clearWindow() callGlobalFunc(GS_COMBAT_INTRO, 0, 0, 0) // Remainder handled in a separate module. Clear enemies out of the heap when finished. result = loadEngine(MOD_COMBAT)=>combat_zoneEncounter(mapCode) global=>p_enemyGroups = NULL heapCollect() if (result == -99) q_playerDeath = TRUE return 0 fin returnFromEngine(!inScript) // only re-render if outside script resumeMapMusic // Advance time a little. It's tricky advancing during combat; this is an ok substitute. advTime(CLOCK_ADV_COMBAT_HOURS, CLOCK_ADV_COMBAT_MINS, CLOCK_ADV_COMBAT_SECS) // If the party fled the combat instead of winning, back up to previous square. if !result and backUpOnFlee moveWayBackward() fin return result end /////////////////////////////////////////////////////////////////////////////////////////////////// // Check for a random encounter at this position export def checkEncounter(x, y, force)#0 word p word p_bestZone, bestDist word d // Don't check for encounter during scripted move if inScript; return; fin // Find the zone that's closest, but not too far. bestDist = INT_MAX p_bestZone = NULL p = global=>p_encounterZones while p d = min(abs(x - p=>w_encX), abs(y - p=>w_encY)) // Using '<=' below so that later-added zones added by a scripted event take precedence if d <= bestDist and (p=>w_encMaxDist == 0 or d <= p=>w_encMaxDist) p_bestZone = p bestDist = d fin p = p=>p_nextObj loop // Roll for an encounter in the zone. d = rand16() % 1000 if p_bestZone if (d < p_bestZone=>w_encChance or force) // Encounter! doCombat(p_bestZone=>s_name, !force) fin fin end /////////////////////////////////////////////////////////////////////////////////////////////////// export def rwGame(cmd)#0 while TRUE if callProRWTS(cmd | RWTS_OPENDIR, @S_GAME1_FILENAME, LOAD_SAVE_BUF, HEAP_SIZE) == 0 break fin textHome() ^$c051 puts("Insert disk 1") rdkey() ^$c050 loop end /////////////////////////////////////////////////////////////////////////////////////////////////// def saveGame()#1 word cursX, cursY cursX, cursY = getCursor() saveMapPos() showMapName("Saving game...") textureControl(FALSE) // also flips to page 1 // Perform garbage collection and record the size of the heap so we can restore it correctly // (also does a CHECK_MEM to be sure we never save corrupted heap) heapCollect() //AUTOMAP_CHECK// readDiskMarks; checkMarks // Copy data to main memory, and write it out. memcpy(HEAP_BOTTOM, LOAD_SAVE_BUF, HEAP_SIZE, 0) // LC to low mem rwGame(RWTS_WRITE) mapNameHash = 0; showMapName(global=>s_mapName) doRender setWindow2() setCursor(cursX, cursY) return 0 end /////////////////////////////////////////////////////////////////////////////////////////////////// def loadGame()#1 // Since old map may have been modified by scripts, force it to really be gone. if pCurMap mmgr(FREE_MEMORY, pCurMap) mmgr(SET_MEM_TARGET, pCurMap) mmgr(REQUEST_MEMORY, 5) mmgr(FREE_MEMORY, pCurMap) pCurMap = NULL fin loadEngine(MOD_DISKOPS)=>diskops_loadGame() restoreMapPos() return 0 end /////////////////////////////////////////////////////////////////////////////////////////////////// def help()#1 flipToPage1 setBigWindow; clearWindow forceRawScrDisp = TRUE loadEngine(GS_HELP)() forceRawScrDisp = FALSE returnFromEngine(TRUE) return 0 end /////////////////////////////////////////////////////////////////////////////////////////////////// def toggleGodMode()#1 byte key key = getUpperKey if key == 15 // ctrl-O key = getUpperKey if key == 4 // ctrl-D flipToPage1() clearTextWindow() if key == 4 // ctrl-D global->b_godmode = !global->b_godmode displayf1("gm:%d\n", global->b_godmode & 1) initCmds() // rebuild the command table with new commands fin textDrawn = TRUE beep; beep clearTextWindow() fin fin return 0 end /////////////////////////////////////////////////////////////////////////////////////////////////// def cheatCmd()#1 byte key word pModule // We don't use laodEngine, since godmode may use it too, and we'd get double modules textureControl(FALSE) // seems to be necessary for teleport-to-3d to work right flipToPage1 mmgr(START_LOAD, 1) // code is in partition 1 pModule = mmgr(QUEUE_LOAD, MOD_GODMODE<<8 | RES_TYPE_MODULE) mmgr(FINISH_LOAD, 0) pModule()=>godmode_cheatCmd(cmdKey) mmgr(FREE_MEMORY, pModule) textureControl(TRUE) return 0 end /////////////////////////////////////////////////////////////////////////////////////////////////// // This one godmode command needs to not load a module -- print memory without disturbing it export def printMem()#1 flipToPage1 ^$c051 mmgr(DEBUG_MEM, 0) rdkey auxMmgr(DEBUG_MEM, 0) rdkey ^$c050 return 0 end /////////////////////////////////////////////////////////////////////////////////////////////////// def showQuests()#1 loadEngine(MOD_QUESTLOG)=>questlog_showQuests() returnFromEngine(TRUE) // do render return 0 end /////////////////////////////////////////////////////////////////////////////////////////////////// def switchMoveMode()#1 byte dir word x, y dir = getDir() if global->b_moveMode == 1 global->b_moveMode = 2 dir = dir & ~1 elsif global->b_moveMode == 2 global->b_moveMode = 4 dir = dir & ~3 else global->b_moveMode = 1 fin setDir(dir) getPos(@x, @y) setPos(x, y) // to round it needRender = TRUE flipToPage1() clearTextWindow() showMoveMode return 0 end /////////////////////////////////////////////////////////////////////////////////////////////////// // Set up the command table for 3D mode def initCmds()#0 // Clear the command table byte i for i = 0 to 95 cmdTbl[i] = 0 next // Commands common to both 2D and 3D cmdTbl['1'] = @showPlayer1 cmdTbl['2'] = @showPlayer2 cmdTbl['3'] = @showPlayer3 cmdTbl['U'] = @levelUp cmdTbl[$13] = @saveGame // ctrl-S cmdTbl[$0c] = @loadGame // ctrl-L cmdTbl['?'] = @help cmdTbl['/'] = @help // in case they forget to hit shift cmdTbl[$07] = @toggleGodMode // ctrl-G cmdTbl[' '] = @snooze // "space out" (snooze) cmdTbl['M'] = @showAutomap cmdTbl['Q'] = @showQuests if global->b_godmode // install cheat commands cmdTbl['T'] = @cheatCmd // teleport cmdTbl[$10] = @cheatCmd // ctrl-P: show pos cmdTbl['>'] = @cheatCmd // next portrait cmdTbl['<'] = @cheatCmd // prev portrait cmdTbl['!'] = @cheatCmd // test combat cmdTbl['Y'] = @cheatCmd // next sky cmdTbl['G'] = @cheatCmd // next ground cmdTbl['&'] = @printMem // print mem cmdTbl['^'] = @cheatCmd // edit flags cmdTbl['*'] = @cheatCmd // soundGen test fin // Commands handled differently in 3D vs 2D if mapIs3D cmdTbl[3] = @switchMoveMode // ctrl-C cmdTbl['W'] = @moveForward cmdTbl['A'] = util3d=>util3d_rotateLeft cmdTbl['D'] = util3d=>util3d_rotateRight cmdTbl['S'] = util3d=>util3d_moveBackward cmdTbl['Z'] = util3d=>util3d_strafeLeft cmdTbl['C'] = util3d=>util3d_strafeRight cmdTbl['I'] = @moveForward cmdTbl['J'] = util3d=>util3d_rotateLeft cmdTbl['L'] = util3d=>util3d_rotateRight cmdTbl['K'] = util3d=>util3d_moveBackward cmdTbl['H'] = util3d=>util3d_strafeLeft cmdTbl[';'] = util3d=>util3d_strafeRight cmdTbl[11] = @moveForward // up-arrow cmdTbl[8] = util3d=>util3d_rotateLeft // left-arrow cmdTbl[21] = util3d=>util3d_rotateRight // right-arrow cmdTbl[10] = util3d=>util3d_moveBackward // down-arrow else cmdTbl['W'] = @moveNorth cmdTbl['D'] = @moveEast cmdTbl['S'] = @moveSouth cmdTbl['A'] = @moveWest cmdTbl['I'] = @moveNorth cmdTbl['J'] = @moveWest cmdTbl['L'] = @moveEast cmdTbl['K'] = @moveSouth cmdTbl[11] = @moveNorth // up-arrow cmdTbl[8] = @moveWest // left-arrow cmdTbl[21] = @moveEast // right-arrow cmdTbl[10] = @moveSouth // down-arrow fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Set up the small-object heap. Set loadedSize to zero on initial, or non-zero for loaded game. export def initHeap(loadedSize)#0 byte i if loadedSize mmgr(SET_MEM_TARGET, HEAP_BOTTOM + loadedSize) fin mmgr(HEAP_SET, HEAP_BOTTOM) i = 0 while typeTbls[i] mmgr(HEAP_ADD_TYPE, typeTbls[i]) i = i+1 loop if loadedSize global = HEAP_BOTTOM else global = mmgr(HEAP_ALLOC, TYPE_GLOBAL) global=>w_typeHash = typeHash fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Constructor: create a modifier given its name and value export def makeModifier(name, value)#1 word p; p = mmgr(HEAP_ALLOC, TYPE_MODIFIER) p=>s_name = mmgr(HEAP_INTERN, name) p=>w_modValue = value return p end /////////////////////////////////////////////////////////////////////////////////////////////////// // Recalculate player's armor score based on their currently equipped armor export def calcPlayerArmor(pl)#0 pl->b_armor = sum(pl=>p_items, &(p) p->t_type == TYPE_ARMOR and p->b_flags & ITEM_FLAG_EQUIP, &(p) p->b_armorValue) end /////////////////////////////////////////////////////////////////////////////////////////////////// // Add gold, return the amount export def addGold(amount)#1 if amount <= 0; return 0; fin amount = min(amount, GOLD_MAX - global=>w_gold) global=>w_gold = global=>w_gold + amount return amount end // Pay out gold, return the amount export def payGold(amount)#1 if amount > global=>w_gold return 0 fin global=>w_gold = global=>w_gold - amount return amount end /////////////////////////////////////////////////////////////////////////////////////////////////// export def scanForNamedObj(list, name)#1 // We don't use lambda here, to preserve context of outer lambdas while list if streqi(list=>s_name, name); break; fin list = list=>p_nextObj loop return list end /////////////////////////////////////////////////////////////////////////////////////////////////// export def createStruct(moduleID, creationFuncNum)#1 word p_module, funcTbl, func, p_thing // Unload textures to make room for the module (also flips to page 1 if needed) textureControl(FALSE) // Load the module that is capable of creating the thing mmgr(START_LOAD, 1) // code is in partition 1 p_module = mmgr(QUEUE_LOAD, moduleID<<8 | RES_TYPE_MODULE) mmgr(FINISH_LOAD, 0) // Figure out which creation function to call there, and create the thing funcTbl = p_module() func = *(funcTbl + creationFuncNum) p_thing = func() // full // Finished with the module now. mmgr(FREE_MEMORY, p_module) return p_thing end /////////////////////////////////////////////////////////////////////////////////////////////////// export def roomInPack(p_player)#1 // Note: initial pack size is established by gen_players.pla, by code generated in PackPartitions. return countList(p_player=>p_items) < p_player->b_packSize end /////////////////////////////////////////////////////////////////////////////////////////////////// export def partyHasItem(itemName)#1 ctx = itemName; return first(global=>p_players, &(p) scanForNamedObj(p=>p_items, ctx)) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def createItem(itemNum)#1 word pItem if itemNum <= NUM_WEAPONS pItem = createStruct(MOD_GEN_WEAPONS, (itemNum-1)<<1) elsif itemNum <= NUM_WEAPONS+NUM_ARMORS pItem = createStruct(MOD_GEN_ARMORS, (itemNum-NUM_WEAPONS-1)<<1) else pItem = createStruct(MOD_GEN_MISC_ITEMS, (itemNum-NUM_WEAPONS-NUM_ARMORS-1)<<1) if pItem->t_type == TYPE_FANCY_ITEM pItem=>w_count = pItem=>w_storeAmount fin fin return pItem end /////////////////////////////////////////////////////////////////////////////////////////////////// // Returns: 0 = dupe // -1 = no room // 1 = added normally // 2 = override-added quest item // 3 = combined with existing stackable export def fullAddItem(pItem, doit)#1 word pPlayer, pComp, addToPlayer pPlayer = global=>p_players addToPlayer = NULL while pPlayer pComp = scanForNamedObj(pPlayer=>p_items, pItem=>s_name) if pComp if pComp->t_type == TYPE_FANCY_ITEM and pComp=>w_count > 0 pComp=>w_count = min(30000, pComp=>w_count + pItem=>w_count) return 3 // combined fin if !pComp=>w_price return 0 // dupe quest item fin fin if !addToPlayer and roomInPack(pPlayer); addToPlayer = pPlayer; fin pPlayer = pPlayer=>p_nextObj loop if addToPlayer if doit; addToList(@addToPlayer=>p_items, pItem); fin return 1 fin if pItem=>w_price return -1 // no room, and item has a price so it's not a quest item fin // Override: quest items allowed to exceed pack limitations if doit; addToList(@global=>p_players=>p_items, pItem); fin return 2 end /////////////////////////////////////////////////////////////////////////////////////////////////// export def giveItemToParty(p_item, displayFunc)#0 when fullAddItem(p_item, TRUE) is 0 displayFunc("Duplicate item.\n")#0 beep break is -1 displayFunc(sprintf1("\nYou have no room; %s dropped.\n", p_item=>s_name))#0 beep break otherwise heapCollect wend end /////////////////////////////////////////////////////////////////////////////////////////////////// // Add XP to one player, leveling them up as appropriate def addXP(player, val)#0 word n if val < 0; return; fin // Add the value plus an intelligence-based bonus player=>w_curXP = player=>w_curXP + val + addPercent(val, 10 * player->b_intelligence) // Enforce cap on number of points to stay within limit of 15 bits if player=>w_curXP < 0 or player=>w_curXP >= 32760 // goes neg if wrapped around player=>w_curXP = 32760 fin while player=>w_curXP >= player=>w_nextXP and player=>w_curXP < 32761 // Level up! player->b_level++ player->b_skillPoints = player->b_skillPoints + callGlobalFunc(GS_LEVEL_S_P, player->b_level, 0, 0) player=>w_maxHealth = player=>w_maxHealth + ((player->b_stamina+1)>>1) + rollDice($2600) // roundup(stam/2) + 2d6 player=>w_health = player=>w_maxHealth // let's make leveling up an extra nice thing needShowParty = TRUE // Check XP for next level, and enforce level cap if any n = callGlobalFunc(GS_LEVEL_X_P, player->b_level + 1, 0, 0) if n > player=>w_nextXP player=>w_nextXP = n else player=>w_nextXP = 32767 // XP cap reached break fin loop end /////////////////////////////////////////////////////////////////////////////////////////////////// // Initialize XP (and skill pts) for newly created character (called by packer code) export def initPlayerXP(player)#0 player->b_skillPoints = callGlobalFunc(GS_LEVEL_S_P, player->b_level, 0, 0) player=>w_curXP = callGlobalFunc(GS_LEVEL_X_P, player->b_level, 0, 0) player=>w_nextXP = callGlobalFunc(GS_LEVEL_X_P, player->b_level + 1, 0, 0) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def equipItem(item)#0 if item item->b_flags = item->b_flags | ITEM_FLAG_EQUIP fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Arm newly created player (or NPC) with first weapon and all armor. export def girdPlayer(player)#0 equipItem(first(player=>p_items, &(p) p->t_type == TYPE_WEAPON)) forSome(player=>p_items, &(p) p->t_type == TYPE_ARMOR, @equipItem) calcPlayerArmor(player) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def addPlayerToParty(playerFuncNum, displayFunc)#0 word p_player if countList(global=>p_players) == MAX_PARTY displayFunc("Party too large.\n")#0 beep return fin // Create the player (NPC flag will be established inside the creation func) p_player = createStruct(MOD_GEN_PLAYERS, playerFuncNum) // Add if not dupe if !scanForNamedObj(global=>p_players, p_player=>s_name) addToList(@global=>p_players, p_player) adjustPartyStat(@S_XP, 0) // to set initial skill pts heapCollect needShowParty = TRUE fin end /////////////////////////////////////////////////////////////////////////////////////////////////// export def removeNamed(name, pList)#1 word p_thing p_thing = scanForNamedObj(*pList, name) if p_thing // If it's stackable and there's more than one, just reduce the count. Otherwise take it all. if p_thing->t_type == TYPE_FANCY_ITEM and p_thing=>w_count > 1 p_thing=>w_count-- else removeFromList(pList, p_thing) fin return TRUE fin return FALSE end /////////////////////////////////////////////////////////////////////////////////////////////////// export def takeItemFromParty(itemName)#0 ctx = itemName; first(global=>p_players, &(p) removeNamed(ctx, @p=>p_items)) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def removePlayerFromParty(playerName)#0 removeNamed(playerName, @global=>p_players) needShowParty = TRUE end /////////////////////////////////////////////////////////////////////////////////////////////////// export def partyHasPlayer(playerName)#1 return scanForNamedObj(global=>p_players, playerName) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def getStat(player, statName)#1 word pSkill when 1 is streqi(statName, @S_INTELLIGENCE); return player->b_intelligence is streqi(statName, @S_STRENGTH); return player->b_strength is streqi(statName, @S_AGILITY); return player->b_agility is streqi(statName, @S_STAMINA); return player->b_stamina is streqi(statName, @S_CHARISMA); return player->b_charisma is streqi(statName, @S_SPIRIT); return player->b_spirit is streqi(statName, @S_LUCK); return player->b_luck is streqi(statName, @S_HEALTH); return player=>w_health is streqi(statName, @S_MAX_HEALTH); return player=>w_maxHealth is streqi(statName, @S_AIMING); return player->b_aiming is streqi(statName, @S_HAND_TO_HAND); return player->b_handToHand is streqi(statName, @S_DODGING); return player->b_dodging is streqi(statName, @S_GOLD); return global=>w_gold is streqi(statName, @S_TIME); return global->b_hour is streqi(statName, @S_XP); return player=>w_curXP is streqi(statName, @S_SP); return player->b_skillPoints is streqi(statName, @S_PACK_SIZE); return player->b_packSize is streqi(statName, @S_BANK_BAL); return global=>w_bankBal wend pSkill = scanForNamedObj(player=>p_skills, statName) if pSkill; return pSkill=>w_modValue; fin puts(statName); return fatal("getStat") end /////////////////////////////////////////////////////////////////////////////////////////////////// // Return an appropriate pronoun for the given gender (M/F/N...) export def hisHerTheir(gender)#1 if gender == 'M'; return @S_HIS; fin if gender == 'F'; return @S_HER; fin return @S_THEIR end /////////////////////////////////////////////////////////////////////////////////////////////////// export def clampByte(val)#1 return max(0, min(255, val)) end /////////////////////////////////////////////////////////////////////////////////////////////////// def setHour(val) #0 val = val % 24 // clamp to range 0..23 if val <> global->b_hour or global->b_minute if global->b_hour >= val; val = val + 24; fin // to ensure positive difference advTime(val - global->b_hour - 1, 59 - global->b_minute, 60 - global->b_second) fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Adjust the given stat for every (living) player in the party export def adjustPartyStat(statName, val)#0 word p_player when 1 is streqi(statName, @S_GOLD); global=>w_gold = max(0, global=>w_gold + val); needShowParty = TRUE; break is streqi(statName, @S_TIME); setHour(global->b_hour + val); break; is streqi(statName, @S_BANK_BAL); global=>w_bankBal = global=>w_bankBal + val; break otherwise p_player = global=>p_players while p_player if p_player=>w_health > 0 setStat(p_player, statName, getStat(p_player, statName) + val) fin p_player = p_player=>p_nextObj loop wend end /////////////////////////////////////////////////////////////////////////////////////////////////// // Get the max value of the stat for any (living) character in the party. // Except if a particular player is using a skill or item in which case it's their stat only. export def getStatInContext(statName)#1 word p_player, val val = 0 when 1 is streqi(statName, @S_GOLD); return global=>w_gold is streqi(statName, @S_TIME); return global->b_hour is streqi(statName, @S_BANK_BAL); return global=>w_bankBal is playerUsing <> NULL; return getStat(playerUsing, statName) otherwise p_player = global=>p_players while p_player val = max(val, getStat(p_player, statName)) p_player = p_player=>p_nextObj loop wend return val end /////////////////////////////////////////////////////////////////////////////////////////////////// export def setStat(player, statName, val)#0 word pSkill when 1 is streqi(statName, @S_INTELLIGENCE); player->b_intelligence = clampByte(val); break is streqi(statName, @S_STRENGTH); player->b_strength = clampByte(val); break is streqi(statName, @S_AGILITY); player->b_agility = clampByte(val); break is streqi(statName, @S_STAMINA); player->b_stamina = clampByte(val); break is streqi(statName, @S_CHARISMA); player->b_charisma = clampByte(val); break is streqi(statName, @S_SPIRIT); player->b_spirit = clampByte(val); break is streqi(statName, @S_LUCK); player->b_luck = clampByte(val); break is streqi(statName, @S_HEALTH); player=>w_health = max(0, min(player=>w_maxHealth, val)) needShowParty = TRUE; break is streqi(statName, @S_MAX_HEALTH); player=>w_maxHealth = max(0, val); break is streqi(statName, @S_AIMING); player->b_aiming = clampByte(val); break is streqi(statName, @S_HAND_TO_HAND); player->b_handToHand = clampByte(val); break is streqi(statName, @S_DODGING); player->b_dodging = clampByte(val); break is streqi(statName, @S_GOLD); global=>w_gold = max(0, val); needShowParty = TRUE; break is streqi(statName, @S_TIME); setHour(val); break; is streqi(statName, @S_XP); addXP(player, val - player=>w_curXP); needShowParty = TRUE; break is streqi(statName, @S_SP); player->b_skillPoints = clampByte(max(0, val)); needShowParty = TRUE; break is streqi(statName, @S_PACK_SIZE); player->b_packSize = clampByte(max(player->b_packSize, val)); break is streqi(statName, @S_BANK_BAL); global=>w_bankBal = val; break otherwise pSkill = scanForNamedObj(player=>p_skills, statName) if pSkill pSkill=>w_modValue = max(0, val) else puts(statName); fatal("setStat") fin wend end /////////////////////////////////////////////////////////////////////////////////////////////////// export def setGameFlag(flagNum, val)#0 byte byteNum, mask byteNum = flagNum >> 3 mask = 1<<(flagNum & 7) if val global->ba_gameFlags[byteNum] = global->ba_gameFlags[byteNum] | mask else global->ba_gameFlags[byteNum] = global->ba_gameFlags[byteNum] & ~mask fin end /////////////////////////////////////////////////////////////////////////////////////////////////// export def getGameFlag(flagNum)#1 byte byteNum, mask byteNum = flagNum >> 3 mask = 1<<(flagNum & 7) return global->ba_gameFlags[byteNum] & mask end /////////////////////////////////////////////////////////////////////////////////////////////////// export def setStoryMode(enable)#0 // Story mode only exists on 800K or hard drive builds, and not the disk-limited ones. if !isFloppyVer and !diskLimit storyMode = enable if enable loadEngine(MOD_STORY) curEngine()=>story_mode(TRUE, preEnginePortraitNum) needRender = FALSE frameLoaded = 0 // since we just destroyed it textDrawn = FALSE textClearCountdown = 0 else curEngine()=>story_mode(FALSE, 0) preEnginePortraitNum = 0 // so we don't restore it returnFromEngine(TRUE) fin fin end /////////////////////////////////////////////////////////////////////////////////////////////////// export def displayStory(storyNum)#0 curEngine()=>story_display(storyNum) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def benchPlayer()#0 loadEngine(MOD_PARTY)=>party_benchPlayer() returnFromEngine(TRUE) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def unbenchPlayer()#0 loadEngine(MOD_PARTY)=>party_unbenchPlayer() returnFromEngine(TRUE) end /////////////////////////////////////////////////////////////////////////////////////////////////// export def buySell(storeCode, profitRatio)#0 if storeCode loadEngine(MOD_STORE)=>store_buyFromStore(storeCode, profitRatio) else loadEngine(MOD_STORE)=>store_sellToStore(profitRatio) fin returnFromEngine(FALSE) // no render, we're mid-script end /////////////////////////////////////////////////////////////////////////////////////////////////// def startGame(firstTime, ask)#0 word p_module typeHash = hashBuffer(@typeTbl_TGlobal, @typeTbls - @typeTbl_TGlobal) ^ HEAP_BOTTOM // Create a new game or load an existing one mmgr(START_LOAD, 1) // code is in partition 1 if firstTime // Temporarily reserve a big chunk of aux mem to keep diskops bytecode out auxMmgr(SET_MEM_TARGET, $800) // well above where resource index loads at startup auxMmgr(REQUEST_MEMORY, $7F00) // plenty of room for resource index + temp expander // Reserve space for the small-object heap mmgr(SET_MEM_TARGET, HEAP_BOTTOM) mmgr(REQUEST_MEMORY, HEAP_SIZE) mmgr(LOCK_MEMORY, HEAP_BOTTOM) fin p_module = mmgr(QUEUE_LOAD, MOD_DISKOPS<<8 | RES_TYPE_MODULE) mmgr(FINISH_LOAD, 0) q_playerDeath = FALSE if firstTime; p_module()=>diskops_startup(); fin if p_module()=>diskops_newOrLoadGame(ask) mapIs3D = q_mapIs3D mapNum = q_mapNum q_mapNum = 0 initMap(q_x, q_y, q_dir) saveGame() setTextCountdown else q_mapNum = 0 restoreMapPos() fin end /////////////////////////////////////////////////////////////////////////////////////////////////// // Main code. // scriptDisplayStr(@_scriptDisplayStr) // 1-time init startGame(TRUE, TRUE) // first time init; ask whether new or load kbdLoop() done