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.

This commit is contained in:
Martin Haye 2015-12-09 07:35:02 -08:00
parent 90187c007c
commit be7ba4a3ed
5 changed files with 360 additions and 48 deletions

View File

@ -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)
}

View File

@ -32,6 +32,7 @@
<!-- Generate code from tables -->
<echo>Generating code from tables.</echo>
<java jar="${pack.dir}/PackPartitions.jar" fork="true" failonerror="true">
<arg value="data/world/world.xml"/>
<arg value="-dataGen"/>
</java>

View File

@ -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()

View File

@ -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

View File

@ -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