Merge branch 'master' into newplasma

This commit is contained in:
Martin Haye 2017-09-03 07:00:38 -07:00
commit 5286d766df
12 changed files with 1231 additions and 686 deletions

View File

@ -19,11 +19,13 @@ import java.nio.ByteBuffer
import java.nio.channels.Channels
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.util.Calendar
import java.util.zip.GZIPInputStream
import java.util.LinkedHashMap
import java.security.MessageDigest
import javax.xml.bind.DatatypeConverter
import groovy.json.JsonOutput
import groovy.util.Node
/**
*
@ -81,6 +83,7 @@ class A2PackPartitions
def modules = [:] // module name to module.num, module.buf
def bytecodes = [:] // module name to bytecode.num, bytecode.buf
def fixups = [:] // module name to fixup.num, fixup.buf
def gameFlags = [:] // flag name to number
def itemNameToFunc = [:]
def playerNameToFunc = [:]
@ -773,6 +776,12 @@ class A2PackPartitions
cache[key] = [hash:hash, data:buf]
}
def updateEngineStamp(name, hash)
{
if (!cache.containsKey("engineStamp") || cache["engineStamp"].hash < hash)
cache["engineStamp"] = [hash:hash]
}
def grabEntireFromCache(kind, addTo, hash)
{
if (cache.containsKey(kind) && cache[kind].hash == hash) {
@ -1559,6 +1568,30 @@ class A2PackPartitions
}
}
/**
* Make a compact representation of a timestamp, useful as a version number
*/
def timestampToVersionNum(engineStamp, scenarioStamp)
{
Calendar cal = Calendar.getInstance()
cal.setTimeInMillis(engineStamp)
def year = cal.get(Calendar.YEAR)
def month = cal.get(Calendar.MONTH)
def day = cal.get(Calendar.DAY_OF_MONTH)
def hour = cal.get(Calendar.HOUR_OF_DAY)
def yearCode = year % 10
def monthCode = (month < 9) ? (char) (48+month+1) :
month == 9 ? 'o' :
month == 10 ? 'n' :
'd'
def hourCode = (char) (97 + hour) // 'a'=0, 'b'=1, etc.
def engineCode = String.format("%d%c%02d%c", yearCode, monthCode, day, hourCode)
def offset = (int) ((scenarioStamp - engineStamp) / (1000 * 60 * 60))
return String.format("%s%s%d", engineCode, offset < 0 ? "-" : ".", Math.abs(offset))
}
/**
* Make an index listing the partition number wherein each map and portrait can be found.
*/
@ -1566,6 +1599,12 @@ class A2PackPartitions
{
def tmp = ByteBuffer.allocate(5000)
// Start with the version number
def combinedVersion = timestampToVersionNum(cache["engineStamp"].hash, cache["scenarioStamp"].hash)
tmp.put((byte)(combinedVersion.length()))
combinedVersion.getBytes().each { b -> tmp.put((byte)b) }
// Then output 2D maps, 3d maps, and portraits
tmp.put((byte) maps2D.size())
maps2D.each { k, v ->
tmp.put((byte) ((parseOrder(v.order) < 0) ? 255 : v.buf.partNum))
@ -1584,6 +1623,8 @@ class A2PackPartitions
name:"resourceIndex", buf:code["resourceIndex"].buf]
part.chunks[["code", "resourceIndex"]] = chunk
part.spaceUsed += calcChunkLen(chunk)
return combinedVersion
}
def fillAllDisks()
@ -1637,7 +1678,7 @@ class A2PackPartitions
assert allMaps.isEmpty : "All data must fit within $MAX_DISKS disks."
// Add the special resource index to disk 1
addResourceIndex(partChunks[0])
def gameVersion = addResourceIndex(partChunks[0])
// And write out each disk
partChunks.each { part ->
@ -1649,6 +1690,8 @@ class A2PackPartitions
def spaceUsed = part.spaceUsed // use var to avoid gigantic assert fail msg
assert spaceUsed == partFile.length()
}
println "Game version: V $gameVersion"
}
def writePartition(stream, partNum, chunks)
@ -1859,6 +1902,7 @@ class A2PackPartitions
def uncompData = readBinary(inDir + "build/" + codeName + ".b")
addToCache("code", code, codeName, hash, compress(uncompData))
updateEngineStamp(codeName, hash)
}
def assembleCore(inDir)
@ -1878,8 +1922,10 @@ class A2PackPartitions
def file = jitCopy(
new File("build/tools/${name=="PRORWTS" ? "ProRWTS/PRORWTS2" : "PLASMA/src/PLVM02"}#4000"))
hash = file.lastModified()
if (!grabFromCache("sysCode", sysCode, name, hash))
if (!grabFromCache("sysCode", sysCode, name, hash)) {
addToCache("sysCode", sysCode, name, hash, compress(readBinary(file.toString())))
updateEngineStamp(name, hash)
}
}
else {
hash = getLastDep(new File(inDir, "${name}.s"))
@ -1892,6 +1938,7 @@ class A2PackPartitions
addToCache("sysCode", sysCode, name, hash,
(name ==~ /loader|decomp/) ? [data:uncompData, len:uncompData.length, compressed:false]
: compress(uncompData))
updateEngineStamp(name, hash)
}
}
@ -1952,6 +1999,8 @@ class A2PackPartitions
addToCache("modules", modules, moduleName, hash, module)
addToCache("bytecodes", bytecodes, moduleName, hash, bytecode)
addToCache("fixups", fixups, moduleName, hash, fixup)
if (!(moduleName ==~ /.*(gs|gen)_.*/ || codeDir ==~ /.*mapScript.*/))
updateEngineStamp(moduleName, hash)
}
def readAllCode()
@ -1980,6 +2029,7 @@ class A2PackPartitions
compileModule("gen_enemies", "src/plasma/")
compileModule("gen_items", "src/plasma/")
compileModule("gen_players", "src/plasma/")
compileModule("gen_flags", "src/plasma/")
globalScripts.each { name, nArgs ->
compileModule("gs_"+name, "src/plasma/")
}
@ -2015,6 +2065,40 @@ class A2PackPartitions
}
}
/*
* Scan all the scripts looking for flags, and make a mapping of flag name to number.
*/
def numberGameFlags(data)
{
String name = data.name().toString()
if (name == "{outlaw}gameData") {
gameFlags = [] as Set // temporary, until we have them all
data.global.scripts.script.each { numberGameFlags(it) }
data.map.scripts.script.each { numberGameFlags(it) }
// Now that we have them all, sort and assign numbers
def flagSet = gameFlags
gameFlags = [:]
flagSet.sort().each { flg -> gameFlags[flg] = gameFlags.size() }
}
else if (name == "{outlaw}block" &&
(data.@type == "interaction_get_flag" || data.@type == "interaction_set_flag"))
{
def els = data.field
assert els.size() == 1
def first = els[0]
assert first.@name == "NAME"
def flg = first.text().toLowerCase()
gameFlags << flg
}
else {
data.iterator().each {
if (it instanceof Node)
numberGameFlags(it)
}
}
}
def readCache()
{
File cacheFile = new File("build/world.cache")
@ -2127,11 +2211,14 @@ class A2PackPartitions
addResourceDep("map", curMapName, toType, toName)
}
def pack(xmlPath, dataIn)
def pack(xmlFile, dataIn)
{
// Save time by using cache of previous run
readCache()
// Record scenario timestamp
cache["scenarioStamp"] = [hash: xmlFile.lastModified()]
// Record global script names
recordGlobalScripts(dataIn)
@ -2242,6 +2329,9 @@ class A2PackPartitions
// Number all the maps and record them with names
numberMaps(dataIn)
// Assign a number to each game flag
numberGameFlags(dataIn)
// Form the translation from item name to function name (and ditto
// for players)
allItemFuncs(dataIn.global.sheets.sheet)
@ -2598,6 +2688,67 @@ end
return parseDice(val)
}
def genAllFlags()
{
// Make constants
new File("build/src/plasma/gen_flags.plh.new").withWriter { out ->
out.println("// Generated code - DO NOT MODIFY BY HAND\n")
out.println("const flags_nameForNumber = 0")
out.println("const flags_numberForName = 2")
out.println()
gameFlags.each { name, num ->
out.println("const GF_${humanNameToSymbol(name, true)} = $num")
}
out.println("const NUM_GAME_FLAGS = ${gameFlags.size()}")
}
replaceIfDiff("build/src/plasma/gen_flags.plh")
// Generate code
new File("build/src/plasma/gen_flags.pla.new").withWriter { out ->
out.println("// Generated code - DO NOT MODIFY BY HAND")
out.println()
out.println("include \"gamelib.plh\"")
out.println("include \"globalDefs.plh\"")
out.println("include \"gen_flags.plh\"")
out.println()
out.println("predef _flags_nameForNumber(num)#1")
out.println("predef _flags_numberForName(name)#1")
out.println()
out.println("word[] funcTbl = @_flags_nameForNumber, @_flags_numberForName")
out.println()
gameFlags.each { name, num ->
out.println("byte[] SF_${humanNameToSymbol(name, true)} = ${escapeString(name.toUpperCase())}")
}
out.println()
gameFlags.each { name, num ->
if (num == 0)
out.print("word[] flagNames = ")
else
out.print("word = ")
out.println("@SF_${humanNameToSymbol(name, true)}")
}
out.println()
out.println("def _flags_nameForNumber(num)#1")
out.println(" if num >= 0 and num < NUM_GAME_FLAGS; return flagNames[num]; fin")
out.println(" return NULL")
out.println("end")
out.println()
out.println("def _flags_numberForName(name)#1")
out.println(" word num")
out.println(" for num = 0 to NUM_GAME_FLAGS-1")
out.println(" if streqi(flagNames[num], name); return num; fin")
out.println(" next")
out.println(" return -1")
out.println("end")
out.println()
// Lastly, the outer module-level code
out.println("return @funcTbl")
out.println("done")
}
replaceIfDiff("build/src/plasma/gen_flags.pla")
}
def genWeapon(func, row, out)
{
out.println(
@ -2629,26 +2780,21 @@ end
"${parseModifier(row, "bonus-value", "bonus-attribute")})")
}
def genAmmo(func, row, out)
{
out.println(
" return makeStuff(" +
"${escapeString(parseStringAttr(row, "name"))}, " +
"${escapeString(parseStringAttr(row, "ammo-kind"))}, " +
"${parseWordAttr(row, "price")}, " +
"${parseWordAttr(row, "max")}, " +
"${parseWordAttr(row, "store-amount")}, " +
"${parseDiceAttr(row, "loot-amount")})")
}
def genItem(func, row, out)
{
out.println(
" return makeItem(" +
"${escapeString(parseStringAttr(row, "name"))}, " +
"${parseWordAttr(row, "price")}, " +
"${parseModifier(row, "bonus-value", "bonus-attribute")}, " +
"${parseByteAttr(row, "number-of-uses")})")
def name = parseStringAttr(row, "name")
def price = parseWordAttr(row, "price")
def modifier = parseModifier(row, "bonus-value", "bonus-attribute")
def kind = parseStringAttr(row, "ammo-kind")
def count = parseWordAttr(row, "count")
def storeAmount = parseWordAttr(row, "store-amount")
def lootAmount = parseDiceAttr(row, "loot-amount")
if ("$kind, $modifier, $count, $storeAmount, $lootAmount" != ", NULL, 0, 0, 0")
out.println(" return makeFancyItem(${escapeString(name)}, $price, " +
"${escapeString(kind)}, $modifier, $count, $storeAmount, $lootAmount)")
else
out.println(" return makePlainItem(${escapeString(name)}, $price)")
}
def genPlayer(func, row, out)
@ -2688,7 +2834,7 @@ end
def itemFunc = itemNameToFunc[name]
assert itemFunc : "Can't locate item '$name'"
if (num > 1)
out.println(" addToList(@p=>p_items, setStuffCount(itemScripts()=>$itemFunc(), $num))")
out.println(" addToList(@p=>p_items, setItemCount(itemScripts()=>$itemFunc(), $num))")
else
out.println(" addToList(@p=>p_items, itemScripts()=>$itemFunc())")
}
@ -2849,24 +2995,22 @@ def makeWeapon_pt2(p, attack0, attack1, attack2, weaponRange, combatText, single
return p
end
def makeStuff(name, kind, price, count, storeAmount, lootAmount)
word p; p = mmgr(HEAP_ALLOC, TYPE_STUFF)
def makePlainItem(name, price)
word p; p = mmgr(HEAP_ALLOC, TYPE_PLAIN_ITEM)
p=>s_name = mmgr(HEAP_INTERN, name)
p=>s_itemKind = mmgr(HEAP_INTERN, kind)
p=>w_price = price
p=>w_count = count
p=>w_storeAmount = storeAmount
p=>r_lootAmount = lootAmount
return p
end
def makeItem(name, price, modifier, maxUses)
word p; p = mmgr(HEAP_ALLOC, TYPE_ITEM)
def makeFancyItem(name, price, kind, modifiers, count, storeAmount, lootAmount)
word p; p = mmgr(HEAP_ALLOC, TYPE_FANCY_ITEM)
p=>s_name = mmgr(HEAP_INTERN, name)
p=>w_price = price
p=>p_modifiers = modifier
p->b_maxUses = maxUses
p->b_curUses = 0
p=>s_itemKind = mmgr(HEAP_INTERN, kind)
p=>p_modifiers = modifiers
p=>w_count = count
p=>w_storeAmount = storeAmount
p=>r_lootAmount = lootAmount
return p
end
@ -2880,7 +3024,7 @@ end
switch (typeName) {
case "weapon": genWeapon(func, row, out); break
case "armor": genArmor(func, row, out); break
case "ammo": genAmmo(func, row, out); break
case "ammo": genItem(func, row, out); break
case "item": genItem(func, row, out); break
default: assert false
}
@ -2993,11 +3137,11 @@ def makePlayer_pt2(p, health, level, aiming, handToHand, dodging, gender)#1
return p
end
def setStuffCount(p, ct)#1
if p->t_type == TYPE_STUFF
def setItemCount(p, ct)#1
if p->t_type == TYPE_FANCY_ITEM
p->w_count = ct
else
fatal(\"stuffct\")
fatal(\"itemct\")
fin
return p // for chaining
end
@ -3088,7 +3232,7 @@ end
return [name.trim(), animFrameNum, animFlags]
}
def dataGen(xmlPath, dataIn)
def dataGen(xmlFile, dataIn)
{
// When generating code, we need to use Unix linebreaks since that's what
// the PLASMA compiler expects to see.
@ -3141,6 +3285,9 @@ end
// Before we can generate global script code, we need to identify and number all the maps.
numberMaps(dataIn)
// Assign a number to each game flag
numberGameFlags(dataIn)
// Form the translation from item name to function name (and ditto
// for players)
allItemFuncs(dataIn.global.sheets.sheet)
@ -3152,6 +3299,9 @@ end
genAllGlobalScripts(dataIn.global.scripts.script)
curMapName = null
// Generate a mapping of flags, for debugging purposes.
genAllFlags()
// Translate enemies, weapons, etc. to code
genAllItems(dataIn.global.sheets.sheet)
genAllEnemies(dataIn.global.sheets.sheet.find { it?.@name.equalsIgnoreCase("enemies") })
@ -3206,7 +3356,7 @@ end
def createHddImage()
{
println "Creating hdd image."
//println "Creating hdd image."
// Copy the combined core executable to the output directory
copyIfNewer(new File("build/src/core/build/LEGENDOS.SYSTEM.sys#2000"),
@ -3231,7 +3381,7 @@ end
def createFloppyImages()
{
println "Creating floppy images."
//println "Creating floppy images."
// We'll be copying stuff from the hdd directory
def hddDir = new File("build/root")
@ -3442,6 +3592,7 @@ end
out << "include \"../plasma/gamelib.plh\"\n"
out << "include \"../plasma/globalDefs.plh\"\n"
out << "include \"../plasma/playtype.plh\"\n"
out << "include \"../plasma/gen_flags.plh\"\n"
out << "include \"../plasma/gen_images.plh\"\n"
out << "include \"../plasma/gen_items.plh\"\n"
out << "include \"../plasma/gen_modules.plh\"\n"
@ -3929,14 +4080,16 @@ end
def packGetFlag(blk)
{
def name = getSingle(blk.field, 'NAME').text()
out << "getGameFlag(${escapeString(name)})"
def name = getSingle(blk.field, 'NAME').text().trim().toLowerCase()
assert gameFlags.containsKey(name)
out << "getGameFlag(GF_${humanNameToSymbol(name, true)})"
}
def packChangeFlag(blk)
{
def name = getSingle(blk.field, 'NAME').text()
outIndented("setGameFlag(${escapeString(name)}, ${blk.@type == 'interaction_set_flag' ? 1 : 0})\n")
def name = getSingle(blk.field, 'NAME').text().trim().toLowerCase()
assert gameFlags.containsKey(name)
outIndented("setGameFlag(GF_${humanNameToSymbol(name, true)}, ${blk.@type == 'interaction_set_flag' ? 1 : 0})\n")
}
def isStringExpr(blk)
@ -4009,6 +4162,10 @@ end
}
}
def packLogicNegate(blk) {
out << "!("; packExpr(getSingle(blk.value, "BOOL").block[0]); out << ")"
}
def packMathArithmetic(blk)
{
def op = getSingle(blk.field, "OP").text()
@ -4078,6 +4235,9 @@ end
case 'logic_operation':
packLogicOperation(blk)
break
case 'logic_negate':
packLogicNegate(blk)
break
case 'variables_get':
packVarGet(blk)
break

View File

@ -747,15 +747,8 @@ gcHash_chk: !zone
rts
.corrup jmp heapCorrupt
!if DEBUG = 0 {
debugOnly:
jsr inlineFatal : !text "DebugOnly",0
}
; Verify integrity of memory manager structures
memCheck: !zone
!if DEBUG = 0 { jmp debugOnly }
!if DEBUG {
jsr heapCheck ; heap check (if there is one)
ldx #0 ; check main bank
jsr .chk
@ -811,7 +804,6 @@ heapCheck: !zone
cmp heapEndPg ; or >= than end of heap
bcc .tscan
; fall through to heapCorrupt...
} ; if DEBUG
heapCorrupt:
+prWord pTmp
@ -1333,16 +1325,10 @@ aux_dispatch:
!if SANITY_CHECK {
saneStart: !zone {
sta saneEnd+2 ; save cmd num for end-checking
pha
tya
pha
txa
cmp #ADVANCE_ANIMS
beq .skip
pha
jsr saneCheck
pla
tax
pla
tay
+prChr 'M'
lda isAuxCmd
beq +
@ -1359,7 +1345,7 @@ saneStart: !zone {
beq +
+prY
+ pla
rts
.skip rts
}
saneCheck: !zone {
@ -1373,6 +1359,8 @@ saneCheck: !zone {
saneEnd: !zone {
pha
lda #$11 ; self-modified earlier by saneStart
cmp #ADVANCE_ANIMS
beq .skip
cmp #REQUEST_MEMORY
beq .val
cmp #QUEUE_LOAD
@ -1385,17 +1373,9 @@ saneEnd: !zone {
bne .noval
.val +prStr : !text "->",0
+prYX
.noval tya
pha
txa
pha
jsr saneCheck
.noval jsr saneCheck
+prStr : !text "m.",0
pla
tax
pla
tay
pla
.skip pla
rts
}
}
@ -2787,6 +2767,7 @@ advSingleAnim:
rts
.dbgout +crout
+waitKey
bit $c050
sta setAuxRd
sta setAuxWr
rts
@ -2828,12 +2809,14 @@ applyPatch:
; loop to skip patches until we find the right one
- dec reqLen ; it starts at 1, which means first patch.
beq +
ldy #1
ldy #0
lda (pSrc),y ; low byte of patch len
pha
iny
lda (pSrc),y ; hi byte of patch len
inx ; -> pSrc+1
jsr .ptradd ; skip by # pages in patch
dey
lda (pSrc),y ; low byte of patch len
pla ; get lo byte of len back
jsr .srcadd ; skip pSrc past last partial page in patch
jmp -
+ !if DEBUG = 2 { jsr .dbgC2 }

View File

@ -132,7 +132,7 @@ def rollPlayerHit(pPlayer, pWeapon, pEnemy, sAction)
roll = rollPercentileWithLuck(-(pPlayer->b_luck)) // luck can reduce roll = increase chance to hit
if combatDebug; displayf2("Roll=%d, need <%d\n", roll, abs(roll < chance)); getUpperKey(); fin
if roll >= chance
setPlural(0)
isPlural = FALSE
displayf3("\n%s %s at %s but misses.\n", pPlayer=>s_name, sAction, pEnemy=>s_name)
return FALSE
fin
@ -143,7 +143,7 @@ end
def rollEnemyDodge(pPlayer, pEnemy, sAction)
// Enemy chance to dodge is taken from their chance to hit divided by 2
if (rand16() % 100) < (pEnemy->b_chanceToHit / 2)
setPlural(0)
isPlural = FALSE
displayf3("\n%s %s at %s, ", pPlayer=>s_name, sAction, pEnemy=>s_name)
displayf1("but %s dodges.\n", pEnemy=>s_name)
return TRUE
@ -157,7 +157,7 @@ def damageEnemy(pPlayer, pEnemy, dmg, sAction)#0
displayf3("\nenemy health: %d-%d=%d\n", pEnemy=>w_health, dmg, pEnemy=>w_health-dmg)
getUpperKey
fin
setPlural(0)
isPlural = FALSE
buildString(@addToString)
printf3("\n%s %s %s ", pPlayer=>s_name, sAction, pEnemy=>s_name)
printf1("for %d damage.", dmg)
@ -290,7 +290,7 @@ def reload(pl, pWeapon, echo)#0
word item
byte orig, n
setPlural(FALSE)
isPlural = FALSE
// If ammo type is null, it means weapon doesn't use ammo in traditional sense.
if !pWeapon=>s_ammoKind
@ -301,7 +301,7 @@ def reload(pl, pWeapon, echo)#0
// Find matching ammo
item = pl=>p_items
while item
if item->t_type == TYPE_STUFF
if item->t_type == TYPE_FANCY_ITEM
if streqi(item=>s_itemKind, pWeapon=>s_ammoKind); break; fin
fin
item = item=>p_nextObj
@ -366,7 +366,7 @@ def displayOpponents()#0
fin
first = FALSE
count = countListFiltered(p=>p_enemies, p_nextObj, @canFight)
setPlural(count <> 1)
isPlural = count <> 1
if (p=>p_enemies=>r_groupSize == 0)
displayf2("%s at %d'", p=>p_enemies=>s_name, p->b_enemyGroupRange)
else
@ -678,7 +678,7 @@ def enemyCombatTurn(pe)#1
pl = randomFromListFiltered(global=>p_players, p_nextObj, @canFight)
if !pl; return FALSE; fin
setPlural(FALSE)
isPlural = FALSE
displayf3("\n%s %s %s ", pe=>s_name, pe=>s_attackText, pl=>s_name)
// Roll to hit
@ -851,7 +851,7 @@ def addItem(pl, pItem)#1
word pComp
pComp = scanForNamedObj(pl=>p_items, pItem=>s_name)
if pComp
if pItem->t_type == TYPE_STUFF
if pItem->t_type == TYPE_FANCY_ITEM
pComp=>w_count = min(30000, pComp=>w_count + pItem=>w_count)
return TRUE
else
@ -882,16 +882,17 @@ def collectLootAndXP()#2
mmgr(FINISH_LOAD, 0)
itemFunc = randomFromArray(pItemsModule()=>items_forLootCode(enemy=>s_lootCode))
pItem = itemFunc()
if pItem->t_type == TYPE_STUFF
if pItem->t_type == TYPE_FANCY_ITEM
pItem=>w_count = rollDiceWithLuck(pItem=>r_lootAmount, global=>p_players->b_luck)
fin
if addItem(global=>p_players, pItem)
if pItem->t_type == TYPE_STUFF and pItem=>w_count > 1
setPlural(TRUE)
displayf2("You find %d %s! ", pItem=>w_count, pItem=>s_name)
displayStr("You find ")
if pItem->t_type == TYPE_FANCY_ITEM and pItem=>w_count > 1
isPlural = TRUE
displayf2("%d %s! ", pItem=>w_count, pItem=>s_name)
else
setPlural(FALSE)
displayf2("You find %s%s! ", anOrA(pItem=>s_name), pItem=>s_name)
isPlural = FALSE
displayf2("%s%s! ", anOrA(pItem=>s_name), pItem=>s_name)
fin
fin
lootedItem = TRUE
@ -908,6 +909,11 @@ end
def startCombat(mapCode)#1
word p, p2, n, s
// Pre-load some global funcs
mmgr(QUEUE_LOAD, GS_COMBAT_INTRO<<8 | RES_TYPE_MODULE)
mmgr(QUEUE_LOAD, GS_COMBAT_PROMPT<<8 | RES_TYPE_MODULE)
mmgr(QUEUE_LOAD, GS_ENEMY_INTRO<<8 | RES_TYPE_MODULE)
// Setup
isFleeing = FALSE
combatDebug = FALSE
@ -915,6 +921,7 @@ def startCombat(mapCode)#1
// Display portrait of first group
setPortrait(global=>p_enemyGroups=>p_enemies->b_image)
mmgr(FINISH_LOAD, 0)
// Clear keyboard stobe, because while wandering the map, the player may have
// queued up movement keys, which are made obsolete by the surprise of combat.
@ -932,7 +939,7 @@ def startCombat(mapCode)#1
p = global=>p_enemyGroups
while p
n = countList(p=>p_enemies)
setPlural(n <> 1)
isPlural = n <> 1
s = callGlobalFunc(GS_ENEMY_INTRO, 0, 0, 0)
displayf2(s, n, p=>p_enemies=>s_name)
p = p=>p_nextObj

View File

@ -37,6 +37,7 @@ predef _newOrLoadGame(ask)#1
word[] funcTbl = @_saveGame, @_loadGame, @_newOrLoadGame
byte[] game1_filename = "GAME.1.SAVE"
byte[] legendos_filename = "LEGENDOS.SYSTEM"
///////////////////////////////////////////////////////////////////////////////////////////////////
// Definitions used by assembly code
@ -223,15 +224,23 @@ def _rwGame(cmd)#0
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def _saveGame()#1
def saveInternal()#0
// Never save corrupted heap
mmgr(CHECK_MEM, 0)
// Perform garbage collection and record the size of the heap so we can restore it correctly
global=>w_heapSize = mmgr(HEAP_COLLECT, 0) - HEAP_BOTTOM
// Copy data to main memory, and write it out.
showMapName("Saving game...")
memcpy(HEAP_BOTTOM, LOAD_SAVE_BUF, HEAP_SIZE) // LC to low mem
_rwGame(RWTS_WRITE)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def _saveGame()#1
showMapName("Saving game...")
saveInternal()
return 0
end
@ -246,12 +255,12 @@ def loadInternal()#1
p_loaded = LOAD_SAVE_BUF
if p_loaded=>w_heapSize == 0
return FALSE // no game saved yet
fin
if p_loaded=>w_heapSize < 100 or p_loaded=>w_heapSize > HEAP_SIZE
fatal("Corrupt game file.")
elsif p_loaded=>w_heapSize < 100 or p_loaded=>w_heapSize > HEAP_SIZE or p_loaded=>w_typeHash <> typeHash
fatal("Incompatible game file.")
fin
memcpy(LOAD_SAVE_BUF, HEAP_BOTTOM, HEAP_SIZE) // low mem to LC
initHeap(p_loaded=>w_heapSize)
mmgr(CHECK_MEM, 0) // make sure heap is valid
return TRUE
end
@ -270,21 +279,123 @@ def _loadGame()#1
return 0
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def gameExists()#1
word p_loaded
// Load first part of save game into mem... 1 block should be plenty to verify it's real.
if callProRWTS(RWTS_READ | RWTS_OPENDIR, @game1_filename, LOAD_SAVE_BUF, 512) == 0
// If heap size is reasonable and type hash matches, chances are high that it's a real save game.
p_loaded = LOAD_SAVE_BUF
if p_loaded=>w_heapSize >= 100 and p_loaded=>w_heapSize <= HEAP_SIZE and p_loaded=>w_typeHash == typeHash
return TRUE
fin
fin
return FALSE
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def getTextKey()#1
byte key
^$c053
key = getUpperKey()
^$c052
textHome()
^$25 = 20
return key
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def pressAnyKey()#0
puts("\n and press any key to continue.")
getTextKey()
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def reinsert()#0
while TRUE
puts(" Re-insert disk 1")
pressAnyKey()
if callProRWTS(RWTS_READ | RWTS_OPENDIR, @legendos_filename, LOAD_SAVE_BUF, 512) == 0
break
fin
puts("\n ")
beep()
loop
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def importGame()#1
puts("\n Insert disk for import")
pressAnyKey()
if gameExists()
loadInternal()
puts("\n Game imported.")
reinsert()
saveInternal()
return TRUE
fin
puts("\n Not found.")
reinsert()
return FALSE
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def isNameChar(ch)
when ch
is '.'
is ','
is '-'
is '\''
is ' '
return TRUE
wend
if ch >= 'A' and ch <= 'Z'; return TRUE; fin
if ch >= '0' and ch <= '9'; return TRUE; fin
return FALSE
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def isValidName(name)
byte len, c
len = ^name
if !len; return FALSE; fin
if ^(name+1) == ' '; return FALSE; fin // don't allow space at start
while len > 0
len--
name++
c = charToUpper(^name)
if c == ' ' and len == 0; return FALSE; fin // don't allow space at end
if !isNameChar(c); return FALSE; fin
loop
return TRUE
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def getCharacterName()#0
word cursX, cursY
displayStr("\nCharacter name?\n")
cursX, cursY = getCursor()
setWindow(cursY+24, cursY+24+18, cursX+154, cursX+154+62)
clearWindow()
global=>p_players=>s_name = getStringResponse()
setWindow2()
setCursor(cursX, cursY)
while TRUE
displayStr("Character name?\n")
cursX, cursY = getCursor()
setWindow(cursY+24, cursY+24+18, cursX+154, cursX+154+62)
clearWindow()
global=>p_players=>s_name = getStringResponse()
setWindow2()
setCursor(cursX, cursY)
if isValidName(global=>p_players=>s_name); break; fin
displayStr("\nInvalid name.\n\n")
beep()
loop
end
def getCharacterGender()#0
displayStr("\n\nGender? (M/F/N/...) \n")
global=>p_players->c_gender = getUpperKey()
displayChar(global=>p_players->c_gender)
byte gender
repeat
displayStr("\n\nGender? (M/F/N/...) \n")
gender = getUpperKey()
displayChar(gender)
until gender >= 'A' and gender <= 'Z'
global=>p_players->c_gender = gender
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -303,6 +414,7 @@ def newGame()#0
showMapName("New game")
setWindow2()
newGameModule()()
clearWindow()
getCharacterName()
getCharacterGender()
if global=>p_players->b_skillPoints
@ -310,52 +422,30 @@ def newGame()#0
fin
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def gameExists()#1
word p_loaded
// Load first part of save game into mem... 1 block should be plenty to verify it's real.
if callProRWTS(RWTS_READ | RWTS_OPENDIR, @game1_filename, LOAD_SAVE_BUF, 512) == 0
// If heap size is reasonable, assume it's a real save game. (Hash will be checked if the
// user chooses to actually load the game)
p_loaded = LOAD_SAVE_BUF
if p_loaded=>w_heapSize >= 100 and p_loaded=>w_heapSize <= HEAP_SIZE
return TRUE
fin
fin
return FALSE
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def _newOrLoadGame(ask)#1
byte key
if !gameExists()
newGame(); return 1
elsif !ask
loadInternal(); return 0
fin
if !ask
loadInternal()
return 0
fin
textHome()
^$c053
^$25 = 20
puts("\n N)ew game, or L)oad last game? ")
while TRUE
key = getUpperKey()
if key == 'N'
^$c052
newGame()
return 1
elsif key == 'L'
^$c052
if loadInternal()
return 0
fin
fin
textHome()
^$25 = 20
puts("\n Game: N)ew, L)oad, I)mport?")
key = getTextKey()
when key
is 'N'
newGame(); return 1
is 'L'
if loadInternal(); return 0; fin
break
is 'I'
if importGame(); return 0; fin
wend
beep()
loop
return 0

View File

@ -39,6 +39,7 @@ import gamelib
predef countArray(arr)#1
predef countList(p)#1
predef countListFiltered(p, offset, filterFunc)#1
predef crout()#0
predef displayChar(chr)#0
predef displayf1(fmt, arg1)#0
predef displayf2(fmt, arg1, arg2)#0
@ -46,7 +47,6 @@ import gamelib
predef displayStr(str)#0
predef encodeDice(nDice, dieSize, add)#1
predef fatal(msg)#1
predef finalWin()#0
predef finishString(isPlural)#1
predef flipToPage1()#0
predef getCharResponse()#1
@ -65,14 +65,15 @@ import gamelib
predef initPlayerXP(player)#0
predef loadFrameImg(img)#0
predef loadMainFrameImg()#0
predef lookupResourcePart(sectionNum, resourceNum)#1
predef makeModifier(name, value)#1
predef max(a, b)#1
predef memcpy(pSrc, pDst, len)#0
predef min(a, b)#1
predef mmgr(cmd, wordParam)#1
predef moveWayBackward()#1
predef numToPlayer(num)#1
predef parseDec(str)#1
predef parseDecWithDefault(str, default)#1
predef partyHasPlayer(playerName)#1
predef pause(count)#0
predef payGold(amount)#1
@ -93,7 +94,6 @@ import gamelib
predef rawDisplayf3(fmt, arg1, arg2, arg3)#0
predef rawDisplayStr(str)#0
predef rdkey()#1
predef readStr()#1
predef removeFromList(pList, toRemove)#0
predef removePlayerFromParty(playerName)#0
predef rightJustifyNum(num, rightX)#0
@ -116,7 +116,6 @@ import gamelib
predef setMap(is3D, num, x, y, dir)#0
predef setMapWindow()#0
predef setBigWindow()#0
predef setPlural(flg)#0
predef setPortrait(portraitNum)#0
predef setScriptInfo(mapName, trigTbl, wdt, hgt)#0
predef setSky(num)#0
@ -147,6 +146,8 @@ import gamelib
word groundNum
byte portraitNum
word pGodModule
word typeHash
byte isPlural
/////////// Shared string constants //////////////

View File

@ -60,7 +60,6 @@ predef doRender()#0
predef playerDeath()#0
predef startGame(ask)#0
predef showAnimFrame()#0
predef finalWin()#0
predef showParty()#0
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -77,12 +76,12 @@ byte renderLoaded = FALSE
byte texturesLoaded = FALSE
byte textDrawn = FALSE
byte textClearCountdown = 0
byte isPlural = FALSE
byte skipEncounterCheck = FALSE
export byte isPlural = 0 // valid values: 0 or $40
byte inScript = FALSE
export word skyNum = 9
export word groundNum = 10
export byte portraitNum = 1
export byte portraitNum = 0
word triggerOriginX, triggerOriginY
word triggerTbl
@ -97,6 +96,7 @@ word pResourceIndex = NULL
word pGlobalTileset = NULL
byte curMapPartition = 0
export word pGodModule = NULL
export word typeHash = 0
// Queue setMap / teleport / start_encounter, since otherwise script might be replaced while executing
byte q_mapIs3D = 0
@ -405,78 +405,73 @@ export asm finishString(isPlural)#1
sta cswl
lda #$FD
sta cswh
bit fixedRTS; V flag for prev-is-punctuation
ldy #1 ; dest offset in Y
ldx #1 ; source offset in X
clv ; V flag for prev-is-alpha
ldy #0 ; dest offset in Y (will be incremented before store)
ldx #0 ; source offset in X (will be incremented before load)
cpx inbuf
beq + ; only process if string has at least 1 char
bcs .done
+ sty tmp+1 ; offset of last punctuation
beq .done ; failsafe: handle zero-length string
.fetch
lda inbuf,x
cmp #"("
inx
lda inbuf,x ; get next input char
iny
sta inbuf,y ; by default copy the char to output
cmp #"(" ; plural processing triggered by parentheses
bne .notpar
bvs .notpar ; skip paren processing right punctuation
lda tmp ; check isPlural flag
bne .plurpr
- lda inbuf,x ; it's singular, so skip everything in parens
cmp #")"
beq .next
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
dex ; needed for failsafe operation
lda tmp ; set copy flag (V) initially
bne .findsl ; to same as isPlural flag
clv
.findsl ; see if there's a slash within the parens
inx
cpx inbuf
bne -
beq .done ; handle missing trailing paren
.plurpr
inx ; it's plural, so copy everything within the parens
lda inbuf,x ; copy characters
cpx inbuf ; handle missing trailing paren
beq +
bcs .store
+ cmp #")" ; go until we reach ending paren
beq .next
sta inbuf,y
iny
bne .plurpr ; always taken
.notpar
lda inbuf,x
cmp #"/"
bne .notsl
bvs .notsl ; skip slash processing right after punctuation
lda tmp ; check isPlural flag
bne .plursl
- inx ; loop that skips plural form
cpx inbuf
bne +
php
pla
eor #$40 ; flip V flag, meaning singular text is before slash, plural after.
pha
plp
+ cmp #")" ; scan until ending paren
beq +
bcs .done ; handle end of string
+ lda inbuf,x
cmp #"A" ; eat letters (and stop when we hit punctuation)
bcs -
bcc .store ; copy the ending punctuation and continue normal processing
.plursl
ldy tmp+1 ; erase singular form by backing up to prev punc
iny ; plus 1 to retain prev punc
bne .next ; resume regular copying of the plural form
.notsl
cmp #"A" ; if <= ASCII "A", consider it punctuation
bcc +
clv ; clear last-is-punc flag
bvc .store ; always taken
+ bit fixedRTS; set prev-is-punc flag
sty tmp+1 ; save dest offset of last punctuation
.store
sta inbuf,y ; save to dest
cpx inbuf
bcc .findsl ; loop to scan next char
bcs .done ; failsafe: handle missing end-paren (always taken)
+ 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
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
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
inx
cpx inbuf ; compare src offset to length
bcc .fetch ; loop while less than
beq .fetch ; or equal
.done
dey
sty inbuf ; save new length
lda #<inbuf ; return pointer to string
ldy #>inbuf
@ -729,7 +724,7 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Print a carriage return
asm crout()#0
export asm crout()#0
+asmPlasmNoRet 0
lda #$8D
jmp _safeCout
@ -743,28 +738,6 @@ export asm beep()#0
rts
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Read a string from the keyboard, turn it into a PLASMA string and return a pointer to the string.
export asm readStr()#1
+asmPlasmRet 0
bit setROM
jsr ROM_getln1
bit setLcRW+lcBank2
txa
pha
beq +
- lda inbuf-1,x
and #$7F
sta inbuf,x
dex
bne -
+ pla
sta inbuf,x
lda #<inbuf
ldy #>inbuf
rts
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Send a command to the memory manager
export asm mmgr(cmd, wordParam)#1
@ -998,7 +971,8 @@ export asm streqi(a, b)#1
bne -- ; abort on inequality
dex
bne -
ldy #0 ; okay, they're equal. Return non-zero (A is guaranteed to be a character already)
lda #1
ldy #0 ; okay, they're equal. Return 1 (not just any char; so that PLASMA when...is can work)
rts
end
@ -1098,12 +1072,6 @@ export def getStringResponse()#1
return mmgr(HEAP_INTERN, $200)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Setter functions for library use
export def setPlural(flg)#0
isPlural = flg
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Convert signed decimal to string in decimalBuf (@decimalBuf returned)
def convertDec(n)#1
@ -1230,14 +1198,6 @@ export def parseDec(str)#1
return n
end
///////////////////////////////////////////////////////////////////////////////////////////////////
export def parseDecWithDefault(str, default)#1
if ^str == 0
return default
fin
return parseDec(str)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Get a keystroke and convert it to upper case
export def getUpperKey()#1
@ -1316,7 +1276,7 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
export def rollDiceWithLuck(encoded, luck)#1
byte i, nDice, dieSize, add, droll, result
nDice = encoded >> 12
nDice = (encoded >> 12) & $F // must mask off replicated hi-bits
dieSize = (encoded >> 8) & $F
add = encoded & $F
result = add
@ -1331,8 +1291,7 @@ export def rollDiceWithLuck(encoded, luck)#1
droll = min(droll, (rand16() % dieSize) + 1)
fin
fin
add = (rand16() % dieSize) + 1
result = result + add
result = result + droll
next
return result
end
@ -1361,13 +1320,13 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Look up the partition for a resource.
// sectioNum: 1=map2d, 2=map3d, 3=portrait
def lookupResourcePart(sectionNum, resourceNum)#1
export def lookupResourcePart(sectionNum, resourceNum)#1
word ptr
byte n
// Skip to the requested section
// Skip to the requested section (starting just after version num)
ptr = pResourceIndex
while sectionNum > 1
while sectionNum > 0
ptr = ptr + readAuxByte(ptr) + 1
sectionNum--
loop
@ -1381,17 +1340,13 @@ def lookupResourcePart(sectionNum, resourceNum)#1
if curMapPartition > 0; return curMapPartition; fin
return 2
fin
if n < 1 or n > 20; fatal("lkupFail2"); fin
if n < 0 or n > 20; fatal("lkupFail2"); fin // allow zero (e.g. portrait not used so not packed)
return n
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Set the sky color (relevant to 3D display only)
export def setSky(num)#0
// hack for end-game screen
if num == 99
finalWin()
fin
skyNum = num
setColor(0, skyNum)
needRender = TRUE
@ -1405,6 +1360,36 @@ export def setGround(num)#0
needRender = TRUE
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def printVersion()#0
word p, len, cv, ch
if !pResourceIndex; return; fin
cv = ^$25
^$23 = 24 // full height window
^$25 = 22
crout()
^$24 = 25
puts("V ")
setWindow(183, 192, 161, 261)
clearWindow()
setWindow(183, 192, 168, 252)
rawDisplayStr("^YV ")
p = pResourceIndex
len = readAuxByte(p)
while len
p++
ch = readAuxByte(p)
printChar(ch)
displayChar(ch)
len--
loop
rawDisplayStr("^N")
^$23 = 23 // shrink window to protect version num
^$25 = cv-1
crout()
setWindow2()
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Load the Frame Image, and lock it.
export def loadFrameImg(img)#0
@ -1435,6 +1420,9 @@ export def loadFrameImg(img)#0
// And show the first frame of the screen image
showAnimFrame()
// Brand the image with the version number
printVersion()
else
curFullscreenImg = NULL
anyAnims = FALSE
@ -1591,6 +1579,9 @@ export def scriptEvent(event, param)#0
word script
if !nMapScripts; return; fin
if inScript; return; fin // avoid doing scripted events inside other scripts
inScript = TRUE
setWindow2()
textDrawn = FALSE
@ -1605,6 +1596,8 @@ export def scriptEvent(event, param)#0
script(event, param)
fin
next
inScript = FALSE
if textDrawn
textClearCountdown = 3
@ -1650,11 +1643,15 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
export def loadMainFrameImg()#0
loadFrameImg(mapIs3D+2)
if curFullscreenImg
auxMmgr(FREE_MEMORY, curFullscreenImg)
curFullscreenImg = NULL
fin
loadFrameImg(mapIs3D+2)
if curFullscreenImg
auxMmgr(FREE_MEMORY, curFullscreenImg) // we don't allow animated main frames, so save memory
curFullscreenImg = NULL
fin
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -1796,15 +1793,17 @@ def doRender()#0
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Advance one step forward (works for either 3D or 2D maps)
def moveForward()#1
def moveInternal(facingDir, moveDir, beepOK)#1
byte val
word x, y
setDir(moveDir)
val = advance()
setDir(facingDir)
// If not blocked, render at the new position.
if val == 0
beep()
if beepOK and !inScript; beep(); fin // don't beep for scripted moves
else
if !mapIs3D
doRender()
@ -1813,7 +1812,7 @@ def moveForward()#1
fin
fin
// If we're on a new map tile, run leave handlers.
// If we're on a new map tile, run leave handlers from old tile.
if val >= 2
scriptEvent(@S_LEAVE, NULL)
nMapScripts = 0
@ -1825,14 +1824,22 @@ def moveForward()#1
scanScripts(x, y)
if nMapScripts
scriptEvent(@S_ENTER, NULL)
elsif global=>p_encounterZones and !skipEncounterCheck
elsif global=>p_encounterZones
checkEncounter(x, y, FALSE)
fin
elsif val >= 2 and global=>p_encounterZones and !skipEncounterCheck
elsif val >= 2 and global=>p_encounterZones
getPos(@x, @y)
checkEncounter(x, y, FALSE)
fin
return 0
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)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -1847,23 +1854,25 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Move backward one step (3D mode). Also actually works in 2D mode.
def moveBackward()#1
adjustDir(8)
moveForward()
return adjustDir(8)
byte facingDir, moveDir
facingDir = getDir()
moveDir = (facingDir + 8) & 15
moveInternal(facingDir, moveDir, TRUE)
return 0
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Move backward two 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
adjustDir(8)
skipEncounterCheck = TRUE
moveForward()
byte facingDir, moveDir
facingDir = getDir()
moveDir = (facingDir + 8) & 15
moveInternal(facingDir, moveDir, FALSE)
if mapIs3D
moveForward()
moveInternal(facingDir, moveDir, FALSE)
fin
skipEncounterCheck = FALSE
return adjustDir(8)
return 0
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -1885,17 +1894,19 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Sidestep to the right (3D mode)
def strafeRight()#1
adjustDir(4)
moveForward()
return adjustDir(-4)
byte facingDir, moveDir
facingDir = getDir()
moveDir = (facingDir + 4) & 15
return moveInternal(facingDir, moveDir, FALSE)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Sidestep to the left (3D mode)
def strafeLeft()#1
adjustDir(-4)
moveForward()
return adjustDir(4)
byte facingDir, moveDir
facingDir = getDir()
moveDir = (facingDir - 4) & 15
return moveInternal(facingDir, moveDir, FALSE)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -2105,7 +2116,9 @@ export def getYN()#1
if key == 'Y'
return 1
elsif key == 'N'
clearTextWindow()
if frameLoaded
clearTextWindow()
fin
break
fin
beep()
@ -2185,7 +2198,8 @@ export def setPortrait(portraitNum)#0
// Load the portrait image and display it
part = lookupResourcePart(3, portraitNum)
if part > 1; part = curMapPartition; fin // Look on disk 1 or current disk only
// 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
@ -2303,12 +2317,6 @@ def loadEngine(moduleNum)#1
flipToPage1()
mmgr(START_LOAD, 1) // code is in partition 1
curEngine = mmgr(QUEUE_LOAD, moduleNum<<8 | RES_TYPE_MODULE)
// For combat module, pre-load some global funcs
if moduleNum == MOD_COMBAT
mmgr(QUEUE_LOAD, GS_COMBAT_INTRO<<8 | RES_TYPE_MODULE)
mmgr(QUEUE_LOAD, GS_COMBAT_PROMPT<<8 | RES_TYPE_MODULE)
mmgr(QUEUE_LOAD, GS_ENEMY_INTRO<<8 | RES_TYPE_MODULE)
fin
mmgr(FINISH_LOAD, 0)
return curEngine() // return function table
end
@ -2323,7 +2331,12 @@ def returnFromEngine(render)#0
if renderLoaded; texControl(1); texturesLoaded = TRUE; fin
mapNameHash = 0; showMapName(global=>s_mapName)
clearTextWindow()
if render; doRender(); fin
if render
doRender()
else
needRender = TRUE
fin
if mapIs3D; showCompassDir(getDir()); fin
showParty()
setWindow2() // in case we're mid-script
fin
@ -2357,13 +2370,21 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Load the Party engine and show data for the given player
def showPlayerSheet(num)#1
word pItemToUse
word pItemToUse, oldFlg
if num+1 > countList(global=>p_players); beep; return 0; fin
pItemToUse = loadEngine(MOD_PARTY)=>party_showPlayerSheet(num)
returnFromEngine(TRUE)
// General 'use' handled here in case it triggers graphical effects
if pItemToUse
oldFlg = textDrawn
textDrawn = FALSE
scriptEvent(@S_USE, pItemToUse=>s_name)
if !textDrawn
displayStr("\nNothing happened.")
textDrawn = TRUE
else
textDrawn = oldFlg
fin
fin
return 0
end
@ -2391,6 +2412,7 @@ def levelUp()#1
return 0
fin
player = player=>p_nextObj
n++
loop
beep
return 0
@ -2433,13 +2455,14 @@ def doCombat(mapCode, backUpOnFlee)#1
// Handled in a separate module. Clear enemies out of the heap when finished.
result = loadEngine(MOD_COMBAT)=>combat_zoneEncounter(mapCode)
global=>p_enemyGroups = NULL
mmgr(CHECK_MEM, 0)
mmgr(HEAP_COLLECT, 0)
if (result == -99)
playerDeath()
return 0
fin
returnFromEngine(TRUE)
returnFromEngine(!inScript) // only re-render if outside script
// If the party fled the combat instead of winning, back up to previous square.
if !result and backUpOnFlee
@ -2455,6 +2478,9 @@ export def checkEncounter(x, y, force)#0
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
@ -2511,6 +2537,7 @@ def toggleGodMode()#1
if ^kbd == $84 // ctrl-D
^kbdStrobe
global->b_godmode = !global->b_godmode
flipToPage1()
clearTextWindow()
displayf1("gm:%d\n", global->b_godmode & 1)
beep; beep
@ -2521,15 +2548,6 @@ def toggleGodMode()#1
return 0
end
///////////////////////////////////////////////////////////////////////////////////////////////////
export def finalWin()#0
flipToPage1()
loadFrameImg(4) // total hack
while 1 // 1 infinite loop
getUpperKey()
loop
end
///////////////////////////////////////////////////////////////////////////////////////////////////
export def setCmd(key, func)#0
cmdTbl[key] = func
@ -2641,10 +2659,6 @@ def loadTitle()#0
auxMmgr(SET_MEM_TARGET, expandVec)
auxMmgr(QUEUE_LOAD, CODE_EXPAND<<8 | RES_TYPE_CODE)
// Also grab the resource index (put it in aux)
pResourceIndex = auxMmgr(QUEUE_LOAD, CODE_RESOURCE_INDEX<<8 | RES_TYPE_CODE)
auxMmgr(LOCK_MEMORY, pResourceIndex)
mmgr(FINISH_LOAD, 0)
// Tell the font engine where to find its font
@ -2663,13 +2677,20 @@ def loadTitle()#0
auxMmgr(SET_MEM_TARGET, expandVec)
auxMmgr(REQUEST_MEMORY, expanderSize)
auxMmgr(LOCK_MEMORY, expandVec)
// To reduce fragmentation, load the resource index directly after the
// remaining part of the expander
pResourceIndex = expandVec + expanderSize
auxMmgr(SET_MEM_TARGET, pResourceIndex)
auxMmgr(QUEUE_LOAD, CODE_RESOURCE_INDEX<<8 | RES_TYPE_CODE)
auxMmgr(LOCK_MEMORY, pResourceIndex)
mmgr(FINISH_LOAD, 0)
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
word typeHash
if !heapLocked
mmgr(SET_MEM_TARGET, HEAP_BOTTOM)
@ -2687,12 +2708,8 @@ export def initHeap(loadedSize)#0
mmgr(HEAP_ADD_TYPE, typeTbls[i])
i = i+1
loop
typeHash = hashBuffer(@typeTbl_Global, @typeTbls - @typeTbl_Global) ^ HEAP_BOTTOM
if loadedSize <> 0
global = HEAP_BOTTOM
if global=>w_typeHash <> typeHash
fatal("Incompatible saved game")
fin
else
global = mmgr(HEAP_ALLOC, TYPE_GLOBAL)
global=>w_typeHash = typeHash
@ -2763,12 +2780,23 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Add to a list if named obj not already in it; returns TRUE if really added.
// Also handles incrementing stackable items.
export def addUnique(pList, p_thing)#1
if !scanForNamedObj(*pList, p_thing=>s_name)
addToList(pList, p_thing)
return TRUE
word p_existing
// If it's stackable and player already has some, just increase the stack
p_existing = scanForNamedObj(*pList, p_thing=>s_name)
if p_existing
if p_existing->t_type == TYPE_FANCY_ITEM and p_existing=>w_count > 0 and !p_existing=>p_modifiers
p_existing=>w_count = p_existing=>w_count + p_thing=>w_count
return TRUE
fin
return FALSE // already have one, and it's not stackable
fin
return FALSE
// Otherwise, add a new item
addToList(pList, p_thing)
return TRUE
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -2868,7 +2896,8 @@ end
export def addPlayerToParty(playerFuncNum)#0
word p
if countList(global=>p_players) == MAX_PARTY
displayStr("Party too large.")
rawDisplayStr("Party too large.")
beep
return
fin
p = createAndAddUnique(MOD_GEN_PLAYERS, playerFuncNum, @global=>p_players)
@ -2882,7 +2911,12 @@ export def removeNamed(name, pList)#0
word p_thing
p_thing = scanForNamedObj(*pList, name)
if p_thing
removeFromList(pList, 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
else
printf1("Warning: couldn't find '%s' to remove.\n", name)
fin
@ -2912,29 +2946,29 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
export def getStat(player, statName)#1
word pSkill
when statName
is @S_INTELLIGENCE; return player->b_intelligence
is @S_STRENGTH; return player->b_strength
is @S_AGILITY; return player->b_agility
is @S_STAMINA; return player->b_stamina
is @S_CHARISMA; return player->b_charisma
is @S_SPIRIT; return player->b_spirit
is @S_LUCK; return player->b_luck
is @S_HEALTH; return player=>w_health
is @S_MAX_HEALTH; return player=>w_maxHealth
is @S_AIMING; return player->b_aiming
is @S_HAND_TO_HAND; return player->b_handToHand
is @S_DODGING; return player->b_dodging
is @S_GOLD; return global=>w_gold
is @S_XP; return player=>w_curXP
is @S_SP; return player->b_skillPoints
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_XP); return player=>w_curXP
is streqi(statName, @S_SP); return player->b_skillPoints
wend
pSkill = player=>p_skills
while pSkill
if streqi(statName, pSkill=>s_name); return pSkill=>w_modValue; fin
pSkill = pSkill=>p_nextObj
loop
puts(statName); return fatal("Unknown stat")
puts(statName); return fatal("getStat")
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -2953,22 +2987,22 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
export def setStat(player, statName, val)#0
word pSkill
when statName
is @S_INTELLIGENCE; player->b_intelligence = clampByte(val); break
is @S_STRENGTH; player->b_strength = clampByte(val); break
is @S_AGILITY; player->b_agility = clampByte(val); break
is @S_STAMINA; player->b_stamina = clampByte(val); break
is @S_CHARISMA; player->b_charisma = clampByte(val); break
is @S_SPIRIT; player->b_spirit = clampByte(val); break
is @S_LUCK; player->b_luck = clampByte(val); break
is @S_HEALTH; player=>w_health = max(0, min(player=>w_maxHealth, val)); needShowParty = TRUE; break
is @S_MAX_HEALTH; player=>w_maxHealth = max(0, val); break
is @S_AIMING; player->b_aiming = clampByte(val); break
is @S_HAND_TO_HAND; player->b_handToHand = clampByte(val); break
is @S_DODGING; player->b_dodging = clampByte(val); break
is @S_GOLD; global=>w_gold = max(0, val); needShowParty = TRUE; break
is @S_XP; player=>w_curXP = max(player=>w_curXP, val); needShowParty = TRUE; break
is @S_SP; player->b_skillPoints = max(0, val); needShowParty = TRUE; break
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_XP); player=>w_curXP = max(player=>w_curXP, val); needShowParty = TRUE; break
is streqi(statName, @S_SP); player->b_skillPoints = max(0, val); needShowParty = TRUE; break
otherwise
pSkill = player=>p_skills
while pSkill
@ -2978,33 +3012,28 @@ export def setStat(player, statName, val)#0
fin
pSkill = pSkill=>p_nextObj
loop
puts(statName); fatal("Unknown stat")
puts(statName); fatal("setStat")
wend
end
///////////////////////////////////////////////////////////////////////////////////////////////////
export def setGameFlag(flagName, val)#0
word p_flag
p_flag = scanForNamedObj(global=>p_gameFlags, flagName)
if p_flag
if val == 0 // setting flag to zero removes it
removeFromList(@global=>p_gameFlags, p_flag)
else
p_flag=>w_modValue = val
fin
elsif val <> 0
addToList(@global=>p_gameFlags, makeModifier(flagName, val))
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(flagName)#1
word p_flag
p_flag = scanForNamedObj(global=>p_gameFlags, flagName)
if p_flag
return p_flag=>w_modValue
fin
return 0
export def getGameFlag(flagNum)#1
byte byteNum, mask
byteNum = flagNum >> 3
mask = 1<<(flagNum & 7)
return global->ba_gameFlags[byteNum] & mask
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -3044,10 +3073,23 @@ export def buySell(storeCode, profitRatio)#0
if portrait; setPortrait(portrait); fin
end
///////////////////////////////////////////////////////////////////////////////////////////////////
export def numToPlayer(num)#1
word player
player = global=>p_players
while player and num > 0
player = player=>p_nextObj
num--
loop
return player
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def startGame(ask)#0
word p_module
typeHash = hashBuffer(@typeTbl_Global, @typeTbls - @typeTbl_Global) ^ HEAP_BOTTOM
// Create a new game or load an existing one
mmgr(START_LOAD, 1) // code is in partition 1
p_module = mmgr(QUEUE_LOAD, MOD_DISKOPS<<8 | RES_TYPE_MODULE)

View File

@ -16,6 +16,7 @@ include "gen_images.plh"
include "gen_modules.plh"
include "gen_items.plh"
include "gen_players.plh"
include "gen_flags.plh"
// Exported functions go here. First a predef for each one, then a table with function pointers
// in the same order as the constants are defined in the the header.
@ -24,6 +25,56 @@ predef _addItem(player)#1
predef _addPlayer()#1
word[] funcTbl = @_setCheatCmds, @_addItem, @_addPlayer
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"
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Read a string from the keyboard, turn it into a PLASMA string and return a pointer to the string.
asm readStr()#1
+asmPlasmRet 0
bit setROM
jsr ROM_getln1
bit setLcRW+lcBank2
txa
pha
beq +
- lda inbuf-1,x
and #$7F
sta inbuf,x
dex
bne -
+ pla
sta inbuf,x
lda #<inbuf
ldy #>inbuf
rts
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def parseDecWithDefault(str, default)#1
if ^str == 0
return default
fin
return parseDec(str)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def splitScreenMode()#0
flipToPage1()
textHome()
^$c053
^$25 = 19
crout()
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Godmode cheats
@ -32,12 +83,10 @@ def kbdTeleport()#1
word x, y
byte dir
flipToPage1()
^$c053
if ^$25 < 23; ^$25 = 23; fin
splitScreenMode()
getPos(@x, @y)
dir = getDir()
printf3("\nCurrent: X=%d Y=%d Facing=%d\n", x, y, dir)
printf3("Current: X=%d Y=%d Facing=%d\n", x, y, dir)
printf1("3D [%d]: ", mapIs3D)
d3 = parseDecWithDefault(readStr(), mapIs3D)
@ -62,11 +111,9 @@ def showPos()#1
word x, y
byte dir
flipToPage1()
^$c053
if ^$25 < 23; ^$25 = 23; fin
splitScreenMode()
getPos(@x, @y)
printf2("\nX=%d Y=%d ", x, y)
printf2("X=%d Y=%d ", x, y)
if mapIs3D
printf3("Facing=%d Sky=%d Ground=%d", getDir(), skyNum, groundNum)
fin
@ -77,19 +124,28 @@ def showPos()#1
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def nextPortrait()#1
portraitNum = portraitNum + 1
if portraitNum > PO_LAST; portraitNum = 1; fin
def advPortrait(dir)#1
while TRUE
portraitNum = portraitNum + dir
if portraitNum > PO_LAST
portraitNum = 1
elsif portraitNum < 1
portraitNum = PO_LAST
fin
if lookupResourcePart(3, portraitNum); break; fin
loop
setPortrait(portraitNum)
return 0
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def nextPortrait()#1
return advPortrait(1)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def prevPortrait()#1
portraitNum = portraitNum - 1
if portraitNum < 1; portraitNum = PO_LAST; fin
setPortrait(portraitNum)
return 0
return advPortrait(-1)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -126,6 +182,45 @@ def nextGround()#1
return 0
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Edit flags cheat
def editFlags()#1
word pModule, funcTbl, flagName, flagNum
flipToPage1
^$c051
textHome()
*$4000 = 0 // so renderer knows we messed up the page
puts("Game flags:")
mmgr(START_LOAD, 1) // code is in partition 1
pModule = mmgr(QUEUE_LOAD, MOD_GEN_FLAGS<<8 | RES_TYPE_MODULE)
mmgr(FINISH_LOAD, 0)
funcTbl = pModule()
while TRUE
^$25 = 0
crout()
for flagNum = 0 to NUM_GAME_FLAGS-1
if flagNum > 0; puts(", "); fin
flagName = funcTbl=>flags_nameForNumber(flagNum)
// make sure flag name will fit on line
if ^$24 + ^flagName > 37; crout(); fin
^$32 = $FF // normal mode
if getGameFlag(flagNum); ^$32 = $3F; fin // inverse mode
puts(flagName)
^$32 = $FF
next
puts("\nToggle: ")
^$24 = 8
flagName = readStr()
if !^flagName; break; fin
flagNum = funcTbl=>flags_numberForName(flagName)
if flagNum < 0; beep; continue; fin
setGameFlag(flagNum, !getGameFlag(flagNum))
loop
^$c050
mmgr(FREE_MEMORY, pModule)
return 0
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def _setCheatCmds()#1
// install cheat commands
@ -137,7 +232,7 @@ def _setCheatCmds()#1
setCmd('Y', @nextSky)
setCmd('G', @nextGround)
setCmd('&', @printMem)
setCmd('_', @finalWin)
setCmd('^', @editFlags)
return 0
end
@ -176,10 +271,14 @@ def selectThing(moduleNum, nThings, nSkip, prompt)#1
nFunc = -1
textHome()
fin
elsif moduleNum == MOD_GEN_PLAYERS
// Players are big, so need collect each time
mmgr(HEAP_COLLECT, 0)
fin
next
mmgr(FREE_MEMORY, pModule)
^$c050
return nFunc
end
@ -188,11 +287,7 @@ end
def _addItem(player)#1
word funcNum
funcNum = selectThing(MOD_GEN_ITEMS, NUM_ITEMS, 2, "Add item #: ")
if funcNum >= 0
giveItemToPlayer(player, funcNum)
rdkey()
fin
^$c050
if funcNum >= 0; giveItemToPlayer(player, funcNum); fin
return 0
end
@ -201,11 +296,7 @@ end
def _addPlayer()#1
word funcNum
funcNum = selectThing(MOD_GEN_PLAYERS, NUM_PLAYERS, 1, "Add player #: ")
if funcNum >= 0
addPlayerToParty(funcNum)
rdkey()
fin
^$c050
if funcNum >= 0; addPlayerToParty(funcNum); fin
return 0
end

View File

@ -68,6 +68,14 @@ def travFind2(pObj, val, func)
return NULL
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def isEquipped(pItem)#1
if pItem->t_type == TYPE_ARMOR or pItem->t_type == TYPE_WEAPON
return pItem->b_flags & ITEM_FLAG_EQUIP
fin
return FALSE
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def itemByNum(player, num)#1
return travFind2(player=>p_items, num, &(n,p)(n-1, n==0))
@ -81,7 +89,7 @@ def unequip(player, type, kind)#1
item = player=>p_items
while item
if item->t_type == type
if (streqi(item=>s_itemKind, kind) or type == TYPE_WEAPON) and item->b_flags & ITEM_FLAG_EQUIP
if (streqi(item=>s_itemKind, kind) or type == TYPE_WEAPON) and isEquipped(item)
item->b_flags = item->b_flags & ~ITEM_FLAG_EQUIP
break
fin
@ -97,7 +105,7 @@ end
def showColumnTitle(x, title, page, nPages)#0
rawDisplayf2("^V000\n^J^J^L^J^T%D%s", x, title)
if nPages > 1
rawDisplayf2(" - p.%d/%d", page, nPages)
rawDisplayf2(" p.%d/%d", page, nPages)
fin
rawDisplayStr("^N\n^J^J")
end
@ -125,10 +133,8 @@ def showInventory(player, page, select)#1
if !first; displayChar('\n'); fin
first = FALSE
if item->t_type == TYPE_WEAPON or item->t_type == TYPE_ARMOR
if item->b_flags & ITEM_FLAG_EQUIP
rawDisplayStr("*")
fin
if isEquipped(item)
rawDisplayStr("*")
fin
rawDisplayf2("^T%D%c.", INVLBL_X, 'A' + s_item)
@ -142,17 +148,6 @@ def showInventory(player, page, select)#1
return s_item
end
def numToPlayer(num)#1
word player
player = global=>p_players
while num > 0
player = player=>p_nextObj
if !player; break; fin // Not that many players
num--
loop
return player
end
def displayDice(dice)#0
byte n, d, p
n = (dice >> 12) & $0F
@ -196,7 +191,7 @@ def showDerived(player)#0
// Get weapon
weapon = player=>p_items
while weapon
if weapon->t_type == TYPE_WEAPON and weapon->b_flags & ITEM_FLAG_EQUIP; break; fin
if weapon->t_type == TYPE_WEAPON and isEquipped(weapon); break; fin
weapon = weapon=>p_nextObj
loop
@ -339,12 +334,8 @@ def showInvMenu(player, totalItems, itemPage, itemsOnPage)#0
rawDisplayf1("Item [A-%c], ", itemsOnPage-1+'A')
if totalItems > INV_ROWS
rawDisplayStr("Pg [")
if totalItems > (itemPage + 1) * INV_ROWS
rawDisplayStr(">")
fin
if itemPage
rawDisplayStr("<")
fin
if totalItems > (itemPage + 1) * INV_ROWS; rawDisplayStr(">"); fin
if itemPage; rawDisplayStr("<"); fin
rawDisplayStr("], ")
fin
fin
@ -372,18 +363,37 @@ def showSkillsMenu()#0
rawDisplayStr("X:Inv or [Esc]")
end
def isSplittable(item)#1
// Disallow splitting items with modifiers, because too edge-casey
return item->t_type == TYPE_FANCY_ITEM and item=>w_count > 1 and !item=>p_modifiers
end
def isJoinable(item)#1
// Disallow joining items with modifiers, because too edge-casey
return item->t_type == TYPE_FANCY_ITEM and item=>w_count > 0 and !item=>p_modifiers
end
// Display menu for selecting inventory items
def showItemMenu(item)#0
byte type
clearMenuRect()
type = item->t_type
if type == TYPE_ARMOR or type == TYPE_WEAPON
rawDisplayStr("E)quip/unequip, ")
fin
if type == TYPE_ITEM
if isEquipped(item)
rawDisplayStr("U)nequip, ")
else
rawDisplayStr("E)quip, ")
fin
elsif type == TYPE_PLAIN_ITEM or type == TYPE_FANCY_ITEM
rawDisplayStr("U)se, ")
if isSplittable(item); rawDisplayStr("S)plit, "); fin
if isJoinable(item); rawDisplayStr("J)oin, "); fin
fin
rawDisplayStr("D)estroy or [Esc]")
if !isEquipped(item)
if global=>p_players=>p_nextObj; rawDisplayStr("T)rade, "); fin
rawDisplayStr("D)rop ")
fin
rawDisplayStr("or [Esc]")
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -398,6 +408,7 @@ def clearMainRect()#0
setWindow(BIGWIN_TOP+9, BIGWIN_BOTTOM-10, BIGWIN_LEFT, BIGWIN_RIGHT)
clearWindow()
setBigWindow()
rawDisplayStr("^V000\n^J^J^J")
end
// Equip/unequip an item.
@ -408,26 +419,151 @@ def doEquip(player, item)#0
calcPlayerArmor(player)
end
def choosePlayer(disp, chooseNum, avoid)
word player
byte num
player = global=>p_players
num = 0
while player
if player <> avoid
if num; rawDisplayStr(", "); fin
if disp
rawDisplayf2("%c) %s", num+'A', player=>s_name)
elsif num == chooseNum
return player
fin
num++
fin
player = player=>p_nextObj
loop
return num
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def displayDone()#1
clearMenuRect()
clearMainRect()
rawDisplayStr("Done.")
pause(800)
return NULL
end
// Trade an item to another player/npc
def doTrade(player, item)#1
word destPlayer
byte first, sel
clearMenuRect()
rawDisplayStr("To: ")
destPlayer = global=>p_players
sel = 0
first = TRUE
while destPlayer
if player <> destPlayer
if !first; rawDisplayStr(", "); fin
first = FALSE
rawDisplayf2("%d. %s", sel+1, destPlayer=>s_name)
fin
sel++
destPlayer = destPlayer=>p_nextObj
loop
rawDisplayStr(" or [Esc]")
while TRUE
sel = getUpperKey()
if sel == $1B; return 0; fin
destPlayer = numToPlayer(sel-'1')
if destPlayer and destPlayer <> player
removeFromList(@player=>p_items, item)
addUnique(@destPlayer=>p_items, item)
return displayDone
fin
beep
loop
return 0
end
// Split a stack of stackables
def doSplit(player, item)#1
word nToSplit, newItem
if item=>w_count == 2
nToSplit = 1
else
clearMenuRect
rawDisplayf1("Split off 1-%d: ", item=>w_count - 1)
nToSplit = parseDec(getStringResponse())
fin
if nToSplit
item=>w_count = item=>w_count - nToSplit
newItem = mmgr(HEAP_ALLOC, TYPE_FANCY_ITEM)
memcpy(item, newItem, FancyItem)
newItem=>w_count = nToSplit
newItem=>p_nextObj = item=>p_nextObj
item=>p_nextObj = newItem
return 1
fin
beep
return 0
end
// Join a stack of stackables
def doJoin(player, item)#1
word match, pPrev
byte anyJoined
pPrev = @player=>p_items
match = player=>p_items
anyJoined = FALSE
while match
if match <> item and match->t_type == TYPE_FANCY_ITEM
if streqi(match=>s_name, item=>s_name)
item=>w_count = item=>w_count + match=>w_count
*pPrev = match=>p_nextObj
match = match=>p_nextObj
anyJoined = TRUE
continue
fin
fin
pPrev = @match=>p_nextObj
match = *pPrev
loop
if anyJoined
displayDone()
return 1
fin
clearMenuRect()
rawDisplayStr("No joinable stack found.")
beep
pause(800)
return 0
end
// Select an item and use it. Returns item if it needs to be processed by outer loop, else NULL
def doUse(player, item)#1
if item=>p_modifiers
if streqi(item=>p_modifiers=>s_name, @S_HEALTH)
clearMenuRect()
clearMainRect()
rawDisplayStr("^V000\n^J^J^J")
if player=>w_health < player=>w_maxHealth
player=>w_health = min(player=>w_health + item=>p_modifiers=>w_modValue, player=>w_maxHealth)
item->b_curUses++
if item->b_curUses >= item->b_maxUses // all used up
removeFromList(@player=>p_items, item)
word pMod, oldVal, newVal
if item->t_type == TYPE_FANCY_ITEM and item=>p_modifiers
clearMenuRect()
clearMainRect()
pMod = item=>p_modifiers
while pMod
oldVal = getStat(player, pMod=>s_name)
setStat(player, pMod=>s_name, oldVal + pMod=>w_modValue)
newVal = getStat(player, pMod=>s_name)
rawDisplayStr(pMod=>s_name)
if newVal <> oldVal
takeItemFromPlayer(player, item=>s_name) // also handles reducing count of stackables
if newVal > oldVal
rawDisplayStr(" increased")
else
rawDisplayStr(" decreased")
fin
rawDisplayf2("Healed to %d/%d", player=>w_health, player=>w_maxHealth)
rawDisplayf2(" from %d to %d.", oldVal, newVal)
else
rawDisplayStr("No healing needed.")
rawDisplayStr(" already at the limit.")
fin
pause(800)
return NULL
fin
pMod = pMod=>p_nextObj
loop
return NULL
fin
return item // general 'use' handled by outer engine, because it might involve graphics
end
@ -435,7 +571,7 @@ end
// Select an item and drop it. Returns TRUE if anything changed
def doDestroy(player, item)#1
clearMenuRect()
rawDisplayStr("Destroy ")
rawDisplayStr("Drop ")
_displayItemName(item)
rawDisplayStr(" (Y/N)?")
if getYN()
@ -451,11 +587,9 @@ def matchEquipped(player, match)#1
word item
item = player=>p_items
while item
if (item->t_type == match->t_type) and (item->t_type == TYPE_WEAPON or item->t_type == TYPE_ARMOR)
if item <> match and (item->b_flags & ITEM_FLAG_EQUIP)
if item->t_type <> TYPE_ARMOR or (item=>s_itemKind == match=>s_itemKind)
return item
fin
if item->t_type == match->t_type and item <> match and isEquipped(item)
if item->t_type <> TYPE_ARMOR or (item=>s_itemKind == match=>s_itemKind)
return item
fin
fin
item = item=>p_nextObj
@ -466,7 +600,7 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
def displayItems(pItem1, pItem2)#0
clearMainRect()
rawDisplayf1("^V000\n^J^J^L^J^T%DInventory", STATS_COL_1)
rawDisplayf1("^T%DInventory", STATS_COL_1)
if pItem2
rawDisplayf1("^T%DEquipped", STATS_COL_2)
fin
@ -477,37 +611,69 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
def interactWithItem(player, item)#1
word comp, quantity
byte sel
byte sel, ok
ok = TRUE
displayItems(item, matchEquipped(player, item))
while TRUE
displayItems(item, matchEquipped(player, item))
showItemMenu(item)
if ok
showItemMenu(item)
else
beep
fin
ok = FALSE
sel = getUpperKey()
rawDisplayf1(" %c\n", sel)
when sel
// Equip/unequip player with weapon/armor
// Equip player with weapon/armor
is 'E'
if item->t_type == TYPE_ARMOR or item->t_type == TYPE_WEAPON
if (item->t_type == TYPE_ARMOR or item->t_type == TYPE_WEAPON) and !isEquipped(item)
doEquip(player, item)
return NULL
else
beep
return displayDone()
fin
break
// Use an item
// Use or Unequip an item
is 'U'
if item->t_type == TYPE_ITEM
if item->t_type == TYPE_PLAIN_ITEM or item->t_type == TYPE_FANCY_ITEM
return doUse(player, item) // general 'use' handled by outer engine, because it might involve graphics
else
beep
elsif (item->t_type == TYPE_ARMOR or item->t_type == TYPE_WEAPON) and isEquipped(item)
doEquip(player, item)
displayItems(item, matchEquipped(player, item))
ok = TRUE
fin
break
// Trade an item
is 'T'
if global=>p_players=>p_nextObj and !isEquipped(item)
return doTrade(player, item)
fin
break
// Split a stack
is 'S'
if isSplittable(item)
if doSplit(player, item)
return NULL
fin
fin
break
// Join stacks
is 'J'
if isJoinable(item)
if doJoin(player, item)
return NULL
fin
fin
break
// Destroy an item
is 'D'
if doDestroy(player, item); return NULL; fin
if !isEquipped(item)
if doDestroy(player, item)
displayDone()
return NULL
fin
ok = TRUE
fin
break
is $1B // Esc
return NULL
otherwise beep
wend
loop
return NULL
@ -538,18 +704,18 @@ def _showPlayerSheet(player_num)#1 // funcTbl functions always have to return a
rawDisplayf1("^Y^I %s ^N\n", player=>s_name)
redisplay = 1
totalItems = countList(player=>p_items)
elsif redisplay > 0
clearInvRect()
fin
if redisplay > 0
if mode == 'I'
itemsOnPage = showInventory(player, i_page, 0)
showDerived(player)
else // 'S'
showSkills(player)
fin
redisplay = 0
elsif redisplay > 0
clearInvRect()
fin
if redisplay > 0 and mode == 'I'
itemsOnPage = showInventory(player, i_page, 0)
fin
redisplay = 0
if !noRepeatMenu
if mode == 'I'
@ -563,22 +729,24 @@ def _showPlayerSheet(player_num)#1 // funcTbl functions always have to return a
// Get a key, do something
sel = getUpperKey()
when sel
is '1'
is '2'
is '3'
is '1'; is '2'; is '3'
sel = sel - '1'
if player_num <> sel and countList(global=>p_players) > sel
if countList(global=>p_players) > sel
player_num = sel
i_page = 0
redisplay = 2
else
beep
fin
break
// Next inventory page
is '>'
if mode=='I' and totalItems > (i_page + 1) * INV_ROWS
i_page++
redisplay = 1
else
beep
noRepeatMenu = TRUE
fin
break
// Previous inventory page
@ -587,6 +755,9 @@ def _showPlayerSheet(player_num)#1 // funcTbl functions always have to return a
if mode=='I' and i_page
i_page--
redisplay = 1
else
beep
noRepeatMenu = TRUE
fin
break
// Other operations...
@ -604,13 +775,16 @@ def _showPlayerSheet(player_num)#1 // funcTbl functions always have to return a
break
is '%' // add item cheat
if global->b_godmode
clearMainRect()
pGodModule=>godmode_addItem(player)
redisplay = 1
redisplay = 2
fin
break
is '9' // add player cheat
if global->b_godmode
clearMainRect()
pGodModule=>godmode_addPlayer()
redisplay = 2
fin
break
is '+' // level up cheat
@ -620,24 +794,30 @@ def _showPlayerSheet(player_num)#1 // funcTbl functions always have to return a
fin
break
is $1B // Esc
mmgr(CHECK_MEM, 0)
mmgr(HEAP_COLLECT, 0)
return NULL
otherwise
if sel == 'X' and mode <> 'I'
if sel == 'X' and mode <> 'I' // switch from stats to inv
mode = 'I'
redisplay = 2
elsif (sel == 'S' or sel == 'U') and mode <> 'S'
elsif (sel == 'S' or sel == 'U') and mode <> 'S' // switch from inv to stats
mode = 'S'
redisplay = 2
elsif mode == 'I'
sel = sel - 'A'
if sel >= 0 and sel < itemsOnPage
item = interactWithItem(player, itemByNum(player, sel))
item = interactWithItem(player, itemByNum(player, i_page * INV_ROWS + sel))
if item; return item; fin // Use an item
if countList(player=>p_items) <= i_page * INV_ROWS // destroyed last item on pg 2
i_page--
fin
redisplay = 2
else
beep
noRepeatMenu = TRUE
fin
elsif mode == 'S'
else // mode == 'S'
noRepeatMenu = TRUE
if sel >= 'A' and (sel-'A' < nSkills)
adjustSkill(player, sel - 'A', 1)
@ -647,12 +827,10 @@ def _showPlayerSheet(player_num)#1 // funcTbl functions always have to return a
beep
noRepeatMenu = FALSE
fin
else
beep
fin
wend
until 0
return NULL
return NULL // never reached
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -755,9 +933,9 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
def formatEquipped(num)#1
if num == 1
rawDisplayStr("Y")
rawDisplayStr("yes")
else
rawDisplayStr("N")
rawDisplayStr("no")
fin
return 0
end
@ -818,7 +996,6 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
def displayWeaponStats(pItem1, pItem2)#0
displayTwoCol("Equip'd", pItem1, pItem2, b_flags, @equippedField, @formatEquipped)
displayTwoCol("Uses", pItem1, pItem2, b_maxUses, @byteField, @formatNum)
displayTwoCol("Ammo", pItem1, pItem2, s_ammoKind, @wordField, @formatStr)
displayTwoCol("Clip", pItem1, pItem2, b_clipSize, @byteField, @formatNum)
displayTwoCol("Melee", pItem1, pItem2, r_meleeDmg, @wordField, @formatDice)
@ -832,25 +1009,19 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
def displayArmorStats(pItem1, pItem2)#0
displayTwoCol("Equip'd", pItem1, pItem2, b_flags, @equippedField, @formatEquipped)
displayTwoCol("Uses", pItem1, pItem2, b_maxUses, @byteField, @formatNum)
displayTwoCol("Protec", pItem1, pItem2, b_armorValue, @byteField, @formatNum)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def displayStuffStats(pItem1, pItem2)#0
// Nothing special
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def _displayItemStats(pItem1, pItem2)#1
word pMod1, pMod2
// First, show the item type and name
when pItem1->t_type
is TYPE_ITEM; rawDisplayStr("\nItem"); break
is TYPE_WEAPON; rawDisplayStr("\nWeapon"); break
is TYPE_ARMOR; rawDisplayStr("\nArmor"); break
is TYPE_STUFF; rawDisplayStr("\nSupply"); break
is TYPE_PLAIN_ITEM
is TYPE_FANCY_ITEM; rawDisplayStr("\nItem"); break
is TYPE_WEAPON; rawDisplayStr("\nWeapon"); break
is TYPE_ARMOR; rawDisplayStr("\nArmor"); break
otherwise fatal("tItem")
wend
tabTo(STATS_COL_1); _displayItemName(pItem1)
@ -862,25 +1033,28 @@ def _displayItemStats(pItem1, pItem2)#1
when pItem1->t_type
is TYPE_WEAPON; displayWeaponStats(pItem1, pItem2); break
is TYPE_ARMOR; displayArmorStats(pItem1, pItem2); break
is TYPE_STUFF; displayStuffStats(pItem1, pItem2); break
wend
// If either item has modifiers, show them
pMod1 = NULL
if pItem1->t_type <> TYPE_STUFF; pMod1 = pItem1=>p_modifiers; fin
if pItem1->t_type <> TYPE_PLAIN_ITEM; pMod1 = pItem1=>p_modifiers; fin
pMod2 = NULL
if pItem2
if pItem2->t_type <> TYPE_STUFF; pMod2 = pItem2=>p_modifiers; fin
if pItem2->t_type <> TYPE_PLAIN_ITEM; pMod2 = pItem2=>p_modifiers; fin
fin
if pMod1 or pMod2
rawDisplayStr("\nSpecial")
while pMod1 or pMod2
if pMod1
rawDisplayf3("^T%D%d %s", STATS_COL_1, pMod1=>w_modValue, pMod1=>s_name)
rawDisplayf1("^T%D", STATS_COL_1)
if pMod1=>w_modValue >= 999; rawDisplayStr("Full "); else rawDisplayf1("%d ", pMod1=>w_modValue); fin
rawDisplayStr(pMod1=>s_name)
pMod1 = pMod1=>p_nextObj
fin
if pMod2
rawDisplayf3("^T%D%d %s", STATS_COL_2, pMod2=>w_modValue, pMod2=>s_name)
rawDisplayf1("^T%D", STATS_COL_2)
if pMod2=>w_modValue >= 999; rawDisplayStr("Full "); else rawDisplayf1("%d ", pMod2=>w_modValue); fin
rawDisplayStr(pMod2=>s_name)
pMod2 = pMod2=>p_nextObj
fin
loop
@ -892,13 +1066,14 @@ end
// For non-countable items, display singular name.
// For countable "stuff" (e.g. ammo), display the count and appropriate singular or plural name.
def _displayItemName(pItem)#1
if pItem->t_type == TYPE_STUFF
setPlural(pItem=>w_count <> 1)
isPlural = FALSE
if pItem->t_type == TYPE_FANCY_ITEM and pItem=>w_count > 1
isPlural = TRUE
rawDisplayf1("%d ", pItem=>w_count)
else
setPlural(FALSE)
fin
rawDisplayf1("%s", pItem=>s_name) // use displayf to get proper plural processing
buildString(@addToString)
printf1("%s", pItem=>s_name) // need proper plural processing
rawDisplayStr(finishString(isPlural))
return 0
end

View File

@ -9,19 +9,17 @@
///////////////////////////////////////////////////////////////////////////////////////////////////
// Garbage collection pointer offsets within each type
byte typeTbl_Global[] = Global, p_players, p_benched, p_enemyGroups, p_combatFirst, p_encounterZones, s_mapName, p_gameFlags, 0
byte typeTbl_Player[] = Player, p_nextObj, s_name, p_combatNext, p_skills, p_items, p_effects, 0
byte typeTbl_Modifier[] = Modifier, p_nextObj, s_name, 0
byte typeTbl_Effect[] = Effect, p_nextObj, s_name, 0
byte typeTbl_Item[] = Item, p_nextObj, s_name, s_itemKind, p_modifiers, 0
byte typeTbl_Weapon[] = Weapon, p_nextObj, s_name, s_itemKind, p_modifiers, s_ammoKind, s_combatText, 0
byte typeTbl_Armor[] = Armor, p_nextObj, s_name, s_itemKind, p_modifiers, 0
byte typeTbl_Stuff[] = Stuff, p_nextObj, s_name, s_itemKind, 0
byte typeTbl_Enemy[] = Enemy, p_nextObj, s_name, p_combatNext, s_attackText, 0
byte typeTbl_EnemyGroup[] = EnemyGroup, p_nextObj, p_enemies, 0
byte typeTbl_Global[] = Global, p_players, p_benched, p_enemyGroups, p_combatFirst, p_encounterZones, s_mapName, 0
byte typeTbl_Player[] = Player, p_nextObj, s_name, p_combatNext, p_skills, p_items, 0
byte typeTbl_Modifier[] = Modifier, p_nextObj, s_name, 0
byte typeTbl_PlainItem[] = PlainItem, p_nextObj, s_name, 0
byte typeTbl_FancyItem[] = FancyItem, p_nextObj, s_name, s_itemKind, p_modifiers, 0
byte typeTbl_Weapon[] = Weapon, p_nextObj, s_name, s_itemKind, p_modifiers, s_ammoKind, s_combatText, 0
byte typeTbl_Armor[] = Armor, p_nextObj, s_name, s_itemKind, p_modifiers, 0
byte typeTbl_Enemy[] = Enemy, p_nextObj, s_name, p_combatNext, s_attackText, 0
byte typeTbl_EnemyGroup[] = EnemyGroup, p_nextObj, p_enemies, 0
byte typeTbl_EncounterZone[] = EncounterZone, p_nextObj, s_name, 0
word typeTbls = @typeTbl_Global, @typeTbl_Player, @typeTbl_Modifier, @typeTbl_Effect, @typeTbl_Item
word = @typeTbl_Weapon, @typeTbl_Armor, @typeTbl_Stuff, @typeTbl_Enemy, @typeTbl_EnemyGroup
word = @typeTbl_EncounterZone
word typeTbls = @typeTbl_Global, @typeTbl_Player, @typeTbl_Modifier, @typeTbl_PlainItem, @typeTbl_FancyItem
word = @typeTbl_Weapon, @typeTbl_Armor, @typeTbl_Enemy, @typeTbl_EnemyGroup, @typeTbl_EncounterZone
word = 0

View File

@ -36,8 +36,7 @@ struc Global
word w_heapSize
word w_typeHash
// General flags maintained by scripts. Linked list of Modifiers.
word p_gameFlags
byte[32] ba_gameFlags // General flags maintained by scripts (array of 256 bits = 32 bytes)
byte b_curAvatar
// god mode flag
@ -86,7 +85,6 @@ struc Player
// Lists
word p_skills // list:Modifier
word p_items // list:Item
word p_effects // list:Effect
end
// Combat skills, weapon modifiers, etc.
@ -98,27 +96,29 @@ struc Modifier
word w_modValue
end
// Buffs and debuffs, that last until a specified time
const TYPE_EFFECT = $83
struc Effect
byte t_type
word p_nextObj
byte s_name
word w_modValue
word w_endTurn
end
const TYPE_ITEM = $84
struc Item
// Plain items are for things that don't stack or have stat effects,
// but may have meaning to scenario logic (e.g. keys, tokens, etc.)
const TYPE_PLAIN_ITEM = $83
struc PlainItem
byte t_type
word p_nextObj
word s_name
word s_itemKind
word w_price
word p_modifiers // list:modifier
// Usables properties
byte b_maxUses
byte b_curUses
end
const TYPE_FANCY_ITEM = $84
struc FancyItem
// Plain item properties
byte t_type
word p_nextObj
word s_name
word w_price
// Fancy properties
word s_itemKind // for ammo
word p_modifiers // list:modifier, e.g. boost health, etc.
word w_count // zero for singular items, 1+ for countables
word w_storeAmount
word r_lootAmount
end
const ITEM_FLAG_EQUIP = $80 // only one weapon/armor equipped (in use) at a time
@ -127,19 +127,16 @@ const WEAPON_FLAG_SINGLE_USE = $01
const TYPE_WEAPON = $85
struc Weapon
// Item properties
// Plain item properties
byte t_type
word p_nextObj
word s_name
word s_itemKind
word w_price
word p_modifiers // list:modifier
// Usables properties
byte b_maxUses
byte b_curUses
// Weapon properties
byte b_flags // WEAPON_FLAG_* above
word s_ammoKind
word s_itemKind // for skill matching
word p_modifiers // list:modifier
byte b_flags // WEAPON_FLAG_* above
word s_ammoKind // for matching to Stackable ammo
byte b_clipSize
byte b_clipCurrent
word r_meleeDmg // 3 hex digits: num dice, die size, add. E.g. $361 = 3d6+1
@ -151,37 +148,19 @@ end
const TYPE_ARMOR = $86
struc Armor
// General item properties
// Plain item properties
byte t_type
word p_nextObj
word s_name
word s_itemKind
word w_price
word p_modifiers // list:modifier
// Usables properties
byte b_maxUses
byte b_curUses
// Armor properties
word s_itemKind
word p_modifiers // list:modifier
byte b_flags // ARMOR_FLAG_* above
byte b_armorValue
end
// Countable things, e.g. ammo and pelts
const TYPE_STUFF = $87
struc Stuff
// General item properties
byte t_type
word p_nextObj
word s_name
word s_itemKind
word w_price
// Stuff properties
word w_count
word w_storeAmount
word r_lootAmount
end
const TYPE_ENEMY = $88
const TYPE_ENEMY = $87
struc Enemy
byte t_type
word p_nextObj
@ -204,7 +183,7 @@ struc Enemy
word r_goldLoot // monetary loot when killed, as 3 hex digits for dice
end
const TYPE_ENEMY_GROUP = $89
const TYPE_ENEMY_GROUP = $88
struc EnemyGroup
byte t_type
word p_nextObj
@ -212,7 +191,7 @@ struc EnemyGroup
byte b_enemyGroupRange
end
const TYPE_ENCOUNTER_ZONE = $8A
const TYPE_ENCOUNTER_ZONE = $89
struc EncounterZone
byte t_type
word p_nextObj

View File

@ -31,8 +31,8 @@ word pItemsModule, pPartyModule
const MAX_PAGE_ITEMS = 20 // should be plenty
word pageItems[MAX_PAGE_ITEMS]
word pagePrices[MAX_PAGE_ITEMS]
word pagePlayers[MAX_PAGE_ITEMS]
word pMatchPlayer
byte playerNum, playerCount
word pPlayer
///////////////////////////////////////////////////////////////////////////////////////////////////
// Definitions used by assembly code
@ -61,14 +61,14 @@ def unloadExtraModules()#0
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def displayBuyTitle(pageNum, nPages)#0
def displayTitle(titleAction, columnAction, pageNum, nPages)#0
clearWindow()
// Can't use centering mode on oversize window - font engine can't handle width > 255
rawDisplayStr("^Y^I Buying")
rawDisplayf2("^I %s %s ", pPlayer=>s_name, titleAction)
if (nPages > 1)
rawDisplayf2(" - p. %d/%d", pageNum+1, nPages)
rawDisplayf2("p.%d/%d ", pageNum+1, nPages)
fin
rawDisplayStr(" ^N\n^V014^LBrowse^T046Price^T085Item^L")
rightJustifyStr(sprintf1(" %d party gold ", global=>w_gold), BIGWIN_RIGHT - 15)
rawDisplayf1("^N\n^V014^L%s^T046Price^T085Item^L", columnAction)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -77,19 +77,45 @@ def displayItemLine(num)#0
pPartyModule()=>party_displayItemName(pageItems[num])
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def displayPaging(action, nItems, pageNum, nPages)#0
rawDisplayStr("\n^V164")
if nItems or playerCount > 1
if nItems
rawDisplayf1("%s [A", action)
if nItems > 1; rawDisplayf1("-%c", nItems-1+'A'); fin
rawDisplayStr("], ")
fin
if nPages > 1
rawDisplayStr("Pg [")
if pageNum+1 < nPages; rawDisplayStr(">"); fin
if pageNum; rawDisplayStr("<"); fin
rawDisplayStr("], ")
fin
if playerCount > 1
rawDisplayf1("Plyr [1-%d], ", playerCount)
fin
rawDisplayStr("or [Esc].")
else
rawDisplayStr("Press [Esc].")
fin
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def displayBuyPage(pItemTbl, markupRatio, pageNum, nPages)#1
byte itemNum
word pFunc, pItem
displayBuyTitle(pageNum, nPages)
// Clear stuff from previous page
mmgr(CHECK_MEM, 0)
mmgr(HEAP_COLLECT, 0)
displayTitle("buying", "Browse", pageNum, nPages)
pFunc = pItemTbl + ((pageNum*PAGE_SIZE) << 1)
for itemNum = 0 to PAGE_SIZE-1
if !(*pFunc); break; fin
pItem = (*pFunc)()
if pItem->t_type == TYPE_STUFF
if pItem->t_type == TYPE_FANCY_ITEM
pItem=>w_count = pItem=>w_storeAmount
fin
pageItems[itemNum] = pItem
@ -97,14 +123,7 @@ def displayBuyPage(pItemTbl, markupRatio, pageNum, nPages)#1
displayItemLine(itemNum)
pFunc = pFunc + 2
next
rawDisplayf1("\n^V166Gold: %d. Browse [A", global=>w_gold)
if itemNum > 1; rawDisplayf1("-%c", itemNum-1+'A'); fin
rawDisplayStr("], ")
if nPages > 1
rawDisplayf1("p. [1-%d], ", nPages)
fin
rawDisplayStr("or [Esc].")
displayPaging("Browse", itemNum, pageNum, nPages)
return itemNum
end
@ -113,7 +132,7 @@ def displayItemBrowse(pItem1, price, pItem2)#0
clearWindow()
rawDisplayf1("^T108^I Browse ^N\n\n^T%D^LMerchandise^L", STATS_COL_1)
if pItem2
rawDisplayf2("^T%D^L%s^L", STATS_COL_2, pMatchPlayer=>s_name)
rawDisplayf2("^T%D^L%s^L", STATS_COL_2, pPlayer=>s_name)
fin
pPartyModule()=>party_displayItemStats(pItem1, pItem2)
rawDisplayf2("\n\nPrice^T%D%d", STATS_COL_1, price)
@ -122,26 +141,20 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
def matchEquipped(pMatch, nSkip)#1
word pPlayer, pItem
pPlayer = global=>p_players
pMatchPlayer = pPlayer
while pPlayer
pItem = pPlayer=>p_items
while pItem
if (pItem->t_type == pMatch->t_type) and (pItem->t_type == TYPE_WEAPON or pItem->t_type == TYPE_ARMOR)
if (pItem->b_flags & ITEM_FLAG_EQUIP)
if pItem->t_type <> TYPE_ARMOR or (pItem=>s_itemKind == pMatch=>s_itemKind)
if nSkip == 0
pMatchPlayer = pPlayer
return pItem
fin
nSkip = nSkip - 1
word pItem
pItem = pPlayer=>p_items
while pItem
if (pItem->t_type == pMatch->t_type) and (pItem->t_type == TYPE_WEAPON or pItem->t_type == TYPE_ARMOR)
if (pItem->b_flags & ITEM_FLAG_EQUIP)
if pItem->t_type <> TYPE_ARMOR or (pItem=>s_itemKind == pMatch=>s_itemKind)
if nSkip == 0
return pItem
fin
nSkip = nSkip - 1
fin
fin
pItem = pItem=>p_nextObj
loop
pPlayer = pPlayer=>p_nextObj
fin
pItem = pItem=>p_nextObj
loop
return NULL
end
@ -169,6 +182,12 @@ def askQuantity(nMax)#1
return num
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def displayMsg(msg, beep)#0
rawDisplayf1("^T000^C%s", msg)
pause(800)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def browseItem(num)#0
word pItem, price, compSkip, pComp, quantity
@ -180,24 +199,22 @@ def browseItem(num)#0
displayItemBrowse(pItem, price, matchEquipped(pItem, compSkip))
displayItemMenu(price, compSkip or matchEquipped(pItem, 1+compSkip))
sel = getUpperKey()
rawDisplayf1(" %c\n", sel)
if sel == 'B' and price <= global=>w_gold
matchEquipped(pItem, compSkip) // to set pMatchPlayer
pComp = scanForNamedObj(pMatchPlayer=>p_items, pItem=>s_name)
pComp = scanForNamedObj(pPlayer=>p_items, pItem=>s_name)
if pComp
if pItem->t_type == TYPE_STUFF
if pItem->t_type == TYPE_FANCY_ITEM and pItem=>w_count > 0
pComp=>w_count = min(30000, pComp=>w_count + pItem=>w_count)
else
rawDisplayStr("\nDuplicate item.")
rawDisplayStr("^T000^CDuplicate item.")
beep()
pause(1000)
pause(400)
continue
fin
else
addToList(@pMatchPlayer=>p_items, pItem)
addToList(@pPlayer=>p_items, pItem)
fin
global=>w_gold = global=>w_gold - price
rawDisplayStr("\nPurchase complete.")
rawDisplayStr("^T000^CDone.")
pause(800)
break
elsif sel == 'N' and (compSkip or matchEquipped(pItem, 1+compSkip))
@ -211,16 +228,24 @@ def browseItem(num)#0
loop
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def storeSetup()#0
loadExtraModules()
setBigWindow()
playerCount = countList(global=>p_players)
playerNum = 0
pPlayer = numToPlayer(playerNum)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def _buyFromStore(storeCode, profitPercent)#1
word pItemTbl, choice, ratio
byte nItemsOnPage, pageNum, nPages, redisplay
loadExtraModules()
storeSetup()
pItemTbl = pItemsModule()=>items_forStoreCode(storeCode)
setBigWindow()
nPages = (countArray(pItemTbl) + PAGE_SIZE - 1) / PAGE_SIZE
pageNum = 0
@ -233,8 +258,13 @@ def _buyFromStore(storeCode, profitPercent)#1
fin
choice = getUpperKey()
redisplay = TRUE
if choice >= '1' and (choice-'1') < nPages
pageNum = choice - '1'
if choice == '<' and pageNum
pageNum--
elsif choice == '>' and pageNum+1 < nPages
pageNum++
elsif choice >= '1' and (choice-'1') <= playerCount and (choice-'1') <> playerNum
playerNum = choice - '1'
pPlayer = numToPlayer(playerNum)
elsif choice >= 'A' and (choice-'A' < nItemsOnPage)
browseItem(choice-'A')
elsif choice == $1B // Esc
@ -246,53 +276,38 @@ def _buyFromStore(storeCode, profitPercent)#1
loop
unloadExtraModules()
mmgr(CHECK_MEM, 0)
return mmgr(HEAP_COLLECT, 0)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def displaySellTitle(pageNum, nPages)#0
clearWindow()
// Can't use centering mode on oversize window - font engine can't handle width > 255
rawDisplayStr("^Y^I Selling")
if (nPages > 1)
rawDisplayf2(" - p. %d/%d", pageNum+1, nPages)
fin
rawDisplayStr(" ^N\n^V014^LSell^T046Amt.^T085Item^L")
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def iterateSellables(skipItems, markdownRatio)#1
word pPlayer, pItem, itemsOnPage, totalItems, price
word pItem, itemsOnPage, totalItems, price
byte ok
itemsOnPage = 0
totalItems = 0
pPlayer = global=>p_players
while pPlayer
pItem = pPlayer=>p_items
while pItem
ok = pItem=>w_price > 0
if pItem->t_type == TYPE_STUFF
ok = FALSE // too much trouble to figure out stuff prices
elsif pItem->t_type == TYPE_WEAPON or pItem->t_type == TYPE_ARMOR
if pItem->b_flags & ITEM_FLAG_EQUIP; ok = FALSE; fin
pItem = pPlayer=>p_items
while pItem
ok = pItem=>w_price > 0
if pItem->t_type == TYPE_FANCY_ITEM
ok = pItem=>w_count > 0 // too much trouble to figure out prices of stackables
elsif pItem->t_type == TYPE_WEAPON or pItem->t_type == TYPE_ARMOR
if pItem->b_flags & ITEM_FLAG_EQUIP; ok = FALSE; fin // don't sell equipped things
fin
if ok
price = max(0, pItem=>w_price - addRatio(pItem=>w_price, markdownRatio))
if !price; ok = FALSE; fin
fin
if ok
if totalItems >= skipItems and itemsOnPage < PAGE_SIZE
pageItems[itemsOnPage] = pItem
pagePrices[itemsOnPage] = price
displayItemLine(itemsOnPage)
itemsOnPage++
fin
if ok
price = max(0, pItem=>w_price - addRatio(pItem=>w_price, markdownRatio))
if !price; ok = FALSE; fin
fin
if ok
if totalItems >= skipItems and itemsOnPage < PAGE_SIZE
pageItems[itemsOnPage] = pItem
pagePrices[itemsOnPage] = price
pagePlayers[itemsOnPage] = pPlayer
displayItemLine(itemsOnPage)
itemsOnPage++
fin
totalItems++
fin
pItem = pItem=>p_nextObj
loop
pPlayer = pPlayer=>p_nextObj
totalItems++
fin
pItem = pItem=>p_nextObj
loop
if skipItems == 9999; return totalItems; fin
return itemsOnPage
@ -301,18 +316,12 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
def displaySellPage(markdownRatio, pageNum, nPages)#1
word nItems
displaySellTitle(pageNum, nPages)
displayTitle("selling", "Sell", pageNum, nPages)
nItems = iterateSellables(pageNum * PAGE_SIZE, markdownRatio)
if !nItems
rawDisplayStr("\n\nNothing to sell here.")
fin
rawDisplayf1("\n^V166Gold: %d. Sell [A", global=>w_gold)
if nItems > 1; rawDisplayf1("-%c", nItems-1+'A'); fin
rawDisplayStr("], ")
if nPages > 1
rawDisplayf1("p. [1-%d], ", nPages)
fin
rawDisplayStr("or [Esc].")
displayPaging("Sell", nItems, pageNum, nPages)
return nItems
end
@ -323,12 +332,10 @@ def sellItem(num)#0
pItem = pageItems[num]
price = pagePrices[num]
clearWindow()
global=>w_gold = global=>w_gold + price
removeFromList(@pagePlayers=>p_items, pItem)
removeFromList(@pPlayer=>p_items, pItem)
rawDisplayStr("\nSale complete.")
rawDisplayStr("^T000^CDone.")
pause(800)
end
@ -337,27 +344,34 @@ def _sellToStore(profitPercent)#1
word pItemTbl, choice, ratio
byte nItemsOnPage, pageNum, totalItems, nPages, redisplay
loadExtraModules()
setBigWindow()
pageNum = 0
storeSetup()
ratio = percentToRatio(profitPercent) / 2 // half profit on buying, half on selling
totalItems = iterateSellables(9999, 0) // initialize count for paging calcs
redisplay = TRUE
while totalItems > 0
while TRUE
nPages = (totalItems + PAGE_SIZE - 1) / PAGE_SIZE // recalc each time since totalItems changes
pageNum = min(nPages-1, pageNum)
if redisplay
nItemsOnPage = displaySellPage(ratio, pageNum, nPages)
fin
choice = getUpperKey()
redisplay = TRUE
if choice >= '1' and (choice-'1') < nPages
pageNum = choice - '1'
if choice == '<' and pageNum
pageNum--
elsif choice == '>' and pageNum+1 < nPages
pageNum++
elsif choice >= '1' and (choice-'1') <= playerCount
playerNum = choice - '1'
pPlayer = numToPlayer(playerNum)
totalItems = iterateSellables(9999, 0)
elsif choice >= 'A' and (choice-'A' < nItemsOnPage)
sellItem(choice-'A')
totalItems = iterateSellables(9999, 0)
if totalItems <= pageNum * PAGE_SIZE // sold last item on pg 2
pageNum--
fin
elsif choice == $1B // Esc
break
else
@ -367,6 +381,7 @@ def _sellToStore(profitPercent)#1
loop
unloadExtraModules()
mmgr(CHECK_MEM, 0)
return mmgr(HEAP_COLLECT, 0)
end

View File

@ -1763,6 +1763,8 @@ pl_advance: !zone
pha
adc walkDirs+1,x
sta playerX+1
jsr .chk
sta .ora+1
lda playerY
pha
@ -1773,27 +1775,15 @@ pl_advance: !zone
pha
adc walkDirs+3,x
sta playerY+1
; Check if the new position is blocked
jsr calcMapOrigin
ldy playerX+1
lda (pMap),y
and #$1F
beq .ok ; empty tiles are never blocked
tax
jsr getTileFlags
sta tmp+1
and #2 ; tile flag 2 is for obstructions
jsr .chk
.ora ora #11 ; self-modified above
beq .ok
; Blocked! Restore old position.
pla
sta playerY+1
pla
sta playerY
pla
sta playerX+1
pla
sta playerX
ldx #3
- pla
sta playerX,x
dex
bpl -
ldy #0
beq .done
.ok ; Not blocked. See if we're in a new map tile.
@ -1820,6 +1810,20 @@ pl_advance: !zone
.done tya ; retrieve ret value
ldy #0 ; hi byte of ret is always 0
rts ; all done
; Check if the new position is blocked
.chk stx .rstx+1
jsr calcMapOrigin
ldy playerX+1
lda (pMap),y
and #$1F
beq .rstx ; empty tiles are never blocked
tax
jsr getTileFlags
sta tmp+1
and #2 ; tile flag 2 is for obstructions
.rstx ldx #11 ; self-modified above
cmp #0
rts
;-------------------------------------------------------------------------------
; Swap tiles at two positions.