Coded ammo consumption and reloading. Now to test test test.

This commit is contained in:
Martin Haye 2017-07-15 11:41:29 -07:00
parent 07ba262072
commit e2b0d2d51d
5 changed files with 157 additions and 37 deletions

View File

@ -2519,6 +2519,19 @@ end
return val
}
def parseBooleanAttr(row, attrName)
{
def val = parseStringAttr(row, attrName)
return (val.toLowerCase() ==~ /yes|true|1/) ? "TRUE" : "FALSE"
}
def parseGenderAttr(row, attrName)
{
def val = parseStringAttr(row, attrName)
if (!val) return 0
return val.charAt(0).toUpperCase()
}
def parseModifier(row, attr1, attr2)
{
def bonusValue = parseWordAttr(row, attr1)
@ -2550,7 +2563,8 @@ end
"${parseByteAttr(row, "semi-auto-shots")}, " +
"${parseByteAttr(row, "auto-shots")}, " +
"${parseByteAttr(row, "range")}, " +
"${escapeString(parseStringAttr(row, "combat-text"))})")
"${escapeString(parseStringAttr(row, "combat-text"))}, " +
"${parseBooleanAttr(row, 'single-use')})")
}
def genArmor(func, row, out)
@ -2603,7 +2617,8 @@ end
"${Math.max(1, parseByteAttr(row, "level"))}, " +
"${parseByteAttr(row, "aiming")}, " +
"${parseByteAttr(row, "hand-to-hand")}, " +
"${parseByteAttr(row, "dodging")})")
"${parseByteAttr(row, "dodging")}, " +
"${parseGenderAttr(row, "gender")})")
row.attributes().sort().eachWithIndex { name, val, idx ->
if (name =~ /^skill-(.*)/) {
out.println(" addToList(@p=>p_skills, " +
@ -2750,18 +2765,24 @@ def makeWeapon_pt1(name, kind, price, modifier, ammoKind, clipSize, meleeDmg, pr
p=>p_modifiers = modifier
p=>s_ammoKind = mmgr(HEAP_INTERN, ammoKind)
p->b_clipSize = clipSize
p->b_clipCurrent = clipSize
p->b_clipCurrent = 0
if !p->b_clipSize and projectileDmg
p->b_clipCurrent = 1 // auto-reloading, e.g. bows
fin
p=>r_meleeDmg = meleeDmg
p=>r_projectileDmg = projectileDmg
return p
end
def makeWeapon_pt2(p, attack0, attack1, attack2, weaponRange, combatText)
def makeWeapon_pt2(p, attack0, attack1, attack2, weaponRange, combatText, singleUse)
p->ba_attacks[0] = attack0
p->ba_attacks[1] = attack1
p->ba_attacks[2] = attack2
p->b_weaponRange = weaponRange
p=>s_combatText = mmgr(HEAP_INTERN, combatText)
if singleUse
p->b_flags = WEAPON_FLAG_SINGLE_USE
fin
return p
end
@ -2887,7 +2908,7 @@ def makePlayer_pt1(name, intelligence, strength, agility, stamina, charisma, spi
return p
end
def makePlayer_pt2(p, health, level, aiming, handToHand, dodging)#1
def makePlayer_pt2(p, health, level, aiming, handToHand, dodging, gender)#1
p=>w_health = health
p->b_level = level
p=>w_maxHealth = health
@ -2896,6 +2917,7 @@ def makePlayer_pt2(p, health, level, aiming, handToHand, dodging)#1
p->b_aiming = aiming
p->b_handToHand = handToHand
p->b_dodging = dodging
p->c_gender = gender
initPlayerXP(p)
return p
end

View File

@ -123,9 +123,8 @@ def rollPlayerHit(pPlayer, pWeapon, pEnemy, sAction)
if combatDebug; displayf1("Final chnc = %d%%\n", chance); fin
// See if it's a hit
// TODO: factor in luck
roll = rand16() % 100
if combatDebug; displayf2("Roll=%d Hit=%d\n", roll, abs(roll < chance)); getUpperKey(); fin
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)
displayf3("\n%s %s at %s but misses.\n", pPlayer=>s_name, sAction, pEnemy=>s_name)
@ -201,8 +200,7 @@ def playerMelee(pPlayer, pWeapon)#0
dmg = encodeDice(scanModifiers(pPlayer=>p_skills, @S_HAND_TO_HAND)+1, 4, 0)
fin
if combatDebug; displayf3("Base dmg: %dd%d+%d = ", (dmg>>12) & $F, (dmg >> 8) & $F, dmg & $FF); fin
// TODO: factor in luck
dmg = rollDice(dmg)
dmg = rollDiceWithLuck(dmg, pPlayer->b_luck)
if combatDebug; displayf1("%d\n", dmg); fin
// Damage bonus is:
@ -259,8 +257,7 @@ def playerShoot(pPlayer, pWeapon)#0
if combatDebug
displayf3("Base dmg: %dd%d+%d = ", (pWeapon=>r_projectileDmg>>12) & $F, (pWeapon=>r_projectileDmg >> 8) & $F, pWeapon=>r_projectileDmg & $FF)
fin
// TODO: factor in luck
dmg = rollDice(pWeapon=>r_projectileDmg)
dmg = rollDiceWithLuck(pWeapon=>r_projectileDmg, pPlayer->b_luck)
if combatDebug; displayf1("%d\n", dmg); fin
// Damage bonus is:
@ -281,6 +278,56 @@ def playerShoot(pPlayer, pWeapon)#0
damageEnemy(pPlayer, pEnemy, dmg, sAction)
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
setPlural(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
item = pl=>p_items
while item
if item->t_type == TYPE_STUFF
if streqi(item=>s_itemKind, pWeapon=>s_ammoKind); break; fin
fin
item = item=>p_nextObj
loop
if !item
displayf3("\n%s has no ammo for %s %s!\n", pl=>s_name, hisHerTheir(pl->c_gender), pWeapon=>s_name))
return
fin
// Transfer ammo to weapon
n = max(item=>w_count, pWeapon->b_clipSize - pWeapon->b_clipCurrent)
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
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def consumeAmmo(pl, pWeapon)#0
if !pWeapon->b_clipCurrent; fatal("clip zero"); fin // sanity check
pWeapon->b_clipCurrent = pWeapon->b_clipCurrent - 1
// 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
fin
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def playerDodge(pPlayer)#0
// no need to display anything. Actual dodging mechanics handled in enemy attack function.
@ -468,14 +515,15 @@ def playerCombatChoose(pl)#0
rawDisplayf1("^D%s choice:", pl=>s_name)
displayOption('M', "Melee")
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
fin
if pWeapon->b_clipCurrent
canShoot = TRUE
displayOption('S', "Shoot")
fin
if pWeapon->b_clipCurrent < pWeapon->b_clipSize
// TODO: Need to check for enough ammo, and use it up.
// TODO: If clip size is zero, weapon reloads automatically (e.g. bow with arrows)
// TODO: Ammo kind is NULL, weapon doesn't use ammo at all
canReload = TRUE
displayOption('R', "Reload")
fin
@ -487,6 +535,7 @@ def playerCombatChoose(pl)#0
displayOption('F', "Flee")
canAdvance = minEnemyDist() > 5
if canAdvance; displayOption('A', "Advance"); fin
setCursor(cursX, cursY)
while TRUE
pl->b_combatChoice = getUpperKey()
@ -500,8 +549,8 @@ def playerCombatChoose(pl)#0
return
is 'S'
if canShoot
setCursor(cursX, cursY)
chooseShotNumber(pl, pWeapon)
setCursor(cursX, cursY)
return
fin
break
@ -510,8 +559,8 @@ def playerCombatChoose(pl)#0
break
is 'C'
if canChange
setCursor(cursX, cursY)
chooseWeapon(pl, pWeapon)
setCursor(cursX, cursY)
return
fin
break
@ -530,6 +579,14 @@ def playerCombatChoose(pl)#0
loop
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Called after a single-use weapon is used (regardless of whether it hit or not)
def checkSingleUse(pl, pWeapon)#0
if !pWeapon; return; fin
if !(pWeapon->b_flags & WEAPON_FLAG_SINGLE_USE); return; fin
removeFromList(@pl=>p_items, pWeapon)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def playerCombatTurn(pl)#0
word pWeapon, pGroup, pEnemy
@ -548,7 +605,7 @@ def playerCombatTurn(pl)#0
when pl->b_combatChoice
is 'M'
playerMelee(pl, pWeapon)
// TODO: check for single-use weapon, consume it after use (even if it didn't hit)
checkSingleUse(pl, pWeapon)
break
is 'F'
break
@ -557,22 +614,20 @@ def playerCombatTurn(pl)#0
break
is 'S'
for i = 1 to pl->b_shotChoice
if nEnemiesFighting
if nEnemiesFighting and pWeapon->b_clipCurrent
playerShoot(pl, pWeapon)
consumeAmmo(pl, pWeapon)
if i+1 < pl->b_shotChoice; combatPause; fin
fin
next
// TODO: check for single-use weapon, consume it after use (even if it didn't hit)
checkSingleUse(pl, pWeapon)
break
is 'R'
pWeapon->b_clipCurrent = pWeapon->b_clipSize
// TODO: Consume ammo here
setPlural(FALSE)
displayf1("\n%s reloads.\n", pl=>s_name)
reload(pl, pWeapon, TRUE) // TRUE = echo
break
is 'C'
// Choose a different weapon
displayf2("\n%s switches to %s.\n", pl=>s_name, pWeapon=>s_name)
displayf3("\n%s switches to %s %s.\n", pl=>s_name, hisHerTheir(pl->c_gender), pWeapon=>s_name)
break
is 'A'
// Advance 5 feet
@ -607,7 +662,7 @@ def enemyCombatTurn(pe)#1
displayf3("\n%s %s %s ", pe=>s_name, pe=>s_attackText, pl=>s_name)
// Roll to hit
roll = rand16() % 100
roll = rollPercentileWithLuck(pl->b_luck) // player luck can raise roll, reducing enemy chance to hit
if combatDebug; displayf2("\nenemy hit roll=%d, need < %d\n", roll, pe->b_chanceToHit); fin
needShow = FALSE
if roll < pe->b_chanceToHit
@ -634,15 +689,12 @@ def enemyCombatTurn(pe)#1
// Max dodge chance is 90%, min is 0.
dodgeChance = max(0, min(90, dodgeChance))
// TODO: factor in luck
roll = rand16() % 100
roll = rollPercentileWithLuck(-(pl->b_luck)) // negate so luck has chance of reducing roll = better
if combatDebug; displayf2("dodge roll %d, need < %d\n", roll, dodgeChance); fin
if roll < dodgeChance
displayf1("but %s dodges!", pl=>s_name)
else
// TODO: factor in luck
dam = rollDice(pe=>r_enemyDmg)
dam = rollDiceWithLuck(pe=>r_enemyDmg, -(pl->b_luck)) // player luck can reduce damage inflicted by enemy
if combatDebug; displayf1("base dmg %d\n", dam); fin
// Each point of armor-value reduces damage 2%, up to max of 90%
damReduce = max(0, min(90, pl->b_armor * 2))
@ -765,9 +817,8 @@ def collectLootAndXP()#2
while group
enemies = group=>p_enemies
while enemies
// TODO: factor in luck
gold = gold + rollDice(enemies=>r_goldLoot)
xp = xp + rollDice(enemies=>r_enemyXP)
gold = gold + rollDiceWithLuck(enemies=>r_goldLoot, global=>p_players->b_luck)
xp = xp + rollDiceWithLuck(enemies=>r_enemyXP, global=>p_players->b_luck)
enemies = enemies=>p_nextObj
loop
group = group=>p_nextObj

View File

@ -60,6 +60,7 @@ import gamelib
predef getYN()#1
predef girdPlayer(player)#0
predef giveItemToPlayer(p_player, itemFuncNum)#0
predef hisHerTheir(c_gender)#1
predef initHeap(loadedSize)#0
predef initPlayerXP(player)#0
predef loadFrameImg(img)#0
@ -98,6 +99,8 @@ import gamelib
predef rightJustifyNum(num, rightX)#0
predef rightJustifyStr(str, rightX)#0
predef rollDice(encoded)#1
predef rollDiceWithLuck(encoded, luck)#1
predef rollPercentileWithLuck(luck)#1
predef scanForNamedObj(p_obj, name)#1
predef scriptCombat(mapCode)#1
predef scriptDisplayStr(str)#0
@ -153,4 +156,7 @@ import gamelib
// Next: common events
byte[] S_ENTER, S_LEAVE, S_USE
// Next: other useful strings
byte[] S_HIS, S_HER, S_THEIR
end

View File

@ -140,6 +140,9 @@ export byte[] S_SP = "SP"
export byte[] S_ENTER = "Enter"
export byte[] S_LEAVE = "Leave"
export byte[] S_USE = "Use"
export byte[] S_HIS = "his"
export byte[] S_HER = "her"
export byte[] S_THEIR = "their"
///////////////////////////////////////////////////////////////////////////////////////////////////
// Definitions used by assembly code
@ -1294,19 +1297,50 @@ export def encodeDice(nDice, dieSize, add)#1 // ndice=0..15, dieSize=0..15, add
end
///////////////////////////////////////////////////////////////////////////////////////////////////
export def rollDice(encoded)#1
byte i, nDice, dieSize, add, result
export def rollDiceWithLuck(encoded, luck)#1
byte i, nDice, dieSize, add, droll, result
nDice = encoded >> 12
dieSize = (encoded >> 8) & $F
add = encoded & $F
result = add
for i = 1 to nDice
droll = (rand16() % dieSize) + 1
if luck > 0
if (rand16() % 100) < (luck * 2)
droll = max(droll, (rand16() % dieSize) + 1)
fin
elsif luck < 0
if (rand16() % 100) < (luck * -2)
droll = min(droll, (rand16() % dieSize) + 1)
fin
fin
add = (rand16() % dieSize) + 1
result = result + add
next
return result
end
///////////////////////////////////////////////////////////////////////////////////////////////////
export def rollDice(encoded)#1
return rollDiceWithLuck(encoded, 0)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
export def rollPercentileWithLuck(luck)#1
byte result
result = rand16() % 100
if luck > 0
if (rand16() % 100) < (luck * 2)
result = max(result, rand16() % 100)
fin
elsif luck < 0
if (rand16() % 100) < (luck * -2)
result = min(result, rand16() % 100)
fin
fin
return result
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Look up the partition for a resource.
// sectioNum: 1=map2d, 2=map3d, 3=portrait
@ -2856,6 +2890,14 @@ export def getStat(player, statName)#1
puts(statName); return fatal("Unknown stat")
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Return an appropriate pronoun for the given gender (M/F/N...)
export def hisHerTheir(gender)#1
if gender == 'M'; return @S_HIS; fin
if gender == 'F'; return @S_HER; fin
return @S_THEIR
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def clampByte(val)#1
return max(0, min(255, val))

View File

@ -124,7 +124,6 @@ end
const ITEM_FLAG_EQUIP = $80 // only one weapon/armor equipped (in use) at a time
const WEAPON_FLAG_SINGLE_USE = $01
const WEAPON_FLAG_WHOLE_GROUP = $02
const TYPE_WEAPON = $85
struc Weapon