From be7ba4a3ed7a87b4a4c520386412bce8d9f03839 Mon Sep 17 00:00:00 2001 From: Martin Haye Date: Wed, 9 Dec 2015 07:35:02 -0800 Subject: [PATCH] Added partial support for NPC party members; packer now produces headers for the PLASMA code so we don't have to hard-code image numbers. --- .../src/org/demo/PackPartitions.groovy | 78 ++++++-- Platform/Apple/virtual/build.xml | 1 + .../Apple/virtual/src/plasma/gameloop.pla | 167 ++++++++++++++---- .../Apple/virtual/src/plasma/playtype.pla | 159 ++++++++++++++++- .../Apple/virtual/src/plasma/playtype.plh | 3 + 5 files changed, 360 insertions(+), 48 deletions(-) diff --git a/Platform/Apple/tools/PackPartitions/src/org/demo/PackPartitions.groovy b/Platform/Apple/tools/PackPartitions/src/org/demo/PackPartitions.groovy index 21450239..2a361c10 100644 --- a/Platform/Apple/tools/PackPartitions/src/org/demo/PackPartitions.groovy +++ b/Platform/Apple/tools/PackPartitions/src/org/demo/PackPartitions.groovy @@ -1356,16 +1356,52 @@ class PackPartitions println "Done." } - def humanNameToFuncName(str) + def isAlnum(ch) + { + if (Character.isAlphabetic(ch.charAt(0) as int)) + return true + if (Character.isDigit(ch.charAt(0) as int)) + return true + return false + } + + def humanNameToSymbol(str, allUpper) { def buf = new StringBuilder() def inParen = false def inSlash = false - str.each { ch -> - if (ch >= 'A' && ch <= 'Z') - buf.append(ch) - else if (ch >= 'a' && ch <= 'z') - buf.append(ch) + def needUnderscore = false + str.eachWithIndex { ch, idx -> + if (ch == '(') { + inParen = true + ch = 0 + } + else if (ch == ')') { + inParen = false + ch = 0 + } + else if (ch == '/') { + inSlash = true + ch = 0 + } + else if (!isAlnum(ch)) + inSlash = false + + if (ch && !inParen && !inSlash) { + if (ch >= 'A' && ch <= 'Z') + needUnderscore = true + if (ch == ' ' || ch == '_') + needUnderscore = true + if (isAlnum(ch)) { + if (needUnderscore && idx > 0) + buf.append('_') + needUnderscore = false + if (allUpper) + buf.append(ch.toUpperCase()) + else + buf.append(ch.toLowerCase()) + } + } } return buf.toString() } @@ -1375,7 +1411,7 @@ class PackPartitions assert columns[0] == "Name" def name = data[0] - out.print("def ${humanNameToFuncName(name)}\n") + out.print("def new_enemy_${humanNameToSymbol(name, false)}\n") assert columns[1] == "Image1" def image1 = data[1] @@ -1419,11 +1455,27 @@ class PackPartitions assert columns[14].toLowerCase() =~ /gold loot/ def goldLoot = data[14] - + out.println("end\n") } - void dataGen() + void dataGen(xmlPath) { + // Open the XML data file produced by Outlaw Editor + def dataIn = new XmlParser().parse(xmlPath) + + // Translate image names to constants + new File("src/plasma/gen_images.plh").withWriter { out -> + def portraitNum = 0 + dataIn.image.each { image -> + def category = image.@category?.toLowerCase() + def name = image.@name + if (category == "portrait") { + ++portraitNum + out.println "const PORTRAIT_${humanNameToSymbol(name, true)} = $portraitNum" + } + } + } + // Translate enemies to code new File("src/plasma/gen_enemies.pla").withWriter { out -> def columns @@ -1455,10 +1507,10 @@ class PackPartitions } // Check the arguments - if (!((args.size() == 1 && args[0] == "-dataGen") || args.size() == 2 || args.size() == 3)) { + if (!(args.size() == 2 || args.size() == 3)) { println "Usage: convert yourOutlawFile.xml game.part.0.bin [intcastMap.js]" println " (where intcastMap.js is to aid in debugging the Javascript raycaster)" - println " or: convert -dataGen" + println " or: convert yourOutlawFile.xml -dataGen" System.exit(1); } @@ -1470,8 +1522,8 @@ class PackPartitions // Go for it. def inst = new PackPartitions() try { - if (args[0] == "-dataGen") - inst.dataGen() + if (args[1] == "-dataGen") + inst.dataGen(args[0]) else inst.pack(args[0], args[1], args.size() > 2 ? args[2] : null) } diff --git a/Platform/Apple/virtual/build.xml b/Platform/Apple/virtual/build.xml index 11b492b4..f4096138 100644 --- a/Platform/Apple/virtual/build.xml +++ b/Platform/Apple/virtual/build.xml @@ -32,6 +32,7 @@ Generating code from tables. + diff --git a/Platform/Apple/virtual/src/plasma/gameloop.pla b/Platform/Apple/virtual/src/plasma/gameloop.pla index 22db5c8f..927612e4 100644 --- a/Platform/Apple/virtual/src/plasma/gameloop.pla +++ b/Platform/Apple/virtual/src/plasma/gameloop.pla @@ -76,6 +76,7 @@ const CHAR_WND_LIFE_X = 91 const CHAR_WND_GUN_X = 114 include "playtype.plh" +include "gen_images.plh" //include "heaptest.plh" word global // the global heap object, from which all live objects must be reachable @@ -647,6 +648,15 @@ asm brk end +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Reboot the machine +// Params: None +asm reboot + inc $3F4 ; invalidate reset vector + jmp $FA62 ; and reset +end + + /////////////////////////////////////////////////////////////////////////////////////////////////// // Set up the font engine // Params: pFont @@ -1420,11 +1430,26 @@ def kbdLoop() loop end +/////////////////////////////////////////////////////////////////////////////////////////////////// +def showMapName(mapName) + word newNameHash + + newNameHash = hashString(mapName) + if newNameHash <> mapNameHash + setWindow1() + clearWindow() + displayChar('Y'-$40) // center mode + displayStr(mapName) + displayChar('N'-$40) // normal mode + if mapIs3D; copyWindow(); fin + mapNameHash = newNameHash + fin +end + /////////////////////////////////////////////////////////////////////////////////////////////////// // Set initial info for the scripts on this map: the name of the map, its trigger table, and the // maximum extent (width, height). This is called by the init function for the scripts. def setScriptInfo(mapName, trigTbl, wdt, hgt) - word newNameHash // Grab the trigger table origins (used so the table can be more compact) triggerOriginX = trigTbl=>0 @@ -1438,16 +1463,7 @@ def setScriptInfo(mapName, trigTbl, wdt, hgt) totalMapHeight = hgt // Display map name - newNameHash = hashString(mapName) - if newNameHash <> mapNameHash - setWindow1() - clearWindow() - displayChar('Y'-$40) // center mode - displayStr(mapName) - displayChar('N'-$40) // normal mode - if mapIs3D; copyWindow(); fin - mapNameHash = newNameHash - fin + showMapName(mapName) // Back to the main text window. setWindow2() @@ -1576,6 +1592,10 @@ end /////////////////////////////////////////////////////////////////////////////////////////////////// // Call like this: addToList(player + items, itemToAdd) def addToList(addTo, p) + // Get to the end of the list + while *addTo + addTo = (*addTo) + p_nextObj + loop p=>p_nextObj = *addTo *addTo = p end @@ -1704,7 +1724,7 @@ end /////////////////////////////////////////////////////////////////////////////////////////////////// def playerDodge(pPlayer) - displayStr("\nTODO: player dodge.\n") + displayStr("\n%s dodges.\n", pPlayer=>s_name) end /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1760,10 +1780,6 @@ def playerCombatChoose(pl) byte nWeapons, key byte canShoot, canReload, canChange - // Tell what the player currently faces - displayOpponents() - combatPause() - // Count weapons, and take the first as the current one. nWeapons = 0 pWeapon = NULL @@ -1776,6 +1792,16 @@ def playerCombatChoose(pl) p = p=>p_nextObj loop + // NPCs always melee for now + if pl=>b_playerFlags & PLAYER_FLAG_NPC + pl->b_combatChoice = 'M' + return + fin + + // Tell what the player currently faces + displayOpponents() + combatPause() + // Let them know their options displayStr("\n") when rand16() % 5 @@ -1934,7 +1960,9 @@ def determineCombatOrder() if canFight(p) p->b_combatOrder = rand16() % (p->b_agility * 10) combatInsert(p) - nPlayersFighting = nPlayersFighting + 1 + if (!(p=>b_playerFlags & PLAYER_FLAG_NPC)) // only count real players + nPlayersFighting = nPlayersFighting + 1 + fin fin p = p=>p_nextObj loop @@ -1964,7 +1992,14 @@ def startCombat() // Create the enemy group(s). global=>p_enemyGroups = NULL - addToList(global + p_enemyGroups, new_EnemyGroup_Dirt_Bags()) + when rand16() % 2 + is 0 + addToList(global + p_enemyGroups, new_EnemyGroup_Dirt_Bags()) + break + otherwise + addToList(global + p_enemyGroups, new_EnemyGroup_Flesh_Feeders()) + break + wend // Display portrait of first group setPortrait(global=>p_enemyGroups=>p_enemies->ba_images[0]) @@ -2011,15 +2046,15 @@ def startCombat() loop rawDisplayStr("\nDo you:\n") - displayOption('F', "Fight") - displayOption('R', "Run") + displayOption('B', "Battle") + displayOption('F', "Flee") while TRUE n = getUpperKey() - if n == 'F' + if n == 'B' clearWindow() - displayStr("Fight!\n") + displayStr("Battle!\n") return TRUE - elsif n == 'R' + elsif n == 'F' clearWindow() displayStr("Coward.") return FALSE @@ -2051,18 +2086,29 @@ def doCombat() p = global=>p_combatFirst while p if !nPlayersFighting - displayStr("\nYou lost.") -//Lose: You bought the farm, with your life! Thank's for playing! Relead last save ? Y/N -//Lose: Didn't see that coming...to see a fine player like you slaughtered like a common rodent...Well, let's reload and try that again, ok? Y/N - return + setPortrait(PORTRAIT_DEATH) + when rand16() % 2 + is 0 + displayStr("\nYou bought the farm, with your life! Thank's for playing! Reload last save?\n"); break + otherwise + displayStr("\nDidn't see that coming... to see a fine player like you slaughtered like a common rodent... Well, let's reload and try that again, ok?\n") + wend + if (!getYN()) + displayStr("Ah, okay. Well... just hit a key when you're ready then.") + getUpperKey() + fin + reboot() elsif !nEnemiesFighting - displayStr("\nYou won!") -//WIN: You survive the ordeal and rifle through the carnage to find X gold. (if player gets random item) Upon further searching you find a -//Win: That was a close call! You see something shimmering on the ground and find X gold. -//Win: Looks like you live to fight another day anyway! You get X experience and discover X gold. -//I forgot to add experience to the above ones -// WIN: You survive the ordeal and rifle through the carnage to find X gold and receive x experience. (if player gets random item) Upon further searching you find a -//Win: That was a close call! You see something shimmering on the ground and find X gold and x expereince. + setPortrait(PORTRAIT_COMBATWIN) + when rand16() % 3 + is 0 + displayStr("\nYou survive the ordeal and rifle through the carnage.\n"); break + is 1 + displayStr("\nThat was a close call! You see search the ground.\n"); break + otherwise + displayStr("\nLooks like you live to fight another day anyway!\n"); break + wend + getUpperKey() return elsif isFleeing displayStr("\nYou have fled.") @@ -2085,6 +2131,55 @@ def doCombat() loop end +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Show player data +def showPlayerSheet(pl) + word x, y + byte dir + getPos(@x, @y) + dir = getDir() + + // First, display the player's name in the title bar + showMapName(pl=>s_name) + + // Next, show stats in the main map area + setMapWindow() + clearWindow() + displayStr("Stats") + + // Show inventory in the right hand area + setWindow2() + clearWindow() + displayStr("Inventory") + + // Wait, then back to normal + getUpperKey() + + setWindow2() + clearWindow() + initMap(x, y, dir) +end + +def showPlayer1() + showPlayerSheet(global=>p_players) +end + +def showPlayer2() + if global=>p_players=>p_nextObj + showPlayerSheet(global=>p_players=>p_nextObj) + else + beep() + fin +end + +def showPlayer3() + if global=>p_players=>p_nextObj and global=>p_players=>p_nextObj=>p_nextObj + showPlayerSheet(global=>p_players=>p_nextObj=>p_nextObj) + else + beep() + fin +end + /////////////////////////////////////////////////////////////////////////////////////////////////// // Set up the command table for 3D mode def initCmds() @@ -2100,6 +2195,9 @@ def initCmds() initCmd('P', @showPos) initCmd('/', @testPortrait) initCmd('!', @doCombat) + initCmd('1', @showPlayer1) + initCmd('2', @showPlayer2) + initCmd('3', @showPlayer3) // Commands handled differently in 3D vs 2D if mapIs3D @@ -2238,6 +2336,7 @@ end // initHeap() addToList(global + p_players, new_Player_Hue_Hauser()) +addToList(global + p_players, new_Player_Mokahnu()) loadTitle() setCallbacks() // Start map/loc per Seth. Need to have this in a script in the futurecheckScripts() diff --git a/Platform/Apple/virtual/src/plasma/playtype.pla b/Platform/Apple/virtual/src/plasma/playtype.pla index 260eaad9..29efe05d 100644 --- a/Platform/Apple/virtual/src/plasma/playtype.pla +++ b/Platform/Apple/virtual/src/plasma/playtype.pla @@ -35,6 +35,42 @@ def new_Armor_Chaps return p end +/////////////////////////////////////////////////////////////////////////////////////////////////// +def new_Armor_ShamanHeaddress() + word p + p = mmgr(HEAP_ALLOC, TYPE_ARMOR) + p=>s_name = mmgr(HEAP_INTERN, "Shaman Headdress(es)") + p->b_itemKind = KIND_HAT + p=>w_cost = -99 // for now + // no modifiers, max uses, etc. for now + p->b_armorValue = 2 + return p +end + +/////////////////////////////////////////////////////////////////////////////////////////////////// +def new_Armor_TahnkuPants() + word p + p = mmgr(HEAP_ALLOC, TYPE_ARMOR) + p=>s_name = mmgr(HEAP_INTERN, "Tahnku Pants") + p->b_itemKind = KIND_PANTS + p=>w_cost = -99 // for now + // no modifiers, max uses, etc. for now + p->b_armorValue = 2 + return p +end + +/////////////////////////////////////////////////////////////////////////////////////////////////// +def new_Armor_TahnkuVest() + word p + p = mmgr(HEAP_ALLOC, TYPE_ARMOR) + p=>s_name = mmgr(HEAP_INTERN, "Tahnku Vest(s)") + p->b_itemKind = KIND_SHIRT + p=>w_cost = -99 // for now + // no modifiers, max uses, etc. for now + p->b_armorValue = 2 + return p +end + /////////////////////////////////////////////////////////////////////////////////////////////////// def encodeDice(nDice, dieSize, add) // ndice=0..15, dieSize=0..15, add=0..255 return (nDice << 12) | (dieSize << 8) | add @@ -73,6 +109,45 @@ def new_Weapon_Handgun return p end +/////////////////////////////////////////////////////////////////////////////////////////////////// +def new_Weapon_SpiritBow + word p + p = mmgr(HEAP_ALLOC, TYPE_WEAPON) + p=>s_name = mmgr(HEAP_INTERN, "Spirit Bow") + p->b_itemKind = KIND_BOW + p=>w_cost = -99 // for now + // no modifiers, max uses, etc. for now + p->b_ammoKind = KIND_ARROW + p->b_clipSize = 12 + p->b_clipCurrent = p->b_clipSize + p=>r_meleeDmg = encodeDice(3, 6, 0) // 3d6 + p=>r_projectileDmg = encodeDice(2, 6, 5) // 2d6+5 + p->ba_attacks[0] = 1 // single attack + p->ba_attacks[1] = 3 // triple attack + p->b_weaponRange = 100 + p=>s_combatText = mmgr(HEAP_INTERN, "shoots") + return p +end + +/////////////////////////////////////////////////////////////////////////////////////////////////// +def new_Weapon_SpiritBlade + word p + p = mmgr(HEAP_ALLOC, TYPE_WEAPON) + p=>s_name = mmgr(HEAP_INTERN, "Spirit Blade") + p->b_itemKind = KIND_BOW + p=>w_cost = -99 // for now + // no modifiers, max uses, etc. for now + p->b_ammoKind = 0 + p->b_clipSize = 0 + p->b_clipCurrent = p->b_clipSize + p=>r_meleeDmg = encodeDice(7, 6, 0) // 7d6 + p=>r_projectileDmg = 0 + p->ba_attacks[0] = 1 // single attack + p->b_weaponRange = 10 + p=>s_combatText = mmgr(HEAP_INTERN, "slices") + return p +end + /////////////////////////////////////////////////////////////////////////////////////////////////// def calcPlayerArmor(player) word pItem @@ -126,13 +201,61 @@ def new_Player_Hue_Hauser return p end +/////////////////////////////////////////////////////////////////////////////////////////////////// +def new_Player_Mokahnu + word p, pItem + p = mmgr(HEAP_ALLOC, TYPE_PLAYER) + p=>s_name = mmgr(HEAP_INTERN, "Mokahnu") + p->b_intelligence = 7 + p->b_strength = 4 + p->b_agility = 6 + p->b_bravery = 7 + p->b_stamina = 6 + p->b_charisma = 7 + p->b_spirit = 10 + + p=>w_maxHealth = 40 + p=>w_health = 40 + + p->b_playerFlags = PLAYER_FLAG_NPC + + // Basic skills + p->b_aiming = 4 + p->b_dodging = 3 + p->b_wilderness = 5 + + // Skills + addToList(p + p_skills, new_Modifier(KIND_MINING, 0)) + addToList(p + p_skills, new_Modifier(KIND_NATIVE_BOND, 10)) + addToList(p + p_skills, new_Modifier(KIND_PYRE_WARE, 0)) + addToList(p + p_skills, new_Modifier(KIND_BLADE, 3)) + addToList(p + p_skills, new_Modifier(KIND_BOW, 3)) + addToList(p + p_skills, new_Modifier(KIND_RIFLE, 3)) + addToList(p + p_skills, new_Modifier(KIND_BLADE, 3)) + + // Items + addToList(p + p_items, new_Armor_ShamanHeaddress()) + addToList(p + p_items, new_Armor_TahnkuPants()) + addToList(p + p_items, new_Armor_TahnkuVest()) + addToList(p + p_items, new_Weapon_SpiritBow()) + addToList(p + p_items, new_Weapon_SpiritBlade()) + + // Calculated attributes + calcPlayerArmor(p) + + // (No buffs or debuffs to start with.) + + // All done with the player. + return p +end + /////////////////////////////////////////////////////////////////////////////////////////////////// def new_Enemy_Dirt_Bag word p p = mmgr(HEAP_ALLOC, TYPE_ENEMY) p=>s_name = mmgr(HEAP_INTERN, "Dirt-Bag(s)") p=>w_health = rollDice(encodeDice(1, 6, 0)) - p->ba_images[0] = 32 // Gunman5 + p->ba_images[0] = PORTRAIT_GUNMAN5 p->b_attackType = 1 // melee p=>s_attackText = mmgr(HEAP_INTERN, "swings at") p->b_enemyAttackRange = 5 @@ -160,3 +283,37 @@ def new_EnemyGroup_Dirt_Bags return p end +/////////////////////////////////////////////////////////////////////////////////////////////////// +def new_Enemy_Flesh_Feeder + word p + p = mmgr(HEAP_ALLOC, TYPE_ENEMY) + p=>s_name = mmgr(HEAP_INTERN, "Flesh Feeder(s)") + p=>w_health = rollDice(encodeDice(6, 6, 0)) + p->ba_images[0] = PORTRAIT_U_D_MAN1 + p->b_attackType = 1 // melee + p=>s_attackText = mmgr(HEAP_INTERN, "bites") + p->b_enemyAttackRange = 5 + p->b_chanceToHit = 40 + p=>r_enemyDmg = encodeDice(3, 6, 0) // 3d6 + p=>r_groupSize = encodeDice(1, 6, 0) // 1d6 + p=>r_initialRange = encodeDice(1, 5, 0) + return p +end + +/////////////////////////////////////////////////////////////////////////////////////////////////// +def new_EnemyGroup_Flesh_Feeders + word p, enem, groupSize + p = mmgr(HEAP_ALLOC, TYPE_ENEMY_GROUP) + enem = new_Enemy_Flesh_Feeder() + p->b_enemyGroupRange = rollDice(enem=>r_initialRange) + + groupSize = rollDice(enem=>r_groupSize) + addToList(p + p_enemies, enem) + while groupSize > 1 + addToList(p + p_enemies, new_Enemy_Flesh_Feeder()) + groupSize = groupSize - 1 + loop + + return p +end + diff --git a/Platform/Apple/virtual/src/plasma/playtype.plh b/Platform/Apple/virtual/src/plasma/playtype.plh index eae883fd..f5e97a97 100644 --- a/Platform/Apple/virtual/src/plasma/playtype.plh +++ b/Platform/Apple/virtual/src/plasma/playtype.plh @@ -10,6 +10,8 @@ struc Global end byte typeTbl_Global[] = Global, p_players, p_enemyGroups, p_combatFirst, 0 +const PLAYER_FLAG_NPC = $01 + const TYPE_PLAYER = $81 struc Player byte t_type @@ -39,6 +41,7 @@ struc Player // Status word w_maxHealth byte b_combatChoice + byte b_playerFlags // Lists word p_skills // list:Modifier