Combat now paying attention to group selection.

This commit is contained in:
Martin Haye 2019-12-19 09:38:14 -08:00
parent 386494b8db
commit 41efac1db0
3 changed files with 69 additions and 50 deletions

View File

@ -36,6 +36,8 @@ struc EnemyData
byte b_en_gangChance
end
const COMBAT_CHOICE_Y = 54
predef _combat_zoneEncounter(s_encZone)#1
word[] funcTbl = @_combat_zoneEncounter
@ -139,34 +141,47 @@ def canFight(p)#1
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def chooseEnemy(maxDist)#1
word p
byte n, n2
// Phase 0: count alive matching enemies
// Phase 1: select matching enemy
def enemyLoop(maxDist, gangChoice, phase, choiceNum)
byte n
word pGroup, pEnemy
// First, determine how many enemies are within striking distance
n = 0
p = global=>p_combatFirst
while p
if p->t_type == TYPE_ENEMY and canFight(p) and p->b_enemyAttackRange <= maxDist
n++
pGroup = global=>p_enemyGroups
while pGroup
if !gangChoice or pGroup->b_enemyNum == gangChoice
pEnemy = pGroup=>p_enemies
while pEnemy
if pEnemy->t_type == TYPE_ENEMY and canFight(pEnemy) and pEnemy->b_enemyAttackRange <= maxDist
if phase == 1 and n == choiceNum; return pEnemy; fin
n++
fin
pEnemy = pEnemy=>p_nextObj
loop
fin
p = p=>p_combatNext
pGroup = pGroup=>p_nextObj
loop
return n
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def chooseEnemy(maxDist, gangChoice)#1
byte n
// See if there are any living enemies in the chosen gang that are in striking distance
n = enemyLoop(maxDist, gangChoice, 0, 0)
if n == 0
// Chosen gang may be all dead. Expand the selection to all gangs.
gangChoice = 0
n = enemyLoop(maxDist, gangChoice, 0, 0)
fin
// If nobody in range, ack!
if !n; return NULL; fin
// Pick one of the in-range enemies to attack.
n2 = rand16() % n
p = global=>p_combatFirst
while p
if p->t_type == TYPE_ENEMY and canFight(p) and p->b_enemyAttackRange <= maxDist
if n2 == 0; return p; fin
n2--
fin
p = p=>p_combatNext
loop
return NULL
return enemyLoop(maxDist, gangChoice, 1, rand16() % n)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -263,7 +278,7 @@ def playerMelee(pPlayer, pWeapon)#0
// Maybe in the future we'll put this under control of a global script
sAction = "swings"
pEnemy = chooseEnemy(5) // max distance 5 feet for melee
pEnemy = chooseEnemy(5, pPlayer->b_gangChoice) // max distance 5 feet for melee
if !pEnemy
if nEnemiesFighting
displayf2("\n%s foolishly %s, misses by a mile.\n", pPlayer=>s_name, sAction)
@ -321,7 +336,7 @@ def playerShoot(pPlayer, pWeapon)#0
sAction = pWeapon=>s_combatText
pEnemy = chooseEnemy(pWeapon->b_weaponRange)
pEnemy = chooseEnemy(pWeapon->b_weaponRange, pPlayer->b_gangChoice)
if !pEnemy
displayf2("\n%s %s but falls short.\n", pPlayer=>s_name, sAction)
return
@ -460,14 +475,14 @@ end
// Phase 0: count alive & in-range gangs
// Phase 1: display gangs
// Phase 2: select gang by number, return enemyNum
def groupLoop(minDist, phase, choiceNum)#1
def groupLoop(maxDist, phase, choiceNum)#1
word p
byte n
p = global=>p_enemyGroups
n = 0
while p
if p=>p_enemies->b_enemyAttackRange <= minDist and groupCanFight(p)
if p=>p_enemies->b_enemyAttackRange <= maxDist and groupCanFight(p)
if phase == 1
displayOption(n + 'A', formatGroupName(p, FALSE))
elsif phase == 2 and n == choiceNum
@ -481,12 +496,12 @@ def groupLoop(minDist, phase, choiceNum)#1
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def chooseGroup(pl, minDist)#1
def chooseGroup(pl, maxDist)#1
word p
byte nAvailGroups, choiceNum
// First, determine how many groups are alive and within striking distance
nAvailGroups = groupLoop(minDist, 0, 0)
nAvailGroups = groupLoop(maxDist, 0, 0)
// If nobody in range, ack!
if !nAvailGroups; return noneInRange(); fin
@ -496,7 +511,7 @@ def chooseGroup(pl, minDist)#1
choiceNum = 0
else
rawDisplayf1("^D%s attack:", pl=>s_name) // ctrl-D = clear to end of page
groupLoop(minDist, 1, 0)
groupLoop(maxDist, 1, 0)
while TRUE
choiceNum = getUpperKey()
if choiceNum == $1B // esc
@ -509,7 +524,7 @@ def chooseGroup(pl, minDist)#1
fin
// Success.
pl->b_gangChoice = groupLoop(minDist, 2, choiceNum)
pl->b_gangChoice = groupLoop(maxDist, 2, choiceNum)
return TRUE
end
@ -528,6 +543,12 @@ def chooseShotNumber(pl, pWeapon)#0
fin
next
// If it's a double-shot weapon but player has only 1 ammo, override to allow single-shot
if nChoices == 0 and pWeapon->ba_attacks[1] == 2 and pWeapon->b_clipCurrent == 1
pl->b_shotChoice = 1
return
fin
// If only one choice, we're done.
if nChoices <= 1; return; fin
@ -611,7 +632,6 @@ def playerCombatChoose(pl)#0
word p, pWeapon
byte key
byte canShoot, canReload, canChange, canAdvance, redisplayMenu
word cursX, cursY
// The party advances all at once
if isAdvancing
@ -619,10 +639,6 @@ def playerCombatChoose(pl)#0
return
fin
// Before we start, save the cursor location so we can
// later restore it for the next player's choice.
cursX, cursY = getCursor()
// Count all weapons and get currently equipped
canShoot = FALSE
canReload = FALSE
@ -647,8 +663,9 @@ def playerCombatChoose(pl)#0
// Let them know their options
if redisplayMenu
setCursor(cursX, cursY)
setCursor(0, COMBAT_CHOICE_Y)
rawDisplayStr("^D") // clear to end of page
callGlobalFunc(GS_COMBAT_PROMPT, 0, 0, 0)
displayf1("%s:\nM)elee, ", pl=>s_name)
if pWeapon
// Special (for e.g. bows): clipSize zero means weapon reloads automatically
@ -675,7 +692,7 @@ def playerCombatChoose(pl)#0
canAdvance = (pl == global=>p_players) and (minEnemyDist() > 5) // only one advance per turn
if canAdvance; displayStr("A)dvance, "); fin
displayStr("F)lee")
setCursor(cursX, cursY)
setCursor(0, COMBAT_CHOICE_Y)
fin
pl->b_combatChoice = getUpperKey()
@ -686,7 +703,7 @@ def playerCombatChoose(pl)#0
redisplayMenu = TRUE
break
fin
setCursor(cursX, cursY)
setCursor(0, COMBAT_CHOICE_Y)
return
is 'F'
isFleeing = TRUE
@ -699,9 +716,9 @@ def playerCombatChoose(pl)#0
redisplayMenu = TRUE
break
fin
setCursor(cursX, cursY)
setCursor(0, COMBAT_CHOICE_Y)
chooseShotNumber(pl, pWeapon)
setCursor(cursX, cursY)
setCursor(0, COMBAT_CHOICE_Y)
return
fin
break
@ -711,7 +728,7 @@ def playerCombatChoose(pl)#0
is 'C'
if canChange
chooseWeapon(pl, pWeapon)
setCursor(cursX, cursY)
setCursor(0, COMBAT_CHOICE_Y)
return
fin
break
@ -783,6 +800,7 @@ def playerCombatTurn(pl)#1
is 'R'
displayStr("\n")
pItemUtil=>itemutil_reloadWeapon(pl, pWeapon, TRUE) // TRUE = echo
displayStr("\n")
break
is 'C'
// Choose a different weapon
@ -1297,8 +1315,9 @@ def _combat_zoneEncounter(s_encZone)#1
// Tell what the party currently faces
clearWindow()
displayOpponents()
callGlobalFunc(GS_COMBAT_PROMPT, 0, 0, 0)
rawDisplayStr("\n")
// Make sure portrait reflects a living enemy
setPortrait(first(global=>p_enemyGroups, @groupCanFight)=>p_enemies->b_image)
// Get the choice of each player or NPC
isAdvancing = FALSE

View File

@ -107,6 +107,7 @@ export byte portraitNum = 0
word triggerOriginX, triggerOriginY
word triggerTbl
byte cmdKey // last command key pressed
word cmdTbl[96] // ASCII $00..$5F
byte frameLoaded = 0
word curEngine = NULL
@ -1507,6 +1508,8 @@ export def getCharResponse()#1
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Select the num'th entry in the list for which the selector returns TRUE. A NULL selector
// is considered to always be TRUE.
export def select(p, sel, num)#1
while p
if !sel
@ -2430,7 +2433,7 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Get a key and dispatch it to a command. Then do it again, forever.
def kbdLoop()#0
word key, func, tmp
word func, tmp
byte xreg
xreg = getXReg()
while TRUE
@ -2438,9 +2441,9 @@ def kbdLoop()#0
// the X register should always have the same value.
tmp = getXReg()
if tmp <> xreg; printHex(xreg<<8 | tmp); fatal("xRegChg"); fin
key = getUpperKey()
if key >= 0 and key < $60
func = cmdTbl[key]
cmdKey = getUpperKey()
if cmdKey >= 0 and cmdKey < $60
func = cmdTbl[cmdKey]
if func
if textClearCountdown
if textClearCountdown == 1; clearTextWindow(); fin
@ -2979,7 +2982,6 @@ end
def cheatCmd()#1
byte key
word pModule
key = charToUpper((^kbd) & $7F)
// We don't use laodEngine, since godmode may use it too, and we'd get double modules
textureControl(FALSE) // seems to be necessary for teleport-to-3d to work right
@ -2988,7 +2990,7 @@ def cheatCmd()#1
pModule = mmgr(QUEUE_LOAD, MOD_GODMODE<<8 | RES_TYPE_MODULE)
mmgr(FINISH_LOAD, 0)
pModule()=>godmode_cheatCmd(key)
pModule()=>godmode_cheatCmd(cmdKey)
mmgr(FREE_MEMORY, pModule)
textureControl(TRUE)

View File

@ -222,8 +222,6 @@ def _reloadWeapon(pl, pWeapon, echo)#1
word item
byte orig, n
printf3("reload for pl=%s weapon=%s echo=%d\n", pl=>s_name, pWeapon=>s_name, echo)
isPlural = FALSE
if !pWeapon; return 0; fin // if no weapon equipped, no reload possible
@ -237,7 +235,7 @@ def _reloadWeapon(pl, pWeapon, echo)#1
ctx = pWeapon; item = first(pl=>p_items, &(p) ammoMatches(p, ctx=>s_ammoKind))
if !item
if echo
displayf3("%s has no ammo to reload %s %s!\n", pl=>s_name, hisHerTheir(pl->c_gender), pWeapon=>s_name))
displayf3("%s has no ammo to reload %s %s! ", pl=>s_name, hisHerTheir(pl->c_gender), pWeapon=>s_name))
fin
return 1
fin
@ -256,7 +254,7 @@ def _reloadWeapon(pl, pWeapon, echo)#1
if item=>w_count == 0
displayStr(" and is now out")
fin
displayStr(".\n")
displayStr(". ")
fin
if item=>w_count == 0
removeFromList(@pl=>p_items, item)