diff --git a/Platform/Apple/tools/PackPartitions/src/org/badvision/A2PackPartitions.groovy b/Platform/Apple/tools/PackPartitions/src/org/badvision/A2PackPartitions.groovy index 0394b490..bc9fb4a0 100644 --- a/Platform/Apple/tools/PackPartitions/src/org/badvision/A2PackPartitions.groovy +++ b/Platform/Apple/tools/PackPartitions/src/org/badvision/A2PackPartitions.groovy @@ -79,6 +79,27 @@ class A2PackPartitions def cache = [:] def buildDir def memUsageFile + + def stats = [ + "intelligence": "@S_INTELLIGENCE", + "strength": "@S_STRENGTH", + "agility": "@S_AGILITY", + "stamina": "@S_STAMINA", + "spirit": "@S_SPIRIT", + "luck": "@S_LUCK", + "health": "@S_HEALTH", + "max health": "@S_MAX_HEALTH", + "aiming": "@S_AIMING", + "hand to hand": "@S_HAND_TO_HAND", + "dodging": "@S_DODGING", + "gold": "@S_GOLD" + ] + + def predefStrings = stats + [ + "enter": "@S_ENTER", + "leave": "@S_LEAVE", + "use": "@S_USE" + ] /** * Keep track of context within the XML file, so we can spit out more useful @@ -117,6 +138,10 @@ class A2PackPartitions def escapeString(inStr) { + // Commonly used strings (e.g. event handler names, attributes) + if (inStr in predefStrings) + return predefStrings[inStr] + def buf = new StringBuilder() buf << '\"' def prev = '\0' @@ -2988,21 +3013,9 @@ end } def nameToStat(name) { - switch (name.toLowerCase().trim()) { - case "intelligence": return "@S_INTELLIGENCE"; return - case "strength": return "@S_STRENGTH"; return - case "agility": return "@S_AGILITY"; return - case "stamina": return "@S_STAMINA"; return - case "spirit": return "@S_SPIRIT"; return - case "luck": return "@S_LUCK"; return - case "health": return "@S_HEALTH"; return - case "max health": return "@S_MAX_HEALTH"; return - case "aiming": return "@S_AIMING"; return - case "hand to hand": return "@S_HAND_TO_HAND"; return - case "dodging": return "@S_DODGING"; return - case "gold": return "@S_GOLD"; return - default: assert false : "Unrecognized stat '$name'" - } + def lcName = name.toLowerCase().trim() + assert lcName in stats : "Unrecognized stat '$name'" + return stats[lcName] } def packChangeStat(blk) diff --git a/Platform/Apple/virtual/src/plasma/gameloop.pla b/Platform/Apple/virtual/src/plasma/gameloop.pla index 6abb39e6..b24e4b2e 100644 --- a/Platform/Apple/virtual/src/plasma/gameloop.pla +++ b/Platform/Apple/virtual/src/plasma/gameloop.pla @@ -89,6 +89,11 @@ word q_x = 0 word q_y = 0 byte q_dir = 0 +// 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 tabBuf[5] @@ -117,6 +122,7 @@ export byte[] S_HAND_TO_HAND = "hand to hand" export byte[] S_DODGING = "dodging" export byte[] S_GOLD = "gold" export byte[] S_ENTER = "enter" +export byte[] S_LEAVE = "leave" export byte[] S_USE = "use" /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1201,6 +1207,82 @@ export def showParty() needShowParty = FALSE end +/////////////////////////////////////////////////////////////////////////////////////////////////// +def getArgCount(pFunc) + 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 + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Send an event to the scripts on the current map square +def scriptEvent(event, param) + byte i + word script + if !nMapScripts; return; fin + + setWindow2() + skipScripts = FALSE + + for i = 0 to nMapScripts-1 + script = mapScripts[i] + if getArgCount(script) == 2 + script(event, param) + elsif event == @S_ENTER // zero-param scripts are assumed to be strictly 'enter' handlers + script() + fin + + // Some scripts need to suppress running of any further scripts on the square + // because they swapped out the render engine. + if skipScripts; break; fin + next + + clearPortrait() + if needShowParty; showParty(); fin + if global=>p_players=>w_health == 0; playerDeath(); fin +end + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Check for script(s) attached to the given location, and establish the array of map scripts. +// Does not call any of them -- that's the job of scriptEvent(). +def scanScripts(x, y) + word p + word script + word pNext + + nMapScripts = 0 + x = x - triggerOriginX + y = y - triggerOriginY + p = triggerTbl + while p + if ^p == $FF + break + fin + pNext = p + p->1 + if ^p == y + p = p + 2 + while p < pNext + if x == ^p + if nMapScripts == MAX_MAP_SCRIPTS; fatal("maxScpts"); fin + mapScripts[nMapScripts] = p=>1 + nMapScripts++ + fin + p = p + 3 + loop + fin + p = pNext + loop +end + /////////////////////////////////////////////////////////////////////////////////////////////////// // Load code and data, set up everything to display a 2D or 3D map def initMap(x, y, dir) @@ -1268,6 +1350,9 @@ def initMap(x, y, dir) doRender() fin + // Populate script handlers for the current square, so that leave handlers will trigger right. + scanScripts(x, y) + // Display the party characters showParty() end @@ -1278,68 +1363,10 @@ export def scriptSetAvatar(avatarTileNum) if renderLoaded; setAvatar(avatarTileNum); fin end -/////////////////////////////////////////////////////////////////////////////////////////////////// -// Send an event to the script -export def scriptEvent(event, param) - if global=>p_mapScript - return global=>p_mapScript(event, param) - fin - return -1 -end - -// Check for script(s) attached to the given location, and call them if there are any. -// Returns TRUE if any were triggered. -def checkScripts(x, y) - word p - word script - word pNext - byte anyTriggered - - scriptEvent(EVENT_LEAVE, 0) - global=>p_mapScript = 0 - anyTriggered = FALSE - x = x - triggerOriginX - y = y - triggerOriginY - p = triggerTbl - while p - if ^p == $FF - break - fin - pNext = p + p->1 - if ^p == y - p = p + 2 - while p < pNext - if x == ^p - script = p=>1 - setWindow2() - skipScripts = FALSE - script() // When should the script be installed as an event handler? - clearPortrait() - // Some scripts need to suppress running of any further scripts on the square - // because they swapped out the render engine. - if skipScripts; return TRUE; fin - anyTriggered = TRUE - fin - p = p + 3 - loop - fin - p = pNext - loop - if anyTriggered - scriptEvent(EVENT_ENTER, 0) - if global=>p_players=>w_health == 0 - playerDeath() - fin - if needShowParty - showParty() - fin - fin - return anyTriggered -end - /////////////////////////////////////////////////////////////////////////////////////////////////// export def unloadTextures() if renderLoaded and texturesLoaded + flipToPage1() texControl(0) texturesLoaded = FALSE fin @@ -1391,20 +1418,25 @@ def moveForward() fin fin - // If we're on a new map tile, clear text from script(s) on the old tile. - if val >= 2 and textDrawn - clearWindow() - if mapIs3D; copyWindow(); fin - textDrawn = FALSE + // If we're on a new map tile, clear text from script(s) on the old tile, and run leave handlers. + if val >= 2 + if textDrawn + clearWindow() + if mapIs3D; copyWindow(); fin + textDrawn = FALSE + fin + scriptEvent(@S_LEAVE, NULL) + nMapScripts = 0 fin // If there are script(s) on the new tile, run them. if val == 3 getPos(@x, @y) - if !checkScripts(x, y) - if global=>p_encounterZones - checkEncounter(x, y, FALSE) - fin + scanScripts(x, y) + if nMapScripts + scriptEvent(@S_ENTER, NULL) + elsif global=>p_encounterZones + checkEncounter(x, y, FALSE) fin elsif val >= 2 and global=>p_encounterZones getPos(@x, @y) @@ -1533,8 +1565,8 @@ def setMap(is3D, num, x, y, dir) initMap(x, y, dir) allowZoneInit = FALSE fin - // Don't check scripts, because we often land on an "Exit to wilderness?" script - //NO:checkScripts() + // Don't send enter event, because we often land on an "Exit to wilderness?" script + //NO:scriptEvent(S_ENTER, NULL) end /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1740,7 +1772,6 @@ export def setPortrait(portraitNum) byte cx, cy clearPortrait() - flipToPage1() // We're going to switch windows. Save the cursor pos in the text window. saveCursor() @@ -1873,6 +1904,7 @@ def loadEngine(moduleNum) fin mmgr(RESET_MEMORY, 0) renderLoaded = FALSE + nMapScripts = 0 mapIs3D = FALSE curPortrait = NULL mmgr(START_LOAD, 1) // code is in partition 1 @@ -2366,11 +2398,10 @@ end export def createAndAddUnique(moduleID, creationFuncNum, pList) word p_module, funcTbl, func, p_thing - // Unload textures to make room for the module + // Unload textures to make room for the module (also flips to page 1 if needed) unloadTextures() // Load the module that is capable of creating the thing - flipToPage1() diskActivity($FF) mmgr(START_LOAD, 1) // code is in partition 1 p_module = mmgr(QUEUE_LOAD, moduleID<<8 | RES_TYPE_MODULE) diff --git a/Platform/Apple/virtual/src/plasma/playtype.plh b/Platform/Apple/virtual/src/plasma/playtype.plh index e47f3a51..3ad1e4a1 100644 --- a/Platform/Apple/virtual/src/plasma/playtype.plh +++ b/Platform/Apple/virtual/src/plasma/playtype.plh @@ -25,7 +25,6 @@ struc Global word w_mapX word w_mapY byte b_mapDir - word p_mapScript // Shared player gold amount word w_gold diff --git a/Platform/Apple/virtual/src/raycast/render.s b/Platform/Apple/virtual/src/raycast/render.s index 648da86d..04af3e09 100644 --- a/Platform/Apple/virtual/src/raycast/render.s +++ b/Platform/Apple/virtual/src/raycast/render.s @@ -1668,7 +1668,7 @@ pl_texControl: !zone { jsr mainLoader lda #0 ; don't re-init scripts jmp loadTextures -.unload +.unload inc $4000 ; make diff from $2000, so we know to restore $4000 later. - txa pha ldy texAddrHi,x