Added ability to reload weapon from inventory screen. Added auto-reload after combat.

This commit is contained in:
Martin Haye 2019-09-12 07:20:54 -07:00
parent 37af866d24
commit cda4387507
4 changed files with 110 additions and 73 deletions

View File

@ -16,6 +16,7 @@ include "gen_items.plh"
include "gen_enemies.plh"
include "gen_modules.plh"
include "combat.plh"
include "itemutil.plh"
struc EnemyData
word s_en_name
@ -49,26 +50,25 @@ byte[] S_A = "a "
byte[] S_EMPTY = ""
// To save time, we preload global funcs
const NUM_PRELOADS = 3
byte[] toPreload = GS_COMBAT_PROMPT, GS_ENEMY_INTRO, GS_COMBAT_WIN
const NUM_PRELOADS = 4
byte[] toPreload = MOD_ITEMUTIL, GS_COMBAT_PROMPT, GS_ENEMY_INTRO, GS_COMBAT_WIN
word[4] preloads
word pItemUtil
///////////////////////////////////////////////////////////////////////////////////////////////////
def preload()#0
byte i
for i = 0 to NUM_PRELOADS-1
mmgr(QUEUE_LOAD, toPreload[i]<<8 | RES_TYPE_MODULE)
preloads[i] = mmgr(QUEUE_LOAD, toPreload[i]<<8 | RES_TYPE_MODULE)
next
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def freePreloaded()#0
byte i
word p
for i = 0 to NUM_PRELOADS-1
p = mmgr(FIND_IN_MEM, toPreload[i]<<8 | RES_TYPE_MODULE)
if p; mmgr(FREE_MEMORY, p); fin
mmgr(FREE_MEMORY, preloads[i])
next
mmgr(FINISH_LOAD, 0)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -362,52 +362,6 @@ def playerShoot(pPlayer, pWeapon)#0
damageEnemy(pPlayer, pEnemy, dmg, sAction)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def ammoMatches(ammo, kind)
if ammo->t_type <> TYPE_FANCY_ITEM; return FALSE; fin
return streqi(ammo=>s_itemKind, kind)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Reloads the given weapon, as full as it can be given the player's ammo supply.
def reload(pl, pWeapon, echo)#0
word item
byte orig, n
isPlural = FALSE
// If ammo type is null, it means weapon doesn't use ammo in traditional sense.
if !pWeapon=>s_ammoKind
pWeapon->b_clipCurrent = 1
return
fin
// Find matching ammo
ctx = pWeapon; item = first(pl=>p_items, &(p) ammoMatches(p, ctx=>s_ammoKind))
if !item
if echo
displayf3("\n%s has no ammo for %s %s!\n", pl=>s_name, hisHerTheir(pl->c_gender), pWeapon=>s_name))
fin
return
fin
// Transfer ammo to weapon
if pWeapon->b_clipSize
n = min(item=>w_count, pWeapon->b_clipSize - pWeapon->b_clipCurrent)
else
n = min(item=>w_count, 1) // clipSize=0 implies auto-reloading weapon
fin
pWeapon->b_clipCurrent = pWeapon->b_clipCurrent + n
item=>w_count = item=>w_count - n
if item=>w_count == 0
removeFromList(@pl=>p_items, item)
fin
if echo
displayf3("\n%s reloads %s %s.\n", pl=>s_name, hisHerTheir(pl->c_gender), pWeapon=>s_name)
fin
if combatDebug; displayf2("Moved %d ammo, %d left\n", n, item=>w_count); getUpperKey; fin
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def consumeAmmo(pl, pWeapon)#0
if !pWeapon->b_clipCurrent; fatal("clip zero"); fin // sanity check
@ -416,7 +370,7 @@ def consumeAmmo(pl, pWeapon)#0
// Special (for e.g. bows): clipSize zero means weapon reloads automatically
if !pWeapon->b_clipCurrent and !pWeapon->b_clipSize
reload(pl, pWeapon, FALSE) // silently reload
pItemUtil=>itemutil_reloadWeapon(pl, pWeapon, FALSE) // silently reload
fin
end
@ -605,7 +559,7 @@ def playerCombatChoose(pl)#0
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
reload(pl, pWeapon, FALSE) // silently reload
pItemUtil=>itemutil_reloadWeapon(pl, pWeapon, FALSE) // silently reload
fin
if pWeapon->b_clipCurrent
canShoot = TRUE
@ -680,6 +634,11 @@ def checkSingleUse(pl, pWeapon)#0
removeFromList(@pl=>p_items, pWeapon)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def getWeapon(pl)#1
return first(pl=>p_items, &(p) p->t_type == TYPE_WEAPON and p->b_flags & ITEM_FLAG_EQUIP)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def playerCombatTurn(pl)#0
word pWeapon, pGroup, pEnemy
@ -688,7 +647,7 @@ def playerCombatTurn(pl)#0
if !nEnemiesFighting; return; fin
// Get weapon
pWeapon = first(pl=>p_items, &(p) p->t_type == TYPE_WEAPON and p->b_flags & ITEM_FLAG_EQUIP)
pWeapon = getWeapon(pl)
// Execute the player's choice
when pl->b_combatChoice
@ -709,7 +668,7 @@ def playerCombatTurn(pl)#0
checkSingleUse(pl, pWeapon)
break
is 'R'
reload(pl, pWeapon, TRUE) // TRUE = echo
pItemUtil=>itemutil_reloadWeapon(pl, pWeapon, TRUE) // TRUE = echo
break
is 'C'
// Choose a different weapon
@ -1049,6 +1008,7 @@ def startCombat(mapCode)#1
// Display portrait of first group
setPortrait(global=>p_enemyGroups=>p_enemies->b_image)
mmgr(FINISH_LOAD, 0)
pItemUtil = preloads[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.
@ -1091,12 +1051,24 @@ def startCombat(mapCode)#1
return 0 // just to keep compiler happy
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def autoReload(pl)#0
pItemUtil=>itemutil_reloadWeapon(pl, getWeapon(pl), FALSE)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def processWin()#0
word gold, xp
rawDisplayStr("^T000")
word gold, xp, player, tmp
// Reload each player's weapon if possible
forEach(global=>p_players, &(pl) autoReload(pl))
// Clear the window, then display the win message
clearWindow
callGlobalFunc(GS_COMBAT_WIN, 0, 0, 0)
// Grab the loot
freePreloaded() // make max space avail for item tables
gold, xp = collectLootAndXP()
if gold > 0
displayf1("You find %d gold! ", addGold(gold))
@ -1105,8 +1077,7 @@ def processWin()#0
addXP_all(xp)
displayf1("You earn %d experience! ", xp)
fin
displayStr("\n(press any key)\n")
getUpperKey()
promptAnyKey(FALSE)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -1149,9 +1120,8 @@ def _combat_zoneEncounter(s_encZone)#1
freePreloaded()
return -99 // special code for death
elsif !nEnemiesFighting
processWin()
processWin() // also calls freePreloaded
// Note: no need to clear heap -- the caller does that.
freePreloaded()
return 1
elsif !p
break

View File

@ -27,7 +27,8 @@ include "itemutil.plh"
// in the same order as the constants are defined in the header.
predef _displayItemStats(pItem1, pItem2)#1
predef _displayItemName(pItem)#1
word[] funcTbl = @_displayItemStats, @_displayItemName
predef _reloadWeapon(pl, pWeapon, echo)#1
word[] funcTbl = @_displayItemStats, @_displayItemName, @_reloadWeapon
// Other global variables here
@ -123,15 +124,18 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
def displayWeaponStats(pItem1, pItem2)#0
displayTwoCol("Equip'd", pItem1, pItem2, b_flags, @equippedField, @formatEquipped)
displayTwoCol("Ammo", pItem1, pItem2, s_ammoKind, @wordField, @formatStr)
displayTwoCol("Clip", pItem1, pItem2, b_clipSize, @byteField, @formatNum)
displayTwoCol("Melee", pItem1, pItem2, r_meleeDmg, @wordField, @formatDice)
displayTwoCol("Proj", pItem1, pItem2, r_projectileDmg, @wordField, @formatDice)
displayTwoCol("Attack", pItem1, pItem2, ba_attacks+0, @byteField, @formatAttack)
displayTwoCol("Att 2", pItem1, pItem2, ba_attacks+1, @byteField, @formatAttack)
displayTwoCol("Att 3", pItem1, pItem2, ba_attacks+2, @byteField, @formatAttack)
displayTwoCol("Range", pItem1, pItem2, b_weaponRange, @byteField, @formatNum)
displayTwoCol("Equip'd", pItem1, pItem2, b_flags, @equippedField, @formatEquipped)
displayTwoCol("Ammo", pItem1, pItem2, s_ammoKind, @wordField, @formatStr)
displayTwoCol("Clip size", pItem1, pItem2, b_clipSize, @byteField, @formatNum)
if (pItem1->b_flags & ITEM_FLAG_EQUIP) and !pItem2
displayTwoCol("Current", pItem1, pItem2, b_clipCurrent, @byteField, @formatNum)
fin
displayTwoCol("Melee", pItem1, pItem2, r_meleeDmg, @wordField, @formatDice)
displayTwoCol("Proj", pItem1, pItem2, r_projectileDmg, @wordField, @formatDice)
displayTwoCol("Attack", pItem1, pItem2, ba_attacks+0, @byteField, @formatAttack)
displayTwoCol("Att 2", pItem1, pItem2, ba_attacks+1, @byteField, @formatAttack)
displayTwoCol("Att 3", pItem1, pItem2, ba_attacks+2, @byteField, @formatAttack)
displayTwoCol("Range", pItem1, pItem2, b_weaponRange, @byteField, @formatNum)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -205,6 +209,53 @@ def _displayItemName(pItem)#1
return 0
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def ammoMatches(ammo, kind)
if ammo->t_type <> TYPE_FANCY_ITEM; return FALSE; fin
return streqi(ammo=>s_itemKind, kind)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Reloads the given weapon, as full as it can be given the player's ammo supply.
def _reloadWeapon(pl, pWeapon, echo)#1
word item
byte orig, n
isPlural = FALSE
if !pWeapon; return 0; fin // if no weapon equipped, no reload possible
// If ammo type is null, it means weapon doesn't use ammo in traditional sense.
if !pWeapon=>s_ammoKind
pWeapon->b_clipCurrent = 1
return 0
fin
// Find matching ammo
ctx = pWeapon; item = first(pl=>p_items, &(p) ammoMatches(p, ctx=>s_ammoKind))
if !item
if echo
displayf3("\n%s has no ammo for %s %s!\n", pl=>s_name, hisHerTheir(pl->c_gender), pWeapon=>s_name))
fin
return 0
fin
// Transfer ammo to weapon
if pWeapon->b_clipSize
n = min(item=>w_count, pWeapon->b_clipSize - pWeapon->b_clipCurrent)
else
n = min(item=>w_count, 1) // clipSize=0 implies auto-reloading weapon
fin
pWeapon->b_clipCurrent = pWeapon->b_clipCurrent + n
item=>w_count = item=>w_count - n
if item=>w_count == 0
removeFromList(@pl=>p_items, item)
fin
if echo
displayf3("\n%s reloads %s %s.\n", pl=>s_name, hisHerTheir(pl->c_gender), pWeapon=>s_name)
fin
return 0
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Boilerplate module initialization code
return @funcTbl

View File

@ -11,6 +11,7 @@
// Each module-exported function needs its own constant, 0..n (multiples of 2)
const itemutil_displayItemStats = 0
const itemutil_displayItemName = 2
const itemutil_reloadWeapon = 4
const STATS_COL_1 = 45
const STATS_COL_2 = 140

View File

@ -409,6 +409,11 @@ def showItemMenu(item)#0
if isEquipped(item); rawDisplayStr("un-"); fin
rawDisplayStr("E)quip, ")
fin
if type == TYPE_WEAPON
if item->b_clipCurrent < item->b_clipSize
rawDisplayStr("R)eload, ")
fin
fin
rawDisplayStr("U)se, ")
if isSplittable(item); rawDisplayStr("S)plit, "); fin
if isJoinable(item); rawDisplayStr("J)oin, "); fin
@ -667,6 +672,16 @@ def interactWithItem(player, item)#1
return doTrade(player, item)
fin
break
is 'R'
if item->t_type == TYPE_WEAPON
if item->b_clipCurrent < item->b_clipSize
pItemutilModule()=>itemutil_reloadWeapon(player, item, FALSE)
displayDone
displayItems(item, NULL)
ok = TRUE
fin
fin
break
// Split a stack
is 'S'
if isSplittable(item)