mirror of
https://github.com/badvision/lawless-legends.git
synced 2025-02-28 12:29:59 +00:00
Good progress on gang combat.
This commit is contained in:
parent
12d02919ab
commit
cbf17851b3
@ -3230,7 +3230,7 @@ class A2PackPartitions
|
||||
out.println("byte = ${lootChance ? lootChance.toInteger() : 10} // loot chance")
|
||||
out.println("word = ${validateLootCode(lootCode, strings)}, " +
|
||||
"${parseDice(goldLoot)} // lootCode, goldLoot")
|
||||
out.println("byte = ${gangChance ? parseDice(gangChance) : 0} // gang chance")
|
||||
out.println("byte = ${gangChance ? gangChance.toInteger() : 0} // gang chance")
|
||||
out.println("")
|
||||
|
||||
// Add portrait dependencies based on encounter zone(s)
|
||||
|
@ -25,7 +25,7 @@ struc EnemyData
|
||||
byte b_en_img1
|
||||
byte b_en_attType
|
||||
word s_en_attText
|
||||
byte r_en_attRange
|
||||
word r_en_attRange
|
||||
byte b_en_chanceToHit
|
||||
word r_en_dmgDice
|
||||
word r_en_expDice
|
||||
@ -57,8 +57,8 @@ word[4] preloads
|
||||
word pItemUtil
|
||||
|
||||
const MAX_GROUPS = 3
|
||||
byte[MAX_GROUPS] groupEnemyNums
|
||||
byte nEnemyGroups
|
||||
byte anyAdvancing
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
def preload()#0
|
||||
@ -404,9 +404,9 @@ def displayOpponents()#0
|
||||
count = sum(p=>p_enemies, @canFight, @trueFunc)
|
||||
isPlural = count <> 1
|
||||
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
|
||||
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
|
||||
p = p=>p_nextObj
|
||||
loop
|
||||
@ -430,7 +430,7 @@ def minEnemyDist()#1
|
||||
p = global=>p_enemyGroups
|
||||
while p
|
||||
if first(p=>p_enemies, @canFight)
|
||||
minDist = min(minDist, p->b_enemyGroupRange)
|
||||
minDist = min(minDist, p=>p_enemies->b_enemyAttackRange)
|
||||
fin
|
||||
p = p=>p_nextObj
|
||||
loop
|
||||
@ -559,8 +559,8 @@ def playerCombatChoose(pl)#0
|
||||
|
||||
// Let them know their options
|
||||
setCursor(cursX, cursY)
|
||||
rawDisplayf1("^D%s:", pl=>s_name)
|
||||
displayOption('M', "Melee")
|
||||
rawDisplayStr("^D") // clear to end of page
|
||||
displayf1("%s:\nM)elee, ", pl=>s_name)
|
||||
if pWeapon
|
||||
// Special (for e.g. bows): clipSize zero means weapon reloads automatically
|
||||
if pWeapon=>r_projectileDmg and !pWeapon->b_clipCurrent and !pWeapon->b_clipSize
|
||||
@ -569,23 +569,23 @@ def playerCombatChoose(pl)#0
|
||||
if pWeapon->b_clipCurrent
|
||||
canShoot = TRUE
|
||||
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
|
||||
displayOption('S', "Shoot")
|
||||
displayStr("S)hoot, ")
|
||||
fin
|
||||
fin
|
||||
if pWeapon->b_clipCurrent < pWeapon->b_clipSize
|
||||
canReload = TRUE
|
||||
displayOption('R', "Reload")
|
||||
displayStr("R)eload, ")
|
||||
fin
|
||||
fin
|
||||
if canChange
|
||||
displayOption('C', "Chg weapon")
|
||||
displayStr("C)hange weapon, ")
|
||||
fin
|
||||
displayOption('D', "Dodge")
|
||||
displayOption('F', "Flee")
|
||||
canAdvance = minEnemyDist() > 5
|
||||
if canAdvance; displayOption('A', "Advance"); fin
|
||||
displayStr("D)odge, ")
|
||||
canAdvance = !anyAdvancing and minEnemyDist() > 5 // only one advance per turn
|
||||
if canAdvance; displayStr("A)dvance, "); fin
|
||||
displayStr("F)lee")
|
||||
setCursor(cursX, cursY)
|
||||
|
||||
while TRUE
|
||||
@ -616,7 +616,10 @@ def playerCombatChoose(pl)#0
|
||||
fin
|
||||
break
|
||||
is 'A'
|
||||
if canAdvance; return; fin
|
||||
if canAdvance
|
||||
anyAdvancing = TRUE
|
||||
return
|
||||
fin
|
||||
break
|
||||
is '%'
|
||||
if global->b_godmode
|
||||
@ -684,10 +687,11 @@ def playerCombatTurn(pl)#0
|
||||
if minEnemyDist() > 5
|
||||
pGroup = global=>p_enemyGroups
|
||||
while pGroup
|
||||
pGroup->b_enemyGroupRange = pGroup->b_enemyGroupRange - 5
|
||||
pEnemy = pGroup=>p_enemies
|
||||
pGroup=>p_enemies->b_enemyAttackRange = pGroup=>p_enemies->b_enemyAttackRange - 5
|
||||
// Copy new range to all enemies in the group
|
||||
pEnemy = pGroup=>p_enemies=>p_nextObj
|
||||
while pEnemy
|
||||
pEnemy->b_enemyAttackRange = pGroup->b_enemyGroupRange
|
||||
pEnemy->b_enemyAttackRange = pGroup=>p_enemies->b_enemyAttackRange
|
||||
pEnemy = pEnemy=>p_nextObj
|
||||
loop
|
||||
pGroup = pGroup=>p_nextObj
|
||||
@ -819,7 +823,7 @@ def determineCombatOrder()#0
|
||||
while p2
|
||||
if canFight(p2)
|
||||
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)
|
||||
nEnemiesFighting = nEnemiesFighting + 1
|
||||
fin
|
||||
@ -841,7 +845,10 @@ def makeEnemy(pData)
|
||||
fin
|
||||
p->b_attackType = pData->b_en_attType
|
||||
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=>r_enemyDmg = pData=>r_en_dmgDice
|
||||
p=>r_enemyXP = pData=>r_en_expDice
|
||||
@ -853,11 +860,11 @@ def makeEnemy(pData)
|
||||
end
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
def makeEnemyGroup(pEnemyData)#1
|
||||
def makeEnemyGroup(enemyNum, pEnemyData)#1
|
||||
word p, enem, groupSize
|
||||
p = mmgr(HEAP_ALLOC, TYPE_ENEMY_GROUP)
|
||||
enem = makeEnemy(pEnemyData)
|
||||
p->b_enemyGroupRange = enem->b_enemyAttackRange
|
||||
p->b_enemyNum = enemyNum
|
||||
if enem=>r_groupSize == 0 // handle unique enemies
|
||||
groupSize = 1
|
||||
else
|
||||
@ -887,14 +894,16 @@ def randomFromArray(arr, filter)#1
|
||||
// Pick a random starting point in the array
|
||||
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
|
||||
while TRUE
|
||||
p = *((cur << 1) + arr)
|
||||
if !filter; break; fin
|
||||
if filter(p); break; fin
|
||||
if !filter; break; fin // no filter, this entry is good
|
||||
if filter(p); break; fin // filter approved, this entry is good
|
||||
cur++
|
||||
// Loop around circularly
|
||||
if cur >= siz; cur = 0; fin
|
||||
// Stop when we've come around to our starting point
|
||||
if cur == start
|
||||
p = NULL
|
||||
break
|
||||
@ -905,35 +914,31 @@ end
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
def nonDupeEnemy(enemyNum)#1
|
||||
byte i
|
||||
for i = 0 to nEnemyGroups
|
||||
if enemyNum == groupEnemyNums[i]; return FALSE; fin
|
||||
next
|
||||
return TRUE
|
||||
ctx = enemyNum
|
||||
return !first(global=>p_enemyGroups, &(p) p->b_enemyNum == ctx)
|
||||
end
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
def makeRandomGroup(mapCode)#1
|
||||
word enemiesModule
|
||||
byte enemyNum
|
||||
byte enemyNum, gangChance
|
||||
word pEnemyData, pGroup
|
||||
|
||||
enemiesModule = mmgr(QUEUE_LOAD, MOD_GEN_ENEMIES<<8 | RES_TYPE_MODULE)
|
||||
mmgr(FINISH_LOAD, 0)
|
||||
enemyNum = randomFromArray(enemiesModule()=>enemies_forZone(mapCode), @nonDupeEnemy)
|
||||
mmgr(FREE_MEMORY, enemiesModule)
|
||||
if !enemyNum; return NULL; fin // no non-dupe enemies left in mapCode zone
|
||||
groupEnemyNums[nEnemyGroups] = enemyNum
|
||||
nEnemyGroups++
|
||||
if !enemyNum; return 0; fin // no non-dupe enemies left in mapCode zone
|
||||
|
||||
enemiesModule = mmgr(QUEUE_LOAD, (MOD_GEN_ENEMIES + 1 + ((enemyNum-1)&1))<<8 | RES_TYPE_MODULE)
|
||||
mmgr(FINISH_LOAD, 0)
|
||||
pEnemyData = enemiesModule() + 1 + (((enemyNum-1)>>1) * EnemyData)
|
||||
pGroup = makeEnemyGroup(pEnemyData)
|
||||
gangChance = pEnemyData->b_en_gangChance
|
||||
pGroup = makeEnemyGroup(enemyNum, pEnemyData)
|
||||
addToList(@global=>p_enemyGroups, pGroup)
|
||||
mmgr(FREE_MEMORY, enemiesModule)
|
||||
|
||||
return pGroup
|
||||
return gangChance
|
||||
end
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -1032,9 +1037,24 @@ def collectLootAndXP()#2
|
||||
return gold, xp
|
||||
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
|
||||
word p, p2, n, s
|
||||
word p, n, s
|
||||
byte n2
|
||||
|
||||
// Pre-load some global funcs
|
||||
preload()
|
||||
@ -1043,17 +1063,16 @@ def startCombat(mapCode)#1
|
||||
isFleeing = FALSE
|
||||
combatDebug = FALSE
|
||||
global=>p_enemyGroups = NULL
|
||||
nEnemyGroups = 0
|
||||
|
||||
// Make one or more enemy groups (rolling for gangChance on each to see if we need another)
|
||||
while TRUE
|
||||
// Generate a random, non-duplicate group (can fail if non-duplicate is impossible)
|
||||
if !makeRandomGroup(mapCode); break; fin
|
||||
// Generate a random, non-duplicate group. Returns chance for adding another group to gang
|
||||
n = makeRandomGroup(mapCode)
|
||||
if !n; break; fin
|
||||
|
||||
// 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
|
||||
break
|
||||
fin
|
||||
if countList(global=>p_enemyGroups) >= MAX_GROUPS; break; fin
|
||||
if (rand16() % 100) >= n; break; fin
|
||||
loop
|
||||
|
||||
// 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.
|
||||
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
|
||||
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
|
||||
n = countList(p=>p_enemies)
|
||||
isPlural = n <> 1
|
||||
s = callGlobalFunc(GS_ENEMY_INTRO, 0, 0, 0)
|
||||
displayf2(s, n, p=>p_enemies=>s_name)
|
||||
n2 = countList(p=>p_enemies)
|
||||
isPlural = n2 <> 1
|
||||
s = concat(s, sprintf3("%s %d %s", p=>p_nextObj ?? "," :: " and", n2, p=>p_enemies=>s_name))
|
||||
isPlural = TRUE // for display on final string below
|
||||
p = p=>p_nextObj
|
||||
loop
|
||||
p = callGlobalFunc(GS_ENEMY_INTRO, 0, 0, 0)
|
||||
displayf2(p, n, s)
|
||||
|
||||
rawDisplayStr("\n\nDo you:")
|
||||
rawDisplayStr("\n")
|
||||
displayOption('B', "Battle")
|
||||
displayOption('F', "Flee")
|
||||
while TRUE
|
||||
@ -1160,6 +1186,7 @@ def _combat_zoneEncounter(s_encZone)#1
|
||||
rawDisplayStr("\n")
|
||||
|
||||
// Get the choice of each player or NPC
|
||||
anyAdvancing = FALSE
|
||||
forSome(global=>p_players, &(pl) canFight(pl) and nEnemiesFighting and !isFleeing, @playerCombatChoose)
|
||||
clearWindow()
|
||||
|
||||
|
@ -93,7 +93,7 @@ byte texturesLoaded = FALSE
|
||||
byte textDrawn = FALSE
|
||||
byte anyInteraction = FALSE
|
||||
byte textClearCountdown = 0
|
||||
export byte isPlural = 0 // valid values: 0 or $40
|
||||
export byte isPlural = 0
|
||||
byte inScript = FALSE
|
||||
export byte isFloppyVer
|
||||
|
||||
@ -429,6 +429,7 @@ addToString:
|
||||
sty ysav1
|
||||
inc inbuf
|
||||
ldy inbuf
|
||||
and #$7F
|
||||
sta inbuf,y
|
||||
ldy ysav1
|
||||
rts
|
||||
@ -440,7 +441,10 @@ end
|
||||
export asm finishString(isPlural)#1
|
||||
!zone {
|
||||
+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
|
||||
sta cswl
|
||||
lda prevCSWL+1+ABS_OFFSET ; put the cout vector back to default
|
||||
@ -454,7 +458,9 @@ export asm finishString(isPlural)#1
|
||||
inx
|
||||
lda inbuf,x ; get next input char
|
||||
iny
|
||||
and #$7F ; clear hi-bit for final string, in case it gets interned
|
||||
sta inbuf,y ; by default copy the char to output
|
||||
ora #$80
|
||||
cmp #"(" ; plural processing triggered by parentheses
|
||||
bne .notpar
|
||||
bvc .notpar ; but only parens directly after alpha char, e.g. preserving "Happy (and safe)."
|
||||
@ -462,13 +468,12 @@ export asm finishString(isPlural)#1
|
||||
dey ; undo copy of the paren
|
||||
stx tmp+1 ; save position in input
|
||||
dex ; needed for failsafe operation
|
||||
bit tmp ; set copy flag (V) initially
|
||||
bne .findsl ; to same as isPlural flag
|
||||
clv
|
||||
bit tmp ; set copy flag (V) initially to same as isPlural flag
|
||||
.findsl ; see if there's a slash within the parens
|
||||
inx
|
||||
cpx inbuf
|
||||
lda inbuf,x
|
||||
ora #$80 ; normalize hi-bit for comparisons below
|
||||
cmp #"/"
|
||||
bne +
|
||||
php
|
||||
@ -487,6 +492,7 @@ export asm finishString(isPlural)#1
|
||||
.plup
|
||||
inx
|
||||
lda inbuf,x
|
||||
ora #$80
|
||||
cmp #"/"
|
||||
bne +
|
||||
php
|
||||
@ -499,6 +505,7 @@ export asm finishString(isPlural)#1
|
||||
beq .notpar ; stop at closing paren
|
||||
bvc .plup ; if not in copy mode, skip copy
|
||||
iny
|
||||
and #$7F ; clear hi-bit for final string, in case it gets interned
|
||||
sta inbuf,y ; else do copy
|
||||
bne .plup ; always taken
|
||||
.notpar
|
||||
|
@ -197,7 +197,7 @@ struc TEnemyGroup
|
||||
byte t_type
|
||||
word p_nextObj
|
||||
word p_enemies
|
||||
byte b_enemyGroupRange
|
||||
byte b_enemyNum
|
||||
end
|
||||
|
||||
const TYPE_ENCOUNTER_ZONE = $89
|
||||
|
Loading…
x
Reference in New Issue
Block a user