Good progress on gang combat.

This commit is contained in:
Martin Haye 2019-11-07 12:01:46 -08:00
parent 12d02919ab
commit cbf17851b3
4 changed files with 91 additions and 57 deletions

View File

@ -3230,7 +3230,7 @@ class A2PackPartitions
out.println("byte = ${lootChance ? lootChance.toInteger() : 10} // loot chance") out.println("byte = ${lootChance ? lootChance.toInteger() : 10} // loot chance")
out.println("word = ${validateLootCode(lootCode, strings)}, " + out.println("word = ${validateLootCode(lootCode, strings)}, " +
"${parseDice(goldLoot)} // lootCode, goldLoot") "${parseDice(goldLoot)} // lootCode, goldLoot")
out.println("byte = ${gangChance ? parseDice(gangChance) : 0} // gang chance") out.println("byte = ${gangChance ? gangChance.toInteger() : 0} // gang chance")
out.println("") out.println("")
// Add portrait dependencies based on encounter zone(s) // Add portrait dependencies based on encounter zone(s)

View File

@ -25,7 +25,7 @@ struc EnemyData
byte b_en_img1 byte b_en_img1
byte b_en_attType byte b_en_attType
word s_en_attText word s_en_attText
byte r_en_attRange word r_en_attRange
byte b_en_chanceToHit byte b_en_chanceToHit
word r_en_dmgDice word r_en_dmgDice
word r_en_expDice word r_en_expDice
@ -57,8 +57,8 @@ word[4] preloads
word pItemUtil word pItemUtil
const MAX_GROUPS = 3 const MAX_GROUPS = 3
byte[MAX_GROUPS] groupEnemyNums
byte nEnemyGroups byte nEnemyGroups
byte anyAdvancing
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
def preload()#0 def preload()#0
@ -404,9 +404,9 @@ def displayOpponents()#0
count = sum(p=>p_enemies, @canFight, @trueFunc) count = sum(p=>p_enemies, @canFight, @trueFunc)
isPlural = count <> 1 isPlural = count <> 1
if (p=>p_enemies=>r_groupSize == 0) if (p=>p_enemies=>r_groupSize == 0)
displayf2("%s at %d'", p=>p_enemies=>s_name, p->b_enemyGroupRange) displayf2("%s at %d'", p=>p_enemies=>s_name, p=>p_enemies->b_enemyAttackRange)
else else
displayf3("%d %s at %d'", count, p=>p_enemies=>s_name, p->b_enemyGroupRange) displayf3("%d %s at %d'", count, p=>p_enemies=>s_name, p=>p_enemies->b_enemyAttackRange)
fin fin
p = p=>p_nextObj p = p=>p_nextObj
loop loop
@ -430,7 +430,7 @@ def minEnemyDist()#1
p = global=>p_enemyGroups p = global=>p_enemyGroups
while p while p
if first(p=>p_enemies, @canFight) if first(p=>p_enemies, @canFight)
minDist = min(minDist, p->b_enemyGroupRange) minDist = min(minDist, p=>p_enemies->b_enemyAttackRange)
fin fin
p = p=>p_nextObj p = p=>p_nextObj
loop loop
@ -559,8 +559,8 @@ def playerCombatChoose(pl)#0
// Let them know their options // Let them know their options
setCursor(cursX, cursY) setCursor(cursX, cursY)
rawDisplayf1("^D%s:", pl=>s_name) rawDisplayStr("^D") // clear to end of page
displayOption('M', "Melee") displayf1("%s:\nM)elee, ", pl=>s_name)
if pWeapon if pWeapon
// Special (for e.g. bows): clipSize zero means weapon reloads automatically // Special (for e.g. bows): clipSize zero means weapon reloads automatically
if pWeapon=>r_projectileDmg and !pWeapon->b_clipCurrent and !pWeapon->b_clipSize if pWeapon=>r_projectileDmg and !pWeapon->b_clipCurrent and !pWeapon->b_clipSize
@ -569,23 +569,23 @@ def playerCombatChoose(pl)#0
if pWeapon->b_clipCurrent if pWeapon->b_clipCurrent
canShoot = TRUE canShoot = TRUE
if pWeapon->b_clipSize if pWeapon->b_clipSize
displayOption('S', sprintf2("Shoot %d:%d", pWeapon->b_clipCurrent, pWeapon->b_clipSize)) displayf2("S)hoot %d:%d, ", pWeapon->b_clipCurrent, pWeapon->b_clipSize))
else else
displayOption('S', "Shoot") displayStr("S)hoot, ")
fin fin
fin fin
if pWeapon->b_clipCurrent < pWeapon->b_clipSize if pWeapon->b_clipCurrent < pWeapon->b_clipSize
canReload = TRUE canReload = TRUE
displayOption('R', "Reload") displayStr("R)eload, ")
fin fin
fin fin
if canChange if canChange
displayOption('C', "Chg weapon") displayStr("C)hange weapon, ")
fin fin
displayOption('D', "Dodge") displayStr("D)odge, ")
displayOption('F', "Flee") canAdvance = !anyAdvancing and minEnemyDist() > 5 // only one advance per turn
canAdvance = minEnemyDist() > 5 if canAdvance; displayStr("A)dvance, "); fin
if canAdvance; displayOption('A', "Advance"); fin displayStr("F)lee")
setCursor(cursX, cursY) setCursor(cursX, cursY)
while TRUE while TRUE
@ -616,7 +616,10 @@ def playerCombatChoose(pl)#0
fin fin
break break
is 'A' is 'A'
if canAdvance; return; fin if canAdvance
anyAdvancing = TRUE
return
fin
break break
is '%' is '%'
if global->b_godmode if global->b_godmode
@ -684,10 +687,11 @@ def playerCombatTurn(pl)#0
if minEnemyDist() > 5 if minEnemyDist() > 5
pGroup = global=>p_enemyGroups pGroup = global=>p_enemyGroups
while pGroup while pGroup
pGroup->b_enemyGroupRange = pGroup->b_enemyGroupRange - 5 pGroup=>p_enemies->b_enemyAttackRange = pGroup=>p_enemies->b_enemyAttackRange - 5
pEnemy = pGroup=>p_enemies // Copy new range to all enemies in the group
pEnemy = pGroup=>p_enemies=>p_nextObj
while pEnemy while pEnemy
pEnemy->b_enemyAttackRange = pGroup->b_enemyGroupRange pEnemy->b_enemyAttackRange = pGroup=>p_enemies->b_enemyAttackRange
pEnemy = pEnemy=>p_nextObj pEnemy = pEnemy=>p_nextObj
loop loop
pGroup = pGroup=>p_nextObj pGroup = pGroup=>p_nextObj
@ -819,7 +823,7 @@ def determineCombatOrder()#0
while p2 while p2
if canFight(p2) if canFight(p2)
p2->b_combatOrder = rand16() % p2->b_chanceToHit p2->b_combatOrder = rand16() % p2->b_chanceToHit
p2->b_enemyAttackRange = p->b_enemyGroupRange p2->b_enemyAttackRange = p=>p_enemies->b_enemyAttackRange // force all to same range
combatInsert(p2) combatInsert(p2)
nEnemiesFighting = nEnemiesFighting + 1 nEnemiesFighting = nEnemiesFighting + 1
fin fin
@ -841,7 +845,10 @@ def makeEnemy(pData)
fin fin
p->b_attackType = pData->b_en_attType p->b_attackType = pData->b_en_attType
p=>s_attackText = mmgr(HEAP_INTERN, pData=>s_en_attText) p=>s_attackText = mmgr(HEAP_INTERN, pData=>s_en_attText)
p->b_enemyAttackRange = rollDice(pData=>r_en_attRange) // possibly random range // Establish (possibly random) attack range.
// The range will propagate up to group, then back down to overwrite range on all
// enemies within the group to keep them consistent.
p->b_enemyAttackRange = rollDice(pData=>r_en_attRange)
p->b_chanceToHit = pData->b_en_chanceToHit p->b_chanceToHit = pData->b_en_chanceToHit
p=>r_enemyDmg = pData=>r_en_dmgDice p=>r_enemyDmg = pData=>r_en_dmgDice
p=>r_enemyXP = pData=>r_en_expDice p=>r_enemyXP = pData=>r_en_expDice
@ -853,11 +860,11 @@ def makeEnemy(pData)
end end
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
def makeEnemyGroup(pEnemyData)#1 def makeEnemyGroup(enemyNum, pEnemyData)#1
word p, enem, groupSize word p, enem, groupSize
p = mmgr(HEAP_ALLOC, TYPE_ENEMY_GROUP) p = mmgr(HEAP_ALLOC, TYPE_ENEMY_GROUP)
enem = makeEnemy(pEnemyData) enem = makeEnemy(pEnemyData)
p->b_enemyGroupRange = enem->b_enemyAttackRange p->b_enemyNum = enemyNum
if enem=>r_groupSize == 0 // handle unique enemies if enem=>r_groupSize == 0 // handle unique enemies
groupSize = 1 groupSize = 1
else else
@ -887,14 +894,16 @@ def randomFromArray(arr, filter)#1
// Pick a random starting point in the array // Pick a random starting point in the array
start = rand16() % siz start = rand16() % siz
// Advance until we find a filter-approved entry (or run out) // Advance (circularly) until we find a filter-approved entry or run out
cur = start cur = start
while TRUE while TRUE
p = *((cur << 1) + arr) p = *((cur << 1) + arr)
if !filter; break; fin if !filter; break; fin // no filter, this entry is good
if filter(p); break; fin if filter(p); break; fin // filter approved, this entry is good
cur++ cur++
// Loop around circularly
if cur >= siz; cur = 0; fin if cur >= siz; cur = 0; fin
// Stop when we've come around to our starting point
if cur == start if cur == start
p = NULL p = NULL
break break
@ -905,35 +914,31 @@ end
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
def nonDupeEnemy(enemyNum)#1 def nonDupeEnemy(enemyNum)#1
byte i ctx = enemyNum
for i = 0 to nEnemyGroups return !first(global=>p_enemyGroups, &(p) p->b_enemyNum == ctx)
if enemyNum == groupEnemyNums[i]; return FALSE; fin
next
return TRUE
end end
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
def makeRandomGroup(mapCode)#1 def makeRandomGroup(mapCode)#1
word enemiesModule word enemiesModule
byte enemyNum byte enemyNum, gangChance
word pEnemyData, pGroup word pEnemyData, pGroup
enemiesModule = mmgr(QUEUE_LOAD, MOD_GEN_ENEMIES<<8 | RES_TYPE_MODULE) enemiesModule = mmgr(QUEUE_LOAD, MOD_GEN_ENEMIES<<8 | RES_TYPE_MODULE)
mmgr(FINISH_LOAD, 0) mmgr(FINISH_LOAD, 0)
enemyNum = randomFromArray(enemiesModule()=>enemies_forZone(mapCode), @nonDupeEnemy) enemyNum = randomFromArray(enemiesModule()=>enemies_forZone(mapCode), @nonDupeEnemy)
mmgr(FREE_MEMORY, enemiesModule) mmgr(FREE_MEMORY, enemiesModule)
if !enemyNum; return NULL; fin // no non-dupe enemies left in mapCode zone if !enemyNum; return 0; fin // no non-dupe enemies left in mapCode zone
groupEnemyNums[nEnemyGroups] = enemyNum
nEnemyGroups++
enemiesModule = mmgr(QUEUE_LOAD, (MOD_GEN_ENEMIES + 1 + ((enemyNum-1)&1))<<8 | RES_TYPE_MODULE) enemiesModule = mmgr(QUEUE_LOAD, (MOD_GEN_ENEMIES + 1 + ((enemyNum-1)&1))<<8 | RES_TYPE_MODULE)
mmgr(FINISH_LOAD, 0) mmgr(FINISH_LOAD, 0)
pEnemyData = enemiesModule() + 1 + (((enemyNum-1)>>1) * EnemyData) pEnemyData = enemiesModule() + 1 + (((enemyNum-1)>>1) * EnemyData)
pGroup = makeEnemyGroup(pEnemyData) gangChance = pEnemyData->b_en_gangChance
pGroup = makeEnemyGroup(enemyNum, pEnemyData)
addToList(@global=>p_enemyGroups, pGroup) addToList(@global=>p_enemyGroups, pGroup)
mmgr(FREE_MEMORY, enemiesModule) mmgr(FREE_MEMORY, enemiesModule)
return pGroup return gangChance
end end
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -1032,9 +1037,24 @@ def collectLootAndXP()#2
return gold, xp return gold, xp
end end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Concatenate two strings to form a single one on the heap. It's unusual to need this, but we
// do to display the enemy prompt for multiple groups.
def concat(s1, s2)#1
byte len
word p
len = ^s1 + ^s2
p = mmgr(HEAP_ALLOC, len+1)
^p = len
memcpy(s1+1, p+1, ^s1, FALSE)
memcpy(s2+1, p+(^s1)+1, ^s2, FALSE)
return p
end
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
def startCombat(mapCode)#1 def startCombat(mapCode)#1
word p, p2, n, s word p, n, s
byte n2
// Pre-load some global funcs // Pre-load some global funcs
preload() preload()
@ -1043,17 +1063,16 @@ def startCombat(mapCode)#1
isFleeing = FALSE isFleeing = FALSE
combatDebug = FALSE combatDebug = FALSE
global=>p_enemyGroups = NULL global=>p_enemyGroups = NULL
nEnemyGroups = 0
// Make one or more enemy groups (rolling for gangChance on each to see if we need another) // Make one or more enemy groups (rolling for gangChance on each to see if we need another)
while TRUE while TRUE
// Generate a random, non-duplicate group (can fail if non-duplicate is impossible) // Generate a random, non-duplicate group. Returns chance for adding another group to gang
if !makeRandomGroup(mapCode); break; fin n = makeRandomGroup(mapCode)
if !n; break; fin
// If maxed out on groups, or dice roll for gang fails, we're done // If maxed out on groups, or dice roll for gang fails, we're done
if nEnemyGroups < MAX_GROUPS and (rand16() % 100) >= p->b_en_gangChance if countList(global=>p_enemyGroups) >= MAX_GROUPS; break; fin
break if (rand16() % 100) >= n; break; fin
fin
loop loop
// Display portrait of first group // Display portrait of first group
@ -1065,17 +1084,24 @@ def startCombat(mapCode)#1
// queued up movement keys, which are made obsolete by the surprise of combat. // queued up movement keys, which are made obsolete by the surprise of combat.
if !recordMode; ^kbdStrobe; fin if !recordMode; ^kbdStrobe; fin
// Say who we're fighting // Say who we're fighting. This is tricky because the global ENEMY_INTRO script
// is expecting a single group.
p = global=>p_enemyGroups p = global=>p_enemyGroups
n = countList(p=>p_enemies)
isPlural = n <> 1
s = mmgr(HEAP_INTERN, sprintf1(p=>p_enemies=>s_name, 0))
p = p=>p_nextObj
while p while p
n = countList(p=>p_enemies) n2 = countList(p=>p_enemies)
isPlural = n <> 1 isPlural = n2 <> 1
s = callGlobalFunc(GS_ENEMY_INTRO, 0, 0, 0) s = concat(s, sprintf3("%s %d %s", p=>p_nextObj ?? "," :: " and", n2, p=>p_enemies=>s_name))
displayf2(s, n, p=>p_enemies=>s_name) isPlural = TRUE // for display on final string below
p = p=>p_nextObj p = p=>p_nextObj
loop loop
p = callGlobalFunc(GS_ENEMY_INTRO, 0, 0, 0)
displayf2(p, n, s)
rawDisplayStr("\n\nDo you:") rawDisplayStr("\n")
displayOption('B', "Battle") displayOption('B', "Battle")
displayOption('F', "Flee") displayOption('F', "Flee")
while TRUE while TRUE
@ -1160,6 +1186,7 @@ def _combat_zoneEncounter(s_encZone)#1
rawDisplayStr("\n") rawDisplayStr("\n")
// Get the choice of each player or NPC // Get the choice of each player or NPC
anyAdvancing = FALSE
forSome(global=>p_players, &(pl) canFight(pl) and nEnemiesFighting and !isFleeing, @playerCombatChoose) forSome(global=>p_players, &(pl) canFight(pl) and nEnemiesFighting and !isFleeing, @playerCombatChoose)
clearWindow() clearWindow()

View File

@ -93,7 +93,7 @@ byte texturesLoaded = FALSE
byte textDrawn = FALSE byte textDrawn = FALSE
byte anyInteraction = FALSE byte anyInteraction = FALSE
byte textClearCountdown = 0 byte textClearCountdown = 0
export byte isPlural = 0 // valid values: 0 or $40 export byte isPlural = 0
byte inScript = FALSE byte inScript = FALSE
export byte isFloppyVer export byte isFloppyVer
@ -429,6 +429,7 @@ addToString:
sty ysav1 sty ysav1
inc inbuf inc inbuf
ldy inbuf ldy inbuf
and #$7F
sta inbuf,y sta inbuf,y
ldy ysav1 ldy ysav1
rts rts
@ -440,7 +441,10 @@ end
export asm finishString(isPlural)#1 export asm finishString(isPlural)#1
!zone { !zone {
+asmPlasmRet 1 +asmPlasmRet 1
sta tmp ; save isPlural flag tax ; test for isPlural == 0
beq +
lda #$40 ; for setting V later
+ sta tmp ; save isPlural flag
lda prevCSWL+ABS_OFFSET ; put the cout vector back to default lda prevCSWL+ABS_OFFSET ; put the cout vector back to default
sta cswl sta cswl
lda prevCSWL+1+ABS_OFFSET ; put the cout vector back to default lda prevCSWL+1+ABS_OFFSET ; put the cout vector back to default
@ -454,7 +458,9 @@ export asm finishString(isPlural)#1
inx inx
lda inbuf,x ; get next input char lda inbuf,x ; get next input char
iny iny
and #$7F ; clear hi-bit for final string, in case it gets interned
sta inbuf,y ; by default copy the char to output sta inbuf,y ; by default copy the char to output
ora #$80
cmp #"(" ; plural processing triggered by parentheses cmp #"(" ; plural processing triggered by parentheses
bne .notpar bne .notpar
bvc .notpar ; but only parens directly after alpha char, e.g. preserving "Happy (and safe)." bvc .notpar ; but only parens directly after alpha char, e.g. preserving "Happy (and safe)."
@ -462,13 +468,12 @@ export asm finishString(isPlural)#1
dey ; undo copy of the paren dey ; undo copy of the paren
stx tmp+1 ; save position in input stx tmp+1 ; save position in input
dex ; needed for failsafe operation dex ; needed for failsafe operation
bit tmp ; set copy flag (V) initially bit tmp ; set copy flag (V) initially to same as isPlural flag
bne .findsl ; to same as isPlural flag
clv
.findsl ; see if there's a slash within the parens .findsl ; see if there's a slash within the parens
inx inx
cpx inbuf cpx inbuf
lda inbuf,x lda inbuf,x
ora #$80 ; normalize hi-bit for comparisons below
cmp #"/" cmp #"/"
bne + bne +
php php
@ -487,6 +492,7 @@ export asm finishString(isPlural)#1
.plup .plup
inx inx
lda inbuf,x lda inbuf,x
ora #$80
cmp #"/" cmp #"/"
bne + bne +
php php
@ -499,6 +505,7 @@ export asm finishString(isPlural)#1
beq .notpar ; stop at closing paren beq .notpar ; stop at closing paren
bvc .plup ; if not in copy mode, skip copy bvc .plup ; if not in copy mode, skip copy
iny iny
and #$7F ; clear hi-bit for final string, in case it gets interned
sta inbuf,y ; else do copy sta inbuf,y ; else do copy
bne .plup ; always taken bne .plup ; always taken
.notpar .notpar

View File

@ -197,7 +197,7 @@ struc TEnemyGroup
byte t_type byte t_type
word p_nextObj word p_nextObj
word p_enemies word p_enemies
byte b_enemyGroupRange byte b_enemyNum
end end
const TYPE_ENCOUNTER_ZONE = $89 const TYPE_ENCOUNTER_ZONE = $89