diff --git a/OutlawEditor/src/main/resources/mythos/mythos-editor/html/editor.html b/OutlawEditor/src/main/resources/mythos/mythos-editor/html/editor.html
index 7cae2d72..89889ef8 100644
--- a/OutlawEditor/src/main/resources/mythos/mythos-editor/html/editor.html
+++ b/OutlawEditor/src/main/resources/mythos/mythos-editor/html/editor.html
@@ -67,6 +67,7 @@
+
diff --git a/OutlawEditor/src/main/resources/mythos/mythos-editor/js/mythos_uncompressed.js b/OutlawEditor/src/main/resources/mythos/mythos-editor/js/mythos_uncompressed.js
index 11f91857..998a90d4 100644
--- a/OutlawEditor/src/main/resources/mythos/mythos-editor/js/mythos_uncompressed.js
+++ b/OutlawEditor/src/main/resources/mythos/mythos-editor/js/mythos_uncompressed.js
@@ -369,6 +369,28 @@ if (typeof Mythos === "undefined") {
this.setTooltip('Set color of the ground');
}
};
+ Blockly.Blocks['events_add_encounter_zone'] = {
+ init: function () {
+ this.setHelpUrl(Mythos.helpUrl);
+ this.setColour(54);
+ this.setPreviousStatement(true);
+ this.setNextStatement(true);
+ this.appendDummyInput()
+ .appendField("Add encounter zone for enemy code")
+ .appendField(new Blockly.FieldTextInput(""), "CODE")
+ .appendField('at X=')
+ .appendField(new Blockly.FieldTextInput("0"), "X")
+ .appendField('Y=')
+ .appendField(new Blockly.FieldTextInput("0"), "Y")
+ .appendField('with max dist')
+ .appendField(new Blockly.FieldTextInput("0"), "MAXDIST")
+ .appendField('(0=inf), and chance')
+ .appendField(new Blockly.FieldTextInput("0.0"), "CHANCE")
+ .appendField('%');
+ this.setOutput(false);
+ this.setTooltip('Add an encounter zone');
+ }
+ };
Blockly.Blocks['text_window'] = {
init: function () {
this.setHelpUrl(Mythos.helpUrl);
diff --git a/Platform/Apple/tools/PackPartitions/src/org/demo/PackPartitions.groovy b/Platform/Apple/tools/PackPartitions/src/org/demo/PackPartitions.groovy
index 729d281b..25011e31 100644
--- a/Platform/Apple/tools/PackPartitions/src/org/demo/PackPartitions.groovy
+++ b/Platform/Apple/tools/PackPartitions/src/org/demo/PackPartitions.groovy
@@ -883,8 +883,10 @@ class PackPartitions
def name = mapEl.@name ?: "map$num"
def num = mapNames[name][1]
//println "Packing 2D map #$num named '$name'."
- def rows = parseMap(mapEl, tileEls)
- write2DMap(name, mapEl, rows)
+ withContext("map '$name'") {
+ def rows = parseMap(mapEl, tileEls)
+ write2DMap(name, mapEl, rows)
+ }
}
def pack3DMap(mapEl, tileEls)
@@ -1769,7 +1771,8 @@ class PackPartitions
"${range.replace("'", "").toInteger()}, " +
"${chanceToHit.toInteger()}, " +
"${parseDice(damage)}, " +
- "${parseDice(groupSize)})")
+ "${parseDice(groupSize)}, " +
+ "\"$mapCode\")")
out.println("end")
}
@@ -2137,8 +2140,8 @@ class PackPartitions
(!yRange || trig.@y.toInteger() in yRange) })
{
scripts << script
- scriptNames[script] = (name == null) ? "trig_$idx" : "sc_${humanNameToSymbol(name, false)}"
}
+ scriptNames[script] = (name == null) ? "trig_$idx" : "sc_${humanNameToSymbol(name, false)}"
}
// Even if there were no scripts, we still need an init to display
@@ -2224,6 +2227,8 @@ class PackPartitions
packSetSky(blk); break
case 'events_set_ground':
packSetGround(blk); break
+ case 'events_add_encounter_zone':
+ packAddEncounterZone(blk); break
case 'events_teleport':
packTeleport(blk); break
case 'events_move_backward':
@@ -2426,6 +2431,25 @@ class PackPartitions
outIndented("setGround($color)\n")
}
+ def packAddEncounterZone(blk)
+ {
+ assert blk.field.size() == 5
+ assert blk.field[0].@name == 'CODE'
+ assert blk.field[1].@name == 'X'
+ assert blk.field[2].@name == 'Y'
+ assert blk.field[3].@name == 'MAXDIST'
+ assert blk.field[4].@name == 'CHANCE'
+ def code = blk.field[0].text()
+ def x = blk.field[1].text().toInteger()
+ def y = blk.field[2].text().toInteger()
+ def maxDist = blk.field[3].text().toInteger()
+ def chance = (int)(blk.field[4].text().toFloat() * 10.0)
+ assert chance > 0 && chance <= 1000
+ outIndented("addEncounterZone(")
+ emitString(code)
+ out << ", $x, $y, $maxDist, $chance)\n"
+ }
+
def packTeleport(blk)
{
assert blk.field.size() == 3
@@ -2495,13 +2519,11 @@ class PackPartitions
out << "byte = \$FF\n\n"
}
- def makeInit(mapName, scripts, maxX, maxY)
+ def makeInit(mapName, script, maxX, maxY)
{
- // Emit the code the user has stored for the init script.
- scripts.each { script ->
- if (script.block.size() == 1)
- packBlock(getSingle(getSingle(script.block, null, 'procedures_defreturn').statement, 'STACK'))
- }
+ // Emit the code the user has stored for the init script (if any)
+ if (script)
+ packScript(script)
// Set up the pointer to global vars
out << "global = getGlobals()\n"
@@ -2510,6 +2532,10 @@ class PackPartitions
def shortName = mapName.replaceAll(/[\s-]*[23][dD][-0-9]*$/, '').take(16)
out << "setScriptInfo(\"$shortName\", @triggerTbl, $maxX, $maxY)\n"
+ // Call init script if one was defined
+ if (script)
+ out << "sc_${humanNameToSymbol(getScriptName(script), false)}()\n"
+
// All done with the init function.
out << "done\n"
}
diff --git a/Platform/Apple/virtual/src/plasma/gamelib.plh b/Platform/Apple/virtual/src/plasma/gamelib.plh
index 5d0ed401..92742671 100644
--- a/Platform/Apple/virtual/src/plasma/gamelib.plh
+++ b/Platform/Apple/virtual/src/plasma/gamelib.plh
@@ -13,6 +13,8 @@
const FALSE = 0
const TRUE = 1
const NULL = 0
+const INT_MAX = 32767
+const INT_MIN = -32768
///////////////////////////////////////////////////////////////////////////////////////////////////
// Memory manager definitions
@@ -102,7 +104,7 @@ const setPlural = gameLibVecs + 3*43
const makeEnemy = gameLibVecs + 3*44
const getStringResponse = gameLibVecs + 3*45
const strcmpi = gameLibVecs + 3*46
-const FUNCN47 = gameLibVecs + 3*47
+const addEncounterZone = gameLibVecs + 3*47
const FUNCN48 = gameLibVecs + 3*48
const FUNCN49 = gameLibVecs + 3*49
const FUNCN50 = gameLibVecs + 3*50
diff --git a/Platform/Apple/virtual/src/plasma/gameloop.pla b/Platform/Apple/virtual/src/plasma/gameloop.pla
index cf736f0a..27111bdc 100644
--- a/Platform/Apple/virtual/src/plasma/gameloop.pla
+++ b/Platform/Apple/virtual/src/plasma/gameloop.pla
@@ -106,7 +106,7 @@ predef _puts, _min, _max
predef _countList, _countListFiltered, _randomFromListFiltered, _addToList, _beep
predef _showParty, _mmgr, _setWindow1, _setWindow2, _setWindow3
predef _reboot, _brk, _encodeDice, _rollDice
-predef _setPlural, _makeEnemy, _getStringResponse, _strcmpi
+predef _setPlural, _makeEnemy, _getStringResponse, _strcmpi, _addEncounterZone
word gameLib_addrs = @_setScriptInfo, @_scriptDisplayStr, @_scriptDisplayStrNL, @_getYN
word = @_queue_setMap, @_setSky, @_setGround, @_queue_teleport, @_setPortrait, @_clearPortrait
@@ -119,7 +119,7 @@ word = @_puts, @_min, @_max
word = @_countList, @_countListFiltered, @_randomFromListFiltered, @_addToList, @_beep
word = @_showParty, @_mmgr, @_setWindow1, @_setWindow2, @_setWindow3
word = @_reboot, @_brk, @_encodeDice, @_rollDice
-word = @_setPlural, @_makeEnemy, @_getStringResponse, @_strcmpi
+word = @_setPlural, @_makeEnemy, @_getStringResponse, @_strcmpi, @_addEncounterZone
word = 0 // end of library functions
@@ -818,6 +818,16 @@ def _min(a, b)
fin
end
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Return the absolute value of a number
+def abs(n)
+ if n < 0
+ return -n
+ else
+ return n
+ fin
+end
+
///////////////////////////////////////////////////////////////////////////////////////////////////
// Read a string from the keyboard using the font manager, and intern it to the heap.
def _getStringResponse()
@@ -1161,6 +1171,10 @@ def initMap(x, y, dir)
loadFrameImg()
mmgr(FINISH_LOAD, 1) // 1 = keep open
+ // Clear the list of encounter zones
+ global=>p_encounterZones = NULL
+ printf1("Cleared zones: $%x\n", global=>p_encounterZones)
+
// Start up the display engine with map data and starting position. This will also load and
// init the script module, if any, which will end up calling us back at the setScriptInfo
triggerTbl = NULL
@@ -1173,23 +1187,53 @@ def initMap(x, y, dir)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
-// Check for script(s) attached to the current location, and call them if there are any.
-def checkScripts()
- word x
- word y
+// Check for a random encounter at this position
+def checkEncounter(x, y)
+ word p
+ word p_bestZone, bestDist
+ word d
+
+ // Find the zone that's closest, but not too far.
+ bestDist = INT_MAX
+ p_bestZone = NULL
+ p = global=>p_encounterZones
+ while p
+ d = min(abs(x - p=>w_encX), abs(y - p=>w_encY))
+ if d < bestDist and (p=>w_encMaxDist == 0 or d < p=>w_encMaxDist))
+ p_bestZone = p
+ bestDist = d
+ fin
+ p = p=>p_nextObj
+ loop
+
+ // Roll for an encounter in the zone. If none, get out early.
+ d = rand16() % 1000
+ if p_bestZone and d < p_bestZone=>w_encChance
+ // Encounter!
+ setWindow2()
+ clearWindow()
+ displayf1("Encounter! %s\n", p_bestZone=>s_name)
+ getYN()
+ clearWindow()
+ fin
+end
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Check for script(s) attached to the given location, and call them if there are any.
+// Returns TRUE if any were triggered.
+def checkScripts(x, y)
word p
word pNext
word script
+ byte anyTriggered
- if !triggerTbl; return; fin
- setWindow2()
- getPos(@x, @y)
+ anyTriggered = FALSE
x = x - triggerOriginX
y = y - triggerOriginY
p = triggerTbl
- while TRUE
+ while p
if ^p == $FF
- return
+ break
fin
pNext = p + p->1
if ^p == y
@@ -1197,13 +1241,17 @@ def checkScripts()
while p < pNext
if x == ^p
script = p=>1
+ setWindow2()
script()
+ anyTriggered = TRUE
fin
p = p + 3
loop
fin
p = pNext
loop
+ printf1("anyTriggered=%d\n", anyTriggered)
+ return anyTriggered
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1232,6 +1280,7 @@ end
// Advance one step forward (3D maps only)
def moveForward()
byte val
+ word x, y
val = advance()
// If not blocked, render at the new position.
@@ -1251,8 +1300,13 @@ def moveForward()
fin
// If there are script(s) on the new tile, run them.
+ getPos(@x, @y)
if val == 3
- checkScripts()
+ if (!checkScripts(x, y))
+ checkEncounter(x, y)
+ fin
+ else
+ checkEncounter(x, y)
fin
end
@@ -1792,7 +1846,7 @@ def restoreMapPos()
end
///////////////////////////////////////////////////////////////////////////////////////////////////
-def _makeEnemy(name, healthDice, image0, image1, attackType, attackText, attackRange, chanceToHit, dmg, groupSize)
+def _makeEnemy(name, healthDice, image0, image1, attackType, attackText, attackRange, chanceToHit, dmg, groupSize, code)
word p; p = mmgr(HEAP_ALLOC, TYPE_ENEMY)
p=>s_name = mmgr(HEAP_INTERN, name)
p=>w_health = rollDice(healthDice) // 4d6
@@ -1807,9 +1861,22 @@ def _makeEnemy(name, healthDice, image0, image1, attackType, attackText, attackR
p->b_chanceToHit = chanceToHit
p=>r_enemyDmg = dmg
p=>r_groupSize = groupSize
+ p=>s_enemyCode = mmgr(HEAP_INTERN, code)
return p
end
+///////////////////////////////////////////////////////////////////////////////////////////////////
+def _addEncounterZone(code, x, y, dist, chance)
+ word p; p = mmgr(HEAP_ALLOC, TYPE_ENCOUNTER_ZONE)
+ p=>s_name = mmgr(HEAP_INTERN, code)
+ p=>w_encX = x
+ p=>w_encY = y
+ p=>w_encMaxDist = dist
+ p=>w_encChance = chance
+ addToList(@global=>p_encounterZones, p)
+ printf1("Added zone: $%x\n", global=>p_encounterZones)
+end
+
///////////////////////////////////////////////////////////////////////////////////////////////////
def _strcmpi(a, b)
word limit, lenDiff, diff
diff --git a/Platform/Apple/virtual/src/plasma/playtype.pla b/Platform/Apple/virtual/src/plasma/playtype.pla
index 424e0d47..688fd471 100644
--- a/Platform/Apple/virtual/src/plasma/playtype.pla
+++ b/Platform/Apple/virtual/src/plasma/playtype.pla
@@ -8,7 +8,7 @@
// governing permissions and limitations under the License.
///////////////////////////////////////////////////////////////////////////////////////////////////
-byte typeTbl_Global[] = Global, p_players, p_enemyGroups, p_combatFirst, 0
+byte typeTbl_Global[] = Global, p_players, p_enemyGroups, p_combatFirst, p_encounterZones, 0
byte typeTbl_Player[] = Player, p_nextObj, s_name, p_combatNext, p_skills, p_items, p_buffs, p_debuffs, 0
byte typeTbl_Modifier[] = Modifier, p_nextObj, 0
byte typeTbl_Effect[] = Effect, p_nextObj, s_effectDescrip, 0
@@ -18,9 +18,11 @@ byte typeTbl_Armor[] = Armor, p_nextObj, s_name, p_modifiers, 0
byte typeTbl_Stuff[] = Stuff, p_nextObj, s_name, 0
byte typeTbl_Enemy[] = Enemy, p_nextObj, s_name, p_combatNext, s_attackText, 0
byte typeTbl_EnemyGroup[] = EnemyGroup, p_nextObj, p_enemies, 0
+byte typeTbl_EncounterZone[] = EncounterZone, p_nextObj, s_name, 0
word typeTbls = @typeTbl_Global, @typeTbl_Player, @typeTbl_Modifier, @typeTbl_Effect, @typeTbl_Item
word = @typeTbl_Weapon, @typeTbl_Armor, @typeTbl_Stuff, @typeTbl_Enemy, @typeTbl_EnemyGroup
+word = @typeTbl_EncounterZone
word = 0
byte[] kind_bow_str = "bow(s)"
diff --git a/Platform/Apple/virtual/src/plasma/playtype.plh b/Platform/Apple/virtual/src/plasma/playtype.plh
index 0e2aa303..86f09f54 100644
--- a/Platform/Apple/virtual/src/plasma/playtype.plh
+++ b/Platform/Apple/virtual/src/plasma/playtype.plh
@@ -17,6 +17,7 @@ struc Global
word p_players
word p_enemyGroups
word p_combatFirst
+ word p_encounterZones
// Map position
byte b_mapIs3D
@@ -170,6 +171,7 @@ struc Enemy
byte b_chanceToHit
word r_enemyDmg // 3 hex digits: num dice, die size, add. E.g. $361 = 3d6+1
word r_groupSize // number encountered, as 3 hex digits for dice
+ word s_enemyCode
end
const TYPE_ENEMY_GROUP = $89
@@ -180,6 +182,17 @@ struc EnemyGroup
byte b_enemyGroupRange
end
+const TYPE_ENCOUNTER_ZONE = $8A
+struc EncounterZone
+ byte t_type
+ word p_nextObj
+ word s_name // enemy code
+ word w_encX
+ word w_encY
+ word w_encMaxDist
+ word w_encChance
+end
+
// Weapon kinds
const KIND_BOW = 1;
const KIND_BLADE = 2;