diff --git a/Platform/Apple/tools/PackPartitions/src/org/badvision/A2PackPartitions.groovy b/Platform/Apple/tools/PackPartitions/src/org/badvision/A2PackPartitions.groovy index 2617ce98..71c82897 100644 --- a/Platform/Apple/tools/PackPartitions/src/org/badvision/A2PackPartitions.groovy +++ b/Platform/Apple/tools/PackPartitions/src/org/badvision/A2PackPartitions.groovy @@ -3120,12 +3120,14 @@ class A2PackPartitions def parseDice(str) { + str = str.trim() + // Handle single value if (str =~ /^\d+$/) { def n = str.toInteger() if (n < 0 || n > 255) { printWarning("Number $n forced to valid range 0..255") - return "0" + return n < 0 ? "0" : "255" } return str } @@ -3182,11 +3184,15 @@ class A2PackPartitions def name = row.@name withContext(name) { - def image1 = row.@image1 + def images = row.@images.split(", *") + if (images.length < 1 || images.length > 2) + throw new Exception("Required: 1 or 2 images") + + def image1 = images[0] if (!portraits.containsKey(image1)) throw new Exception("Image '$image1' not found") - def image2 = row.@image2 + def image2 = images.length > 1 ? images[1] : "" if (image2.size() > 0 && !portraits.containsKey(image2)) throw new Exception("Image '$image2' not found") @@ -3208,6 +3214,7 @@ class A2PackPartitions def lootChance = row.@"loot-chance"; // optional, defaults to 10% def lootCode = row.@"loot-code" // optional def goldLoot = row.@"gold-loot"; assert goldLoot + def gangChance = row.@"gang-chance"; // optional out.println("word = " + "@${strings[name]}, ${parseDice(hitPoints)} // name, hit dice") @@ -3215,14 +3222,15 @@ class A2PackPartitions (image2.size() > 0 ? "PO${humanNameToSymbol(image2, false)}, " : "0, ") + "$attackTypeCode // img0, img1, attack type") out.println("word = @${strings[attackText]} // attack text") - out.println("byte = ${range.replace("'", "").toInteger()}, " + - "${chanceToHit.toInteger()} // attack range, chance to hit") + out.println("word = ${parseDice(range.replace("'", ""))} // attack range dice") + out.println("byte = ${chanceToHit.toInteger()} // chance to hit") 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 = ${gangChance ? parseDice(gangChance) : 0} // gang chance") out.println("") // Add portrait dependencies based on encounter zone(s) diff --git a/Platform/Apple/virtual/src/plasma/combat.pla b/Platform/Apple/virtual/src/plasma/combat.pla index 4b8586a7..338ea62e 100644 --- a/Platform/Apple/virtual/src/plasma/combat.pla +++ b/Platform/Apple/virtual/src/plasma/combat.pla @@ -25,7 +25,7 @@ struc EnemyData byte b_en_img1 byte b_en_attType word s_en_attText - byte b_en_attRange + byte r_en_attRange byte b_en_chanceToHit word r_en_dmgDice word r_en_expDice @@ -33,6 +33,7 @@ struc EnemyData byte b_en_lootChance word s_en_lootCode word r_en_goldLoot + byte b_en_gangChance end predef _combat_zoneEncounter(s_encZone)#1 @@ -55,6 +56,10 @@ byte[] toPreload = MOD_ITEMUTIL, GS_COMBAT_PROMPT, GS_ENEMY_INTRO, GS_COMBAT_WIN word[4] preloads word pItemUtil +const MAX_GROUPS = 3 +byte[MAX_GROUPS] groupEnemyNums +byte nEnemyGroups + /////////////////////////////////////////////////////////////////////////////////////////////////// def preload()#0 byte i @@ -836,7 +841,7 @@ def makeEnemy(pData) fin p->b_attackType = pData->b_en_attType p=>s_attackText = mmgr(HEAP_INTERN, pData=>s_en_attText) - p->b_enemyAttackRange = pData->b_en_attRange + p->b_enemyAttackRange = rollDice(pData=>r_en_attRange) // possibly random range p->b_chanceToHit = pData->b_en_chanceToHit p=>r_enemyDmg = pData=>r_en_dmgDice p=>r_enemyXP = pData=>r_en_expDice @@ -869,31 +874,66 @@ end /////////////////////////////////////////////////////////////////////////////////////////////////// // Return a random entry from an array which is terminated by a zero entry -def randomFromArray(arr)#1 - byte siz +def randomFromArray(arr, filter)#1 + byte siz, start, cur + word p + + // Determine the array size by looking for the terminating zero siz = 0 while *((siz << 1) + arr) siz++ loop - return *(((rand16() % siz) << 1) + arr) + + // Pick a random starting point in the array + start = rand16() % siz + + // Advance 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 + cur++ + if cur >= siz; cur = 0; fin + if cur == start + p = NULL + break + fin + loop + return p end /////////////////////////////////////////////////////////////////////////////////////////////////// -def makeRandomGroup(mapCode)#0 +def nonDupeEnemy(enemyNum)#1 + byte i + for i = 0 to nEnemyGroups + if enemyNum == groupEnemyNums[i]; return FALSE; fin + next + return TRUE +end + +/////////////////////////////////////////////////////////////////////////////////////////////////// +def makeRandomGroup(mapCode)#1 word enemiesModule byte enemyNum - word pEnemyData + word pEnemyData, pGroup enemiesModule = mmgr(QUEUE_LOAD, MOD_GEN_ENEMIES<<8 | RES_TYPE_MODULE) mmgr(FINISH_LOAD, 0) - enemyNum = randomFromArray(enemiesModule()=>enemies_forZone(mapCode)) + 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++ 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) - addToList(@global=>p_enemyGroups, makeEnemyGroup(pEnemyData)) + pGroup = makeEnemyGroup(pEnemyData) + addToList(@global=>p_enemyGroups, pGroup) mmgr(FREE_MEMORY, enemiesModule) + + return pGroup end /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -975,7 +1015,7 @@ def collectLootAndXP()#2 mmgr(START_LOAD, 1) // code is in partition 1 pItemsModule = mmgr(QUEUE_LOAD, MOD_GEN_ITEMS<<8 | RES_TYPE_MODULE) mmgr(FINISH_LOAD, 0) - itemFunc = randomFromArray(pItemsModule()=>items_forLootCode(enemy=>s_lootCode)) + itemFunc = randomFromArray(pItemsModule()=>items_forLootCode(enemy=>s_lootCode), @trueFunc) pItem = itemFunc() if pItem->t_type == TYPE_FANCY_ITEM pItem=>w_count = rollDiceWithLuck(pItem=>r_lootAmount, global=>p_players->b_luck) @@ -1003,7 +1043,18 @@ def startCombat(mapCode)#1 isFleeing = FALSE combatDebug = FALSE global=>p_enemyGroups = NULL - makeRandomGroup(mapCode) + 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 + + // 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 + loop // Display portrait of first group setPortrait(global=>p_enemyGroups=>p_enemies->b_image) diff --git a/Platform/Apple/virtual/src/plasma/gameloop.pla b/Platform/Apple/virtual/src/plasma/gameloop.pla index e375e18a..60facbdf 100644 --- a/Platform/Apple/virtual/src/plasma/gameloop.pla +++ b/Platform/Apple/virtual/src/plasma/gameloop.pla @@ -1606,7 +1606,7 @@ export def rollDice(encoded)#1 word result nDice = (encoded >> 12) & $F // must mask off replicated hi-bits dieSize = (encoded >> 8) & $F - result = encoded & $F // initial add + result = encoded & $FF // initial add while nDice result = result + (rand16() % dieSize) + 1 nDice--