Multiple loot groups per enemy now fully implemented.

This commit is contained in:
Martin Haye 2020-02-13 06:58:23 -08:00
parent 562a1bf285
commit dd9690ad82
4 changed files with 192 additions and 88 deletions

View File

@ -132,6 +132,7 @@ class A2PackPartitions
def automapSpecials = []
def stories = [:] // story text to story.num, story.text
def storyPartition = 0
def lootCodes = [:] // loot code to loot.num
def automapExitTile = -1
def maxMapSections = 0
@ -139,7 +140,6 @@ class A2PackPartitions
def itemNameToFunc = [:]
def playerNameToFunc = [:]
def allLootCodes
def nWeapons
def nArmors
@ -3118,7 +3118,10 @@ class A2PackPartitions
// significant, calculate a digest and replace the excess characters
// with part of it.
def bigHash = Integer.toString(result.hashCode(), 36)
result = result.substring(0, 10) + "_" + bigHash.substring(bigHash.length() - 4)
def toAdd = bigHash.substring(bigHash.length() - 4)
if (allUpper)
toAdd = toAdd.toUpperCase()
result = result.substring(0, 10) + "_" + toAdd
}
return result
}
@ -3159,18 +3162,15 @@ class A2PackPartitions
return String.format("\$%X", ((nDice << 12) | (dieSize << 8) | add))
}
def validateLootCode(code, strings = null)
def validateLootCode(code)
{
if (!code || code == "0")
return "NULL"
if (!allLootCodes.contains(code.toLowerCase())) {
return "0"
if (!lootCodes.containsKey(code.toLowerCase())) {
printWarning("Unknown loot-code '$code'")
return "NULL"
return "0"
}
if (strings)
return "@${strings[code.toLowerCase()]}"
else
return escapeString(code.toLowerCase())
return "LOOT_${humanNameToSymbol(code.toLowerCase(), true)}"
}
def extractEnemyStrings(row, strings)
@ -3216,12 +3216,18 @@ class A2PackPartitions
def experience = row.@experience; assert experience
def mapCode = row.@"map-code"; assert mapCode
def groupSize = row.@"group-size"; assert groupSize
def lootChance = row.@"loot-chance"; // optional, defaults to 10%
lootChance = "100"; println("FIXME: setting lootChance to 100")
def lootCode = row.@"loot-code" // optional
def lootChances = row.@"loot-chance"; // optional, defaults to 10%
def lootCodes = row.@"loot-code" // optional
def goldLoot = row.@"gold-loot"; assert goldLoot
def gangChance = row.@"gang-chance"; // optional
def parsedLootCodes = (lootCodes ? lootCodes : "").split(/[,.;]/).collect { validateLootCode(it.trim()) }
if (parsedLootCodes.size() > 2)
throw new Exception("Max of two loot codes allowed")
def parsedLootChances = (lootChances ? lootChances : "").split(/[,.;]/).collect { it.toInteger() }
if (parsedLootChances.size() > parsedLootCodes.size())
throw new Exception("Excess loot chances - or not enough loot codes")
out.println("word = " +
"@${strings[name]}, ${parseDice(hitPoints)} // name, hit dice")
out.println("byte = PO${humanNameToSymbol(image1, false)}, " +
@ -3233,9 +3239,11 @@ class A2PackPartitions
out.println("word = ${parseDice(damage)}, " +
"${parseDice(experience)}, " +
"${parseDice(groupSize)} // damage dice, exp dice, group size dice")
out.println("byte = ${lootChance ? lootChance.toInteger() : 10} // loot chance")
out.println("word = ${validateLootCode(lootCode, strings)}, " +
"${parseDice(goldLoot)} // lootCode, goldLoot")
out.println("byte = ${parsedLootChances.size() >= 1 ? parsedLootChances[0] : 10}, " +
"${parsedLootChances.size() >= 2 ? parsedLootChances[1] : 10} // loot chances")
out.println("byte = ${parsedLootCodes.size() >= 1 ? parsedLootCodes[0] : "NULL"}, " +
"${parsedLootCodes.size() >= 2 ? parsedLootCodes[1] : "NULL"} // loot codes")
out.println("word = ${parseDice(goldLoot)} // goldLoot")
out.println("byte = ${gangChance ? gangChance.toInteger() : 0} // gang chance")
out.println("")
@ -3310,6 +3318,7 @@ class A2PackPartitions
out.println()
out.println("include \"globalDefs.plh\"")
out.println("include \"gen_images.plh\"")
out.println("include \"gen_items.plh\"")
out.println()
def strings = [:]
@ -3637,9 +3646,12 @@ class A2PackPartitions
def storeAmount = parseWordAttr(row, "store-amount")
def lootAmount = parseDiceAttr(row, "loot-amount")
if ("$kind, $modifier, $count, $storeAmount, $lootAmount" != ", NULL, 0, 0, 0")
if ("$kind, $modifier, $count, $storeAmount, $lootAmount" != ", NULL, 0, 0, 0") {
if (count > 0 && !name.contains("("))
printWarning("countable item should have (singular/plural) in name")
out.println(" return makeFancyItem(${escapeString(name)}, $price, " +
"@${strings[kind]}, $modifier, $count, $storeAmount, $lootAmount)")
}
else
out.println(" return makePlainItem(${escapeString(name)}, $price)")
}
@ -3740,10 +3752,17 @@ class A2PackPartitions
{
out.println("def $funcName(code)#1")
codeToFunc.sort().each { code, funcs ->
def s = (strings != null && strings[code]) ? "@${strings[code]}" : escapeString(code)
out.println(" if streqi(code, $s); return @$prefix${humanNameToSymbol(code, false)}; fin")
if (prefix == "lootCode_")
out.println(" if code == LOOT_${humanNameToSymbol(code, true)}; return @$prefix${humanNameToSymbol(code, false)}; fin")
else {
def s = (strings != null && strings[code]) ? "@${strings[code]}" : escapeString(code)
out.println(" if streqi(code, $s); return @$prefix${humanNameToSymbol(code, false)}; fin")
}
}
out.println(" puts(code)")
if (prefix == "lootCode_")
out.println(" printf1(\"%d\", code)")
else
out.println(" puts(code)")
out.println(" return fatal(\"$funcName\")")
out.println("end\n")
}
@ -3793,7 +3812,7 @@ class A2PackPartitions
addCodeToFunc(itemNum, row.@"loot-code", lootCodeToItemNums) // no underscore because item num instead of func
addCodeToFunc(itemNum, row.@"store-code", storeCodeToItemNums) // ...ditto
}
allLootCodes = (lootCodeToItemNums.keySet().collect{ it.toLowerCase() }) as Set
lootCodeToItemNums.keySet().each { lootCodes[it.toLowerCase()] = [num: lootCodes.size() + 1] }
// Make constants for the function table
new File("build/src/plasma/gen_items.plh.new").withWriter { out ->
@ -3801,6 +3820,12 @@ class A2PackPartitions
out.println("const items_forLootCode = 0")
out.println("const items_forStoreCode = 2")
out.println("")
lootCodes.keySet().sort().each {
out.println("const LOOT_${humanNameToSymbol(it, true)} = ${lootCodes[it].num}")
}
out.println("")
items.each { typeName, itemNum, index, row ->
out.println("const ${itemNum} = ${index}")
if (index == nWeapons)

View File

@ -30,8 +30,10 @@ struc EnemyData
word r_en_dmgDice
word r_en_expDice
word r_en_groupSize
byte b_en_lootChance
word s_en_lootCode
byte b_en_lootChance0
byte b_en_lootChance1
byte b_en_lootCode0
byte b_en_lootCode1
word r_en_goldLoot
byte b_en_gangChance
end
@ -52,6 +54,8 @@ byte isAdvancing
byte[] S_AN = "an "
byte[] S_A = "a "
byte[] S_EMPTY = ""
byte[] S_COMMA = ", "
byte[] S_AND = " and "
// To save time, we preload global funcs
const NUM_PRELOADS = 4
@ -976,8 +980,10 @@ def makeEnemy(pData)
p=>r_enemyDmg = pData=>r_en_dmgDice
p=>r_enemyXP = pData=>r_en_expDice
p=>r_groupSize = pData=>r_en_groupSize
p->b_lootChance = pData->b_en_lootChance
if pData=>s_en_lootCode; p=>s_lootCode = mmgr(HEAP_INTERN, pData=>s_en_lootCode); fin
p->b_lootChance0 = pData->b_en_lootChance0
p->b_lootChance1 = pData->b_en_lootChance1
p->b_lootCode0 = pData->b_en_lootCode0
p->b_lootCode1 = pData->b_en_lootCode1
p=>r_goldLoot = pData=>r_en_goldLoot
return p
end
@ -1096,69 +1102,147 @@ def youFind(pItem, suffix)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def addIfPossible(pl, pItem)#1
word pComp
pComp = scanForNamedObj(pl=>p_items, pItem=>s_name)
if pComp
if pItem->t_type == TYPE_FANCY_ITEM
pComp=>w_count = min(30000, pComp=>w_count + pItem=>w_count)
return youFind(pItem, "")
// Returns: 0 = dupe
// -1 = no room
// 1 = added normally
// 2 = override-added quest item
// 3 = combined with existing stackable
def fullAddItem(pItem, doit)
word pPlayer, pComp, addToPlayer
pPlayer = global=>p_players
addToPlayer = NULL
while pPlayer
pComp = scanForNamedObj(pPlayer=>p_items, pItem=>s_name)
if pComp
if pComp->t_type == TYPE_FANCY_ITEM and pComp=>w_count > 0
pComp=>w_count = min(30000, pComp=>w_count + pItem=>w_count)
return 3 // combined
fin
if !pComp=>w_price
return 0 // dupe quest item
fin
fin
if !addToPlayer and roomInPack(pPlayer); addToPlayer = pPlayer; fin
pPlayer = pPlayer=>p_nextObj
loop
if addToPlayer
if doit; addToList(@addToPlayer=>p_items, pItem); fin
return 1
fin
if pItem=>w_price
return -1 // no room, and item has a price so it's not a quest item
fin
// Override: quest items allowed to exceed pack limitations
if doit; addToList(@global=>p_players=>p_items, pItem); fin
return 2
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def lootItem(ppItems, lootChance, lootCode)#0
word pItemTblModule, itemNum, pItem, pComp
if lootCode == 0; return; fin
if rollPercentileWithLuck(-(global=>p_players->b_luck)) >= lootChance; return; fin
mmgr(START_LOAD, 1) // code is in partition 1
pItemTblModule = mmgr(QUEUE_LOAD, MOD_GEN_ITEM_TABLES<<8 | RES_TYPE_MODULE)
mmgr(FINISH_LOAD, 0)
itemNum = randomFromArray(pItemTblModule()=>items_forLootCode(lootCode), NULL)
pItem = createItem(itemNum)
if pItem->t_type == TYPE_FANCY_ITEM
pItem=>w_count = rollDiceWithLuck(pItem=>r_lootAmount, global=>p_players->b_luck)
fin
mmgr(FREE_MEMORY, pItemTblModule)
pComp = scanForNamedObj(*ppItems, pItem=>s_name)
if pComp and pItem->t_type == TYPE_FANCY_ITEM
pComp=>w_count = min(30000, pComp=>w_count + pItem=>w_count)
elsif fullAddItem(pItem, FALSE) >= 1 // trial-run, don't add to main inv
addToList(ppItems, pItem)
fin
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def calcListSuffix(pThing, final)#1
if pThing=>p_nextObj
if pThing=>p_nextObj=>p_nextObj
return @S_COMMA
else
return FALSE // duplicate item
return @S_AND
fin
fin
if roomInPack(pl)
addToList(@pl=>p_items, pItem)
return youFind(pItem, "")
fin
return FALSE // no room
return final
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def addItem(pItem)#0
byte ok
ctx = pItem; ok = first(global=>p_players, &(pl) addIfPossible(pl, ctx))
if ok; return; fin
if first(global=>p_players, &(pl) scanForNamedObj(pl=>p_items, ctx=>s_name))
return // duplicate item, silently pretend it didn't happen
fin
youFind(pItem, " but have no room")
def dispLoot(pItems)#0
word pItem, final, suffix
final = "!\n"
displayStr("You find ")
pItem = pItems
while pItem
suffix = calcListSuffix(pItem, final)
if pItem->t_type == TYPE_FANCY_ITEM and pItem=>w_count > 1
isPlural = TRUE
displayf3("%d %s%s", pItem=>w_count, pItem=>s_name, suffix)
else
isPlural = FALSE
displayf2("%s%s", pItem=>s_name, suffix)
fin
pItem = pItem=>p_nextObj
loop
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def collectLootAndXP()#2
word group, enemy, gold, xp, pItemTblModule, itemNum, pItem
byte lootedItem
def collectLootAndXP()#0
word group, enemy, gold, xp, pItems, pItem, first
gold = 0
xp = 0
pItems = NULL
group = global=>p_enemyGroups
lootedItem = FALSE
while group
enemy = group=>p_enemies
if enemy
lootItem(@pItems, enemy->b_lootChance0, enemy->b_lootCode0)
lootItem(@pItems, enemy->b_lootChance1, enemy->b_lootCode1)
fin
while enemy
gold = gold + rollDiceWithLuck(enemy=>r_goldLoot, global=>p_players->b_luck)
xp = xp + rollDiceWithLuck(enemy=>r_enemyXP, global=>p_players->b_luck)
if !lootedItem and enemy=>s_lootCode and enemy->b_lootChance
if rollPercentileWithLuck(-(global=>p_players->b_luck)) < enemy->b_lootChance
mmgr(START_LOAD, 1) // code is in partition 1
pItemTblModule = mmgr(QUEUE_LOAD, MOD_GEN_ITEM_TABLES<<8 | RES_TYPE_MODULE)
mmgr(FINISH_LOAD, 0)
itemNum = randomFromArray(pItemTblModule()=>items_forLootCode(enemy=>s_lootCode), NULL)
pItem = createItem(itemNum)
if pItem->t_type == TYPE_FANCY_ITEM
pItem=>w_count = rollDiceWithLuck(pItem=>r_lootAmount, global=>p_players->b_luck)
fin
addItem(pItem)
mmgr(FREE_MEMORY, pItemTblModule)
lootedItem = TRUE
fin
fin
enemy = enemy=>p_nextObj
loop
group = group=>p_nextObj
loop
return gold, xp
if pItems
dispLoot(pItems)
// When taking out of one list and into another, we can't use forEach, because the p_nextObj
// link gets destroyed as we go along.
while pItems
pItem = pItems
pItems = pItems=>p_nextObj
fullAddItem(pItem, TRUE)
loop
fin
if gold > 0
displayf1("You collect %d gold!\n", addGold(gold))
fin
if xp > 0
addXP_all(xp)
displayf1("You earn %d experience!\n", xp); first = FALSE
fin
if pItems or gold or xp
promptAnyKey(FALSE)
fin
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -1262,8 +1346,6 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
def processWin()#0
word gold, xp, player, tmp
// Clear the window, then display the win message
clearWindow
callGlobalFunc(GS_COMBAT_WIN, 0, 0, 0)
@ -1276,20 +1358,8 @@ def processWin()#0
clearWindow
fin
// Make max space avail for item tables.
freePreloaded
// Grab the loot
gold, xp = collectLootAndXP()
if gold > 0
displayf1("You find %d gold! ", addGold(gold))
fin
if xp > 0
addXP_all(xp)
displayf1("You earn %d experience! ", xp)
fin
promptAnyKey(FALSE)
// Make max space avail for item tables, then grab the loot
collectLootAndXP()
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -1316,10 +1386,13 @@ def _combat_zoneEncounter(s_encZone)#1
// Tell what the party currently faces
clearWindow()
displayOpponents()
// Make sure portrait reflects a living enemy
setPortrait(first(global=>p_enemyGroups, @groupCanFight)=>p_enemies->b_image)
p = first(global=>p_enemyGroups, @groupCanFight)
if p
displayOpponents()
setPortrait(p=>p_enemies->b_image)
fin
// Get the choice of each player or NPC
isAdvancing = FALSE

View File

@ -593,7 +593,7 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Select an item and use it. Returns item if it needs to be processed by outer loop, else NULL
def doUseItem(player, item)#1
word pMod, oldVal, newVal
word pMod, oldVal, newVal, name
if item->t_type == TYPE_FANCY_ITEM and item=>p_modifiers
clearMenuRect()
clearMainRect()
@ -616,7 +616,11 @@ def doUseItem(player, item)#1
pause(800)
return NULL
fin
return item=>s_name // general 'use' handled by outer engine, because it might involve graphics
// General 'use' handled by outer engine, because it might involve graphics.
// Depluralize the name on the way out, because scripts will match on that.
isPlural = FALSE
return mmgr(HEAP_INTERN, sprintf1("%s", item=>s_name))
end
///////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -188,8 +188,10 @@ struc TEnemy
word r_enemyDmg // 3 hex digits: num dice, die size, add. E.g. $361 = 3d6+1
word r_enemyXP // 3 hex digits: num dice, die size, add. E.g. $361 = 3d6+1
word r_groupSize // number encountered, as 3 hex digits for dice
byte b_lootChance // % chance for loot item
word s_lootCode // code in loot table
byte b_lootChance0 // % chance for first loot item
byte b_lootChance1 // % chance for second loot item
byte b_lootCode0 // code in loot table for first loot item
byte b_lootCode1 // code in loot table for second loot item
word r_goldLoot // monetary loot when killed, as 3 hex digits for dice
end