move standalone projects into own directory
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
package data.blades;
|
||||
|
||||
using Type;
|
||||
using Reflect;
|
||||
import kiss.Prelude;
|
||||
import kiss.Kiss;
|
||||
|
||||
enum MeleeAbility {
|
||||
Poison;
|
||||
Slow;
|
||||
Disease;
|
||||
Sleep;
|
||||
Paralyze;
|
||||
Acid;
|
||||
Fire;
|
||||
Cold;
|
||||
DrainXP;
|
||||
ColdDrainXP;
|
||||
Web;
|
||||
}
|
||||
|
||||
enum RayAbility {
|
||||
CurseAndWeaken;
|
||||
TurnToStone;
|
||||
Charm;
|
||||
Sleep;
|
||||
Paralyze;
|
||||
DrainSP;
|
||||
Confuse;
|
||||
Terrify;
|
||||
Fire;
|
||||
MagicDamage;
|
||||
}
|
||||
|
||||
enum ThrowAbility {
|
||||
Web4; // range 4
|
||||
Rocks;
|
||||
Spines; // damage and paralysis
|
||||
}
|
||||
|
||||
enum FieldAbility {
|
||||
Fire;
|
||||
Cold;
|
||||
AntiMagic;
|
||||
Sleep;
|
||||
Stink;
|
||||
Blade;
|
||||
}
|
||||
|
||||
enum BreathAbility {
|
||||
Fire;
|
||||
Cold;
|
||||
Acid;
|
||||
Darkness;
|
||||
}
|
||||
|
||||
enum SpecialAbility {
|
||||
None;
|
||||
Melee(a: MeleeAbility);
|
||||
Ray(a: RayAbility);
|
||||
Throw(a: ThrowAbility);
|
||||
Radiate(a: FieldAbility);
|
||||
Breath(a: BreathAbility);
|
||||
Invisible;
|
||||
SplitsWhenHit;
|
||||
ForceCage;
|
||||
}
|
||||
|
||||
enum Attitude {
|
||||
Friendly;
|
||||
Neutral;
|
||||
HostileA;
|
||||
HostileB;
|
||||
}
|
||||
|
||||
enum Species {
|
||||
Human;
|
||||
Humanoid;
|
||||
Nephil; // bonus missle weapons and dexterity
|
||||
Slith; // bonus pole weapons and fire resistance
|
||||
Giant;
|
||||
Reptile; // won't pick up or use items
|
||||
Beast; // same
|
||||
Demon; // same + vulnerable to Repel Spirit at high levels + immune to mental spells
|
||||
Undead; // same + vulnerable to Repel Spirit + same
|
||||
Insect; // same + immune to mental spells + immune to webs
|
||||
SlimeOrPlant; // same + same + immune to assassination
|
||||
StoneOrGolem; // same + same + immune to beams
|
||||
Special; // immune to assassination and lethal blow. Immune to simulacrum. Immune to webs.
|
||||
Vahnatai;
|
||||
Other;
|
||||
}
|
||||
|
||||
enum AttackType {
|
||||
Strike;
|
||||
Claw;
|
||||
Bite;
|
||||
Slimes;
|
||||
Punches;
|
||||
Stings;
|
||||
Clubs;
|
||||
Burns;
|
||||
Harms;
|
||||
Stabs;
|
||||
Kicks;
|
||||
}
|
||||
|
||||
enum StrategyType {
|
||||
Default;
|
||||
ArcherOrCaster;
|
||||
}
|
||||
|
||||
typedef Strategy = {
|
||||
type: StrategyType,
|
||||
persistentTarget: Bool
|
||||
};
|
||||
|
||||
class CreatureData {
|
||||
public function new() {}
|
||||
|
||||
public function clone():CreatureData {
|
||||
var cd = new CreatureData();
|
||||
for (field in CreatureData.getInstanceFields()) {
|
||||
try {
|
||||
cd.setField(field, this.field(field));
|
||||
} catch (e) {
|
||||
// can't set functions in c++
|
||||
}
|
||||
}
|
||||
return cd;
|
||||
}
|
||||
|
||||
public var name:String = "";
|
||||
public var default_script:String = "basicnpc";
|
||||
public var level = 2;
|
||||
public var hp_bonus = 0;
|
||||
public var sp_bonus = 0;
|
||||
private var special_abil = 0;
|
||||
// specialAbility() defined in kiss file
|
||||
public function specialAbility() {
|
||||
return Kiss.exp('(case special_abil
|
||||
(0 None)
|
||||
(1 (Melee Poison))
|
||||
(2 (Ray CurseAndWeaken))
|
||||
(3 (Ray TurnToStone))
|
||||
(4 (Melee Slow))
|
||||
(5 (Throw Web4))
|
||||
(6 (Melee Disease))
|
||||
(7 (Ray Charm))
|
||||
(8 (Melee Sleep))
|
||||
(9 (Ray Sleep))
|
||||
(10 (Melee Paralyze))
|
||||
(11 (Ray Paralyze))
|
||||
(12 (Melee Acid))
|
||||
(13 (Ray DrainSP))
|
||||
(14 (Ray Confuse))
|
||||
(15 (Ray Terrify))
|
||||
(16 (Throw Rocks))
|
||||
(17 (Breath Fire))
|
||||
(18 (Breath Cold))
|
||||
(19 (Breath Acid))
|
||||
(20 (Melee Fire))
|
||||
(21 (Melee Cold))
|
||||
(22 (Melee DrainXP))
|
||||
(23 (Melee ColdDrainXP))
|
||||
(24 Invisible)
|
||||
(26 (Radiate Fire))
|
||||
(27 (Radiate Cold))
|
||||
(28 (Radiate AntiMagic))
|
||||
(29 SplitsWhenHit)
|
||||
(30 (Ray Fire))
|
||||
(32 (Ray MagicDamage))
|
||||
(33 (Breath Darkness))
|
||||
(34 (Throw Spines))
|
||||
(35 ForceCage)
|
||||
(36 (Melee Web))
|
||||
(37 (Radiate Sleep))
|
||||
(38 (Radiate Stink))
|
||||
(39 (Radiate Blade))
|
||||
(otherwise (throw "undefined special_abil")))');
|
||||
}
|
||||
|
||||
private var default_attitude = 2;
|
||||
public function defaultAttitude() {
|
||||
return Attitude.createEnumIndex(default_attitude - 2);
|
||||
}
|
||||
|
||||
private var species = 0;
|
||||
public function getSpecies() {
|
||||
return Species.createEnumIndex(species);
|
||||
}
|
||||
public var natural_armor = 0;
|
||||
public var attack_1 = 0;
|
||||
public var attack_2 = 0;
|
||||
public var attack_3 = 0;
|
||||
private var attack_1_type = 0;
|
||||
public function attack1Type() {
|
||||
return AttackType.createEnumIndex(attack_1_type);
|
||||
}
|
||||
private var attack_23_type = 0;
|
||||
public function attack23Type() {
|
||||
return AttackType.createEnumIndex(attack_23_type);
|
||||
}
|
||||
public var ap_bonus = 0;
|
||||
private var default_strategy = 0;
|
||||
public function defaultStrategy():Strategy {
|
||||
return {
|
||||
type: StrategyType.createEnumIndex(default_strategy % 10),
|
||||
persistentTarget: default_strategy >= 10
|
||||
};
|
||||
}
|
||||
|
||||
private var default_aggression = 100;
|
||||
public function defaultAggression() {
|
||||
return default_aggression / 100.0;
|
||||
}
|
||||
private var default_courage = 100;
|
||||
public function defaultCourage() {
|
||||
return default_courage / 100.0;
|
||||
}
|
||||
public var which_sheet = 0;
|
||||
// TODO implement icon adjustments
|
||||
public var icon_adjust = 0;
|
||||
private var small_or_large_template = 0;
|
||||
public var which_sheet_upper = -1;
|
||||
public var summon_class = -1;
|
||||
private var what_stat_adjust:Array<Int> = [];
|
||||
private var amount_stat_adjust:Array<Int> = [];
|
||||
private var start_item:Array<Int> = [];
|
||||
private var start_item_chance:Array<Int> = [];
|
||||
private var immunities:Array<Int> = [];
|
||||
|
||||
// TODO public functions that return and contextualize the array variables
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package data.blades;
|
||||
|
||||
using Type;
|
||||
using Reflect;
|
||||
|
||||
import flixel.input.keyboard.FlxKey;
|
||||
|
||||
enum FloorShimmerType {
|
||||
None;
|
||||
LightToDark;
|
||||
Water;
|
||||
}
|
||||
|
||||
enum FloorSpecialProperty {
|
||||
Nothing;
|
||||
|
||||
// For dice-based properties, special_strength is the size of the dice.
|
||||
FireDamage2Dice;
|
||||
ColdDamage2Dice;
|
||||
MagicDamage2Dice;
|
||||
PoisonLevels1Die50Percent;
|
||||
DiseaseLevels1Die50Percent;
|
||||
|
||||
BlockedToNPCs;
|
||||
NoRest;
|
||||
|
||||
// Call the scenario script with the number special_strength
|
||||
CallScenarioScript;
|
||||
}
|
||||
|
||||
class FloorData {
|
||||
public function new() {}
|
||||
|
||||
public function clone():FloorData {
|
||||
var fd = new FloorData();
|
||||
for (field in FloorData.getInstanceFields()) {
|
||||
try {
|
||||
fd.setField(field, this.field(field));
|
||||
} catch (e) {
|
||||
// can't set functions in c++
|
||||
}
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
public var name:String = "";
|
||||
public var which_sheet:Int = 0;
|
||||
public var which_icon:Int = 0;
|
||||
// TODO implement icon adjustments
|
||||
public var icon_adjust:Int = 0;
|
||||
public var ed_which_sheet:Int = 0;
|
||||
public var ed_which_icon:Int = 0;
|
||||
private var blocked:Int = 0;
|
||||
public function isBlocked() {
|
||||
return blocked == 1;
|
||||
}
|
||||
public var step_sound:Int = -1;
|
||||
public var light_radius:Int = 0;
|
||||
public var floor_height_pixels:Int = 0;
|
||||
private var special_property:Int = 0;
|
||||
public function specialProperty() {
|
||||
return FloorSpecialProperty.createEnumIndex(special_property);
|
||||
}
|
||||
public var special_strength:Int = 0;
|
||||
public var is_water:Bool = false;
|
||||
public var is_floor:Bool = false;
|
||||
public var is_ground:Bool = false;
|
||||
public var is_rough:Bool = false;
|
||||
public var fly_over:Bool = false;
|
||||
private var shortcut_key:Int = -1;
|
||||
public function shortcutKey() {
|
||||
return if (shortcut_key == -1) {
|
||||
NONE;
|
||||
} else {
|
||||
FlxKey.A + shortcut_key;
|
||||
};
|
||||
}
|
||||
public var anim_steps:Int = 0;
|
||||
private var shimmers:Int;
|
||||
public function shimmerType() {
|
||||
return FloorShimmerType.createEnumIndex(shimmers);
|
||||
}
|
||||
public var out_fight_town_used:Int = 1000;
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package data.blades;
|
||||
|
||||
using Type;
|
||||
using Reflect;
|
||||
|
||||
enum ItemVariety {
|
||||
Null;
|
||||
OneHand;
|
||||
TwoHand;
|
||||
Gold;
|
||||
Food;
|
||||
ThrownMissile;
|
||||
Bow;
|
||||
Potion;
|
||||
Scroll;
|
||||
WandOrRod;
|
||||
Tool;
|
||||
Pants;
|
||||
Shield;
|
||||
Armor;
|
||||
Helm;
|
||||
Gloves;
|
||||
Boots;
|
||||
Cloak;
|
||||
Ring;
|
||||
Necklace;
|
||||
Bracelet;
|
||||
Object;
|
||||
Crossbow;
|
||||
Arrows;
|
||||
Bolts;
|
||||
}
|
||||
|
||||
class ItemData {
|
||||
public function new() {}
|
||||
|
||||
public function clone():ItemData {
|
||||
var id = new ItemData();
|
||||
for (field in ItemData.getInstanceFields()) {
|
||||
try {
|
||||
id.setField(field, this.field(field));
|
||||
} catch (e) {
|
||||
// can't set functions in c++
|
||||
}
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public var name = "";
|
||||
public var full_name = "";
|
||||
private var variety = 0;
|
||||
public function getVariety() {
|
||||
return ItemVariety.createEnumIndex(variety);
|
||||
}
|
||||
public var damage_per_level = 0;
|
||||
public var bonus = 0;
|
||||
private var weapon_skill_used = 4;
|
||||
// TODO public getter to convert weapon skill to a skill enum using the skill numbers in the appendix
|
||||
public var protection = 0;
|
||||
public var charges = 0;
|
||||
public var encumbrance = 0;
|
||||
public var floor_which_sheet = 0;
|
||||
public var floor_which_icon = 0;
|
||||
// TODO implement icon adjustments
|
||||
public var icon_adjust = 0;
|
||||
public var inventory_icon = 0;
|
||||
private var ability_1 = -1;
|
||||
private var ability_str_1 = 0;
|
||||
private var ability_2 = -1;
|
||||
private var ability_str_2 = 0;
|
||||
private var ability_3 = -1;
|
||||
private var ability_str_3 = 0;
|
||||
private var ability_4 = -1;
|
||||
private var ability_str_4 = 0;
|
||||
// TODO public getters for ability types as an enum
|
||||
public var special_class = 0;
|
||||
public var value = 0;
|
||||
private var weight = 0;
|
||||
public function getWeight():Float {
|
||||
return weight / 10.0;
|
||||
}
|
||||
private var identified = 0;
|
||||
public function isIdentified() {
|
||||
return identified == 1;
|
||||
}
|
||||
private var magic = 0;
|
||||
public function isMagic() {
|
||||
return magic == 1;
|
||||
}
|
||||
private var cursed = 0;
|
||||
public function isCursed() {
|
||||
return cursed == 1;
|
||||
}
|
||||
private var once_per_day = 0;
|
||||
public function oncePerDay() {
|
||||
return once_per_day == 1;
|
||||
}
|
||||
private var junk_item = 0;
|
||||
public function junkItem() {
|
||||
return junk_item == 1;
|
||||
}
|
||||
private var missile_anim_type = 0;
|
||||
// TODO make missile anim type publically available as an enum
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
package data.blades;
|
||||
|
||||
import haxe.iterators.DynamicAccessIterator;
|
||||
import hscript.Parser;
|
||||
import hscript.Interp;
|
||||
import sys.io.File;
|
||||
import sys.FileSystem;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.util.FlxColor;
|
||||
|
||||
using Reflect;
|
||||
using StringTools;
|
||||
using haxe.io.Path;
|
||||
|
||||
class ScenData {
|
||||
private function new() {}
|
||||
|
||||
public var floorData:Map<Int, FloorData> = [];
|
||||
public var terrainData:Map<Int, TerrainData> = [];
|
||||
public var creatureData:Map<Int, CreatureData> = [];
|
||||
public var itemData:Map<Int, ItemData> = [];
|
||||
|
||||
private var spriteSheets:Map<Int, FlxSprite> = [];
|
||||
|
||||
public var data = "";
|
||||
|
||||
public static function coreData() {
|
||||
var d = new ScenData();
|
||||
|
||||
// TODO find the proper data path with the currently installed BoA depending on mac/windows
|
||||
d.data = "Data";
|
||||
|
||||
// load core data:
|
||||
d.load('${d.data}/corescendata.txt');
|
||||
d.load('${d.data}/corescendata2.txt');
|
||||
d.load('assets/bladesmoddata.txt');
|
||||
|
||||
d.test();
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
private function floorOrTerrainSpriteSheet(num:Int) {
|
||||
if (spriteSheets.exists(num)) {
|
||||
return spriteSheets[num];
|
||||
}
|
||||
|
||||
var path = '$data/Terrain Graphics/G$num.bmp';
|
||||
if (!FileSystem.exists(path)) return null;
|
||||
spriteSheets[num] = SpriteSheet.fromSimpleBmp(path, 46, 55);
|
||||
return spriteSheets[num];
|
||||
}
|
||||
|
||||
public function floorSprite(floorId:Int) {
|
||||
if (!(floorId >= 0 && floorId < 256))
|
||||
throw 'floor $floorId is out of range';
|
||||
if (!floorData.exists(floorId) || floorId == 255) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var fd = floorData[floorId];
|
||||
|
||||
var sheet = floorOrTerrainSpriteSheet(fd.which_sheet);
|
||||
if (sheet == null) {
|
||||
return null;
|
||||
}
|
||||
var sprite = new FlxSprite();
|
||||
sprite.loadGraphicFromSprite(sheet);
|
||||
sprite.animation.frameIndex = fd.which_icon;
|
||||
|
||||
// TODO if it's animated add the animations
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
public function terrainSprite(terrainId:Int, wallSheet1:Int, wallSheet2:Int) {
|
||||
if (!(terrainId >= 0 && terrainId < 512))
|
||||
throw 'terrain $terrainId is out of range';
|
||||
if (!terrainData.exists(terrainId) || terrainId == 1) {
|
||||
return null;
|
||||
}
|
||||
var td = terrainData[terrainId];
|
||||
|
||||
var which_sheet = if (td.name == "Wall") {
|
||||
if (terrainId >= 38)
|
||||
wallSheet2;
|
||||
else
|
||||
wallSheet1;
|
||||
} else {
|
||||
td.which_sheet;
|
||||
};
|
||||
var sheet = floorOrTerrainSpriteSheet(which_sheet);
|
||||
if (sheet == null) {
|
||||
return null;
|
||||
}
|
||||
var sprite = new FlxSprite();
|
||||
sprite.loadGraphicFromSprite(sheet);
|
||||
sprite.animation.frameIndex = td.which_icon;
|
||||
|
||||
|
||||
// ADDED: stamp an icon on another one (for corner walls)
|
||||
if (td.stamp_icon != -1) {
|
||||
var stampSprite = new FlxSprite();
|
||||
stampSprite.loadGraphicFromSprite(sheet);
|
||||
stampSprite.animation.frameIndex = td.stamp_icon;
|
||||
var stampedSprite = new FlxSprite(-92/4, -110/4);
|
||||
stampedSprite.makeGraphic(92, 110, FlxColor.TRANSPARENT, true);
|
||||
stampedSprite.stamp(sprite, Std.int(92/4)+td.icon_offset_x, Std.int(110/4)+td.icon_offset_y);
|
||||
stampedSprite.stamp(stampSprite, Std.int(92/4)+td.stamp_icon_offset_x, Std.int(110/4)+td.stamp_icon_offset_y);
|
||||
return stampedSprite;
|
||||
}
|
||||
|
||||
sprite.x += td.icon_offset_x;
|
||||
sprite.y += td.icon_offset_y;
|
||||
|
||||
// TODO if it's a tall terrain combine it with the upper sprite and set its origin to offset y
|
||||
if (td.second_icon != -1) {
|
||||
var upperSprite = new FlxSprite();
|
||||
upperSprite.loadGraphicFromSprite(sheet);
|
||||
upperSprite.animation.frameIndex = td.second_icon;
|
||||
|
||||
var tallerSprite = new FlxSprite(sprite.x, sprite.y);
|
||||
tallerSprite.makeGraphic(46, 110, FlxColor.TRANSPARENT, true);
|
||||
tallerSprite.stamp(upperSprite, 0, 0);
|
||||
tallerSprite.stamp(sprite, 0, 55);
|
||||
tallerSprite.y -= 55;
|
||||
return tallerSprite;
|
||||
}
|
||||
|
||||
// TODO if it's animated add the animations
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
static var failed:Array<String> = [];
|
||||
static var passed = 0;
|
||||
static function assert(e:Bool, msg="") {
|
||||
if (!e) {
|
||||
failed.push(msg);
|
||||
} else {
|
||||
passed++;
|
||||
};
|
||||
}
|
||||
|
||||
// Core data unit tests:
|
||||
public function test() {
|
||||
failed = [];
|
||||
passed = 0;
|
||||
|
||||
assert(floorData[95].name == "Floor", "floor is floor");
|
||||
assert(floorData[95].which_sheet == 704, "floor gets the right spritesheet");
|
||||
assert(floorData[129].name == "Floor", "floors can import other floors' data");
|
||||
assert(floorData[129].which_sheet == 704, "floors can import other floors' data");
|
||||
assert(floorData[129].specialProperty() == BlockedToNPCs, "blocked special property becomes enum");
|
||||
assert(terrainData[12].name == "Door", "door is door");
|
||||
assert(terrainData[404].name == "Hitching Post", "multi-token name string");
|
||||
|
||||
assert(creatureData[23].field("start_item")[2] == 55, "array properties (nephil starter item)");
|
||||
assert(creatureData[23].field("start_item_chance")[2] == 100, "array properties (nephil starter item)");
|
||||
|
||||
trace('$passed assertions passed');
|
||||
if (failed.length > 0) {
|
||||
trace('${failed.length} assertions failed: $failed');
|
||||
Sys.exit(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function load(file:String) {
|
||||
var scenDataLines = File.getContent(file).replace("\r", "\n").split("\n");
|
||||
var parser = new Parser();
|
||||
var interp = new Interp();
|
||||
|
||||
var defining = "";
|
||||
var id = -1;
|
||||
var data:Dynamic = null;
|
||||
interp.variables["data"] = null;
|
||||
|
||||
function mapFor(type:String):Map<Int,Dynamic> {
|
||||
return switch (type) {
|
||||
case "floor":
|
||||
floorData;
|
||||
case "terrain":
|
||||
terrainData;
|
||||
case "creature":
|
||||
creatureData;
|
||||
case "item":
|
||||
itemData;
|
||||
default:
|
||||
null;
|
||||
};
|
||||
}
|
||||
|
||||
function commitData() {
|
||||
data = interp.variables["data"];
|
||||
if (data == null) return;
|
||||
|
||||
mapFor(defining)[id] = data;
|
||||
data = data.clone();
|
||||
interp.variables["data"] = data;
|
||||
}
|
||||
function clear(?type:String) {
|
||||
if (type == null) type = defining;
|
||||
data = switch (type) {
|
||||
case "floor":
|
||||
new FloorData();
|
||||
case "terrain":
|
||||
new TerrainData();
|
||||
case "creature":
|
||||
new CreatureData();
|
||||
case "item":
|
||||
new ItemData();
|
||||
default:
|
||||
null;
|
||||
};
|
||||
interp.variables["data"] = data;
|
||||
}
|
||||
function beginDefine(type, tid) {
|
||||
commitData();
|
||||
|
||||
if (defining != type) {
|
||||
clear(type);
|
||||
}
|
||||
if (mapFor(type).exists(tid)) {
|
||||
data = mapFor(type)[tid];
|
||||
}
|
||||
interp.variables["data"] = data;
|
||||
|
||||
defining = type;
|
||||
id = tid;
|
||||
}
|
||||
|
||||
function _import(id) {
|
||||
data = mapFor(defining)[id].clone();
|
||||
interp.variables["data"] = data;
|
||||
}
|
||||
|
||||
interp.variables["beginDefine"] = beginDefine;
|
||||
interp.variables["beginscendatascript"] = null;
|
||||
interp.variables["clear"] = clear;
|
||||
interp.variables["_import"] = _import;
|
||||
|
||||
for (line in scenDataLines) {
|
||||
var commentIndex = line.indexOf("//");
|
||||
if (commentIndex != -1) {
|
||||
line = line.substr(0, commentIndex);
|
||||
}
|
||||
line = line.trim();
|
||||
if (line.length > 0) {
|
||||
if (line.startsWith("begindefine")) {
|
||||
line = "beginDefine('" + line.replace("begindefine", "").replace(" ", "',").replace(";", ");");
|
||||
} else if (line.startsWith("fl_")) {
|
||||
line = "data." + line.substr(3);
|
||||
} else if (line.startsWith("te_")) {
|
||||
line = "data." + line.substr(3);
|
||||
} else if (line.startsWith("cr_")) {
|
||||
line = "data." + line.substr(3);
|
||||
} else if (line.startsWith("it_")) {
|
||||
line = "data." + line.substr(3);
|
||||
} else if (line == "clear;") {
|
||||
line = "clear();";
|
||||
} else if (line.startsWith("import =")) {
|
||||
line = "_" + line.replace("=", "(").replace(";", ");");
|
||||
}
|
||||
|
||||
// Wrap array assignments:
|
||||
if (line.startsWith("data.")) {
|
||||
var tokens = line.split(" ");
|
||||
var isString = false;
|
||||
for (token in tokens) {
|
||||
if (token.indexOf('"') != -1)
|
||||
isString = true;
|
||||
}
|
||||
// <thing> = <value> or <thing> <index> = <value>
|
||||
if (!isString && tokens.length > 3) {
|
||||
line = tokens[0] + '[${tokens[1]}] = ' + tokens[3];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
interp.execute(parser.parseString(line));
|
||||
}
|
||||
catch (e) {
|
||||
trace('line `$line` failed because $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Commit the last data object that was defined:
|
||||
commitData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package data.blades;
|
||||
|
||||
import kiss.ByteStream;
|
||||
|
||||
import data.blades.TileMap;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class Scenario {
|
||||
public var outdoorSections:TileArray<TileMap> = [];
|
||||
public var towns:Array<TileMap> = [];
|
||||
public var name = "";
|
||||
public var description1 = "";
|
||||
public var description2 = "";
|
||||
public var credit1 = "";
|
||||
public var credit2 = "";
|
||||
|
||||
public var intro:Array<Array<String>>;
|
||||
|
||||
function new() {
|
||||
|
||||
}
|
||||
|
||||
static var failed:Array<String> = [];
|
||||
static var passed = 0;
|
||||
static function assert(e:Bool, msg="") {
|
||||
if (!e) {
|
||||
failed.push(msg);
|
||||
} else {
|
||||
passed++;
|
||||
};
|
||||
}
|
||||
|
||||
public static function test() {
|
||||
var scen = fromBasFile("Blades of Avernum Scenarios/Valley of Dying Things/valleydy.bas");
|
||||
|
||||
assert(scen.name == "Valley of Dying Things", '${scen.name} is the wrong title');
|
||||
assert(scen.description1.startsWith("Everything in the valley is dying."), '${scen.description1} is the wrong description');
|
||||
assert(scen.description2 == 'Can you cure them?', '${scen.description2} is the wrong description');
|
||||
assert(scen.intro[0][0].startsWith("Adventurers, at last!"), 'problem with ${scen.intro[0][0]}');
|
||||
assert(scen.intro[0][0].endsWith("rewards to earn!"), 'problem with ${scen.intro[0][0]}');
|
||||
assert(scen.intro[0][1] == 'Soon you will be heroes, if you have anything to say about it.', 'problem with ${scen.intro[0][1]}');
|
||||
assert(scen.intro[0][5] == 'Apparently, you are to go to Skylark Vale and investigate a minor plague or disaster or something. They aren\'t specific.', 'problem with ${scen.intro[0][5]}');
|
||||
assert(scen.intro[1][2] == "You've just stayed the night in your room at the fort. You had a good night's sleep and a good meal. Now the sun is rising. Finally, it's time to go out and be heroes!", 'problem with ${scen.intro[1][2]}');
|
||||
assert(scen.intro[1][3] == "", 'problem with ${scen.intro[1][3]}');
|
||||
assert(scen.outdoorSections[0][0].script == "o00Northweste", 'problem with outdoor scripts[0][0]');
|
||||
assert(scen.outdoorSections[2][2].script == "o22Valley Ent", 'problem with outdoor scripts[2][2]');
|
||||
|
||||
trace('$passed assertions passed');
|
||||
if (failed.length > 0) {
|
||||
trace('${failed.length} assertions failed: $failed');
|
||||
Sys.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromBasFile(file) {
|
||||
var scen = new Scenario();
|
||||
var stream = ByteStream.fromFile(file);
|
||||
|
||||
// TODO
|
||||
stream.unknownBytes(11);
|
||||
|
||||
var numTowns = stream.readUInt16();
|
||||
var outdoorWidth = stream.readUInt16();
|
||||
var outdoorHeight = stream.readUInt16();
|
||||
|
||||
// TODO
|
||||
stream.unknownBytes(5);
|
||||
// scenario title, 49 char max, 0-terminated
|
||||
scen.name = stream.readCString(49);
|
||||
|
||||
// in valleydy, it's 07 6C
|
||||
// in stealth, it's 07 6D
|
||||
stream.unknownBytes(2);
|
||||
|
||||
scen.description1 = stream.readCString();
|
||||
stream.skipZeros();
|
||||
scen.credit1 = stream.readCString();
|
||||
stream.skipZeros();
|
||||
// weirdly, this is the one that can be changed in the editor, and the only one to appear in "About this scenario"
|
||||
scen.credit2 = stream.readCString();
|
||||
stream.skipZeros();
|
||||
// Weirdly, this doesn't seem to appear anywhere, but in the case of valleydy, it would make a punchier description
|
||||
scen.description2 = stream.readCString();
|
||||
stream.skipZeros();
|
||||
|
||||
// TODO in valleydy and stealth, it's 02 26 02 27 FF FF
|
||||
stream.unknownBytes(6);
|
||||
|
||||
// Intro paragraphs are c-strings fitting in 16*16 bytes.
|
||||
// After the 0 terminator there are usually junk characters.
|
||||
// 3 pages of 6 paragraphs
|
||||
scen.intro = [for (page in 0...3) {
|
||||
[for (paragraph in 0...6)
|
||||
stream.readCString(16*16-1)];
|
||||
}];
|
||||
|
||||
// After the intro paragpraphs there are a lot of unknown bytes that could possibly be metadata of the first outdoor section's tiles
|
||||
// 1D30 08 is the first outdoor section
|
||||
stream.unknownUntil("0x1D38");
|
||||
|
||||
for (x in 0...outdoorWidth) {
|
||||
scen.outdoorSections[x] = [];
|
||||
}
|
||||
// outdoor section rows
|
||||
for (y in 0...outdoorHeight) {
|
||||
for (x in 0...outdoorWidth) {
|
||||
var outdoorWidth = 48;
|
||||
var outdoorHeight = 48;
|
||||
|
||||
// section name, max length 19, followed by floor tile columns
|
||||
// the type Outdoors(false) is temporary. the above/underground byte comes at the end
|
||||
var sec = new TileMap(outdoorWidth, outdoorHeight, stream.readCString(19), Outdoors(false));
|
||||
|
||||
for (x in 0...outdoorWidth) {
|
||||
for (y in 0...outdoorHeight) {
|
||||
sec.setFloor(x, y, stream.readByte());
|
||||
}
|
||||
}
|
||||
|
||||
// floor heights
|
||||
for (x in 0...outdoorWidth) {
|
||||
for (y in 0...outdoorHeight) {
|
||||
sec.setFloorHeight(x, y, stream.readByte());
|
||||
}
|
||||
}
|
||||
|
||||
// 1 byte of padding to align the int16s
|
||||
// terrain
|
||||
for (x in 0...outdoorWidth) {
|
||||
for (y in 0...outdoorHeight) {
|
||||
// these are big-endian
|
||||
sec.setTerrain(x, y, 256 * stream.readByte() + stream.readByte());
|
||||
}
|
||||
}
|
||||
|
||||
stream.tracePosition();
|
||||
|
||||
// TODO Includes things like area descriptions and sign text.
|
||||
// God, I hope this is a fixed length for all outdoor sections.
|
||||
stream.unknownBytes(3620);
|
||||
|
||||
sec.script = stream.readCString(14);
|
||||
sec.type = switch (stream.readByte()) {
|
||||
case 0:
|
||||
Outdoors(true);
|
||||
case 1:
|
||||
Outdoors(false);
|
||||
default:
|
||||
stream.tracePosition();
|
||||
throw 'bad byte for aboveground/underground';
|
||||
};
|
||||
stream.paddingBytes(20);
|
||||
|
||||
scen.outdoorSections[x][y] = sec;
|
||||
}
|
||||
}
|
||||
|
||||
stream.tracePosition();
|
||||
|
||||
var townIdx = 0;
|
||||
// TODO find an end condition for this loop
|
||||
while (true) {
|
||||
var name = stream.readCString();
|
||||
|
||||
// TODO I'm guessing these zeros identify light level, surface/underground,
|
||||
// creature respawn chance, map size
|
||||
stream.skipZeros();
|
||||
|
||||
var map = new TileMap(48, 48, name, Town(false, {wallSheet1: 601, wallSheet2: 600}));
|
||||
|
||||
|
||||
|
||||
scen.towns[townIdx++] = map;
|
||||
|
||||
// TODO don't
|
||||
break;
|
||||
}
|
||||
|
||||
// 1E274: Fort Talrus (name of town 0)
|
||||
|
||||
// 27866 bytes (medium town, 48x48)
|
||||
|
||||
// 24F4E: Sweetgrove
|
||||
|
||||
// 36826 bytes (large town, 64x64)
|
||||
|
||||
// 2DF28: Blinlock
|
||||
|
||||
// 72922 bytes (medium town, 48x48)
|
||||
|
||||
// well I guess that means they aren't uniform size between small/medium/large...
|
||||
|
||||
// 3FC02: Marralis
|
||||
|
||||
// (medium town, 48x48)
|
||||
|
||||
// TODO all the other stuff
|
||||
return scen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package data.blades;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
|
||||
import kiss.Prelude;
|
||||
import flash.display.BitmapData;
|
||||
import flash.geom.Rectangle;
|
||||
import flash.geom.Point;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.util.FlxColor;
|
||||
|
||||
@:build(kiss.Kiss.build())
|
||||
class SpriteSheet {}
|
||||
@@ -0,0 +1,32 @@
|
||||
// make a clean spritesheet the way Flixel wants it (no borders)
|
||||
(function :FlxSprite fromSimpleBmp [file :Int frameWidth :Int frameHeight]
|
||||
(let [oBmp (Bmp.loadBitmapData file)
|
||||
oWidth oBmp.width
|
||||
oHeight oBmp.height
|
||||
columns (/ (- oWidth 1) (+ frameWidth 1))
|
||||
rows (/ (- oHeight 1) (+ frameHeight 1))
|
||||
width (* frameWidth columns)
|
||||
height (* frameHeight rows)
|
||||
bmp (new BitmapData (Std.int width) (Std.int height))
|
||||
spriteSheet (new FlxSprite)]
|
||||
(doFor row (range rows)
|
||||
(doFor col (range columns)
|
||||
(bmp.copyPixels oBmp (new Rectangle
|
||||
(+ 1 (* col (+ 1 frameWidth)))
|
||||
(+ 1 (* row (+ 1 frameHeight)))
|
||||
frameWidth
|
||||
frameHeight)
|
||||
(new Point (* col frameWidth) (* row frameHeight)))))
|
||||
(spriteSheet.loadGraphic (FlxGraphic.fromBitmapData bmp) true frameWidth frameHeight)
|
||||
(spriteSheet.replaceColor FlxColor.WHITE FlxColor.TRANSPARENT)
|
||||
spriteSheet))
|
||||
|
||||
// Grab a whole BMP and add transparency
|
||||
(function :FlxSprite fromWholeBmp [file]
|
||||
(let [oBmp (Bmp.loadBitmapData file)
|
||||
sprite (new FlxSprite)]
|
||||
(sprite.loadGraphic (FlxGraphic.fromBitmapData oBmp))
|
||||
(sprite.replaceColor FlxColor.WHITE FlxColor.TRANSPARENT)
|
||||
sprite))
|
||||
|
||||
// TODO some of the sprite sheets mix multiple frame sizes (i.e. character sheets)
|
||||
@@ -0,0 +1,274 @@
|
||||
package data.blades;
|
||||
|
||||
using Type;
|
||||
using Reflect;
|
||||
|
||||
import data.blades.FloorData;
|
||||
|
||||
import flixel.input.keyboard.FlxKey;
|
||||
import kiss.Prelude;
|
||||
import kiss.Kiss;
|
||||
|
||||
enum Direction {
|
||||
North;
|
||||
NorthWest;
|
||||
West;
|
||||
SouthWest;
|
||||
South;
|
||||
SouthEast;
|
||||
East;
|
||||
NorthEast;
|
||||
}
|
||||
|
||||
enum HillDirection {
|
||||
Up(dir:Direction);
|
||||
Down(dir:Direction);
|
||||
}
|
||||
|
||||
enum BeamHitType {
|
||||
Auto;
|
||||
Clear;
|
||||
Undefined;
|
||||
Crumble;
|
||||
}
|
||||
|
||||
enum MirrorType {
|
||||
// ^
|
||||
// |
|
||||
ForwardSlash; // ----/
|
||||
BackSlash; // ----\
|
||||
// |
|
||||
// v
|
||||
}
|
||||
|
||||
enum BeamBehavior {
|
||||
SwapTerrainWhenHit;
|
||||
Fire(dir:Direction);
|
||||
Mirror(type:MirrorType);
|
||||
PowerSource;
|
||||
}
|
||||
|
||||
enum TerrainSpecialProperty {
|
||||
Floor(prop:FloorSpecialProperty);
|
||||
Hill(dir:HillDirection);
|
||||
Beam(behavior:BeamBehavior);
|
||||
Sign;
|
||||
Container;
|
||||
Table;
|
||||
ShimmerLightAndDark;
|
||||
Waterfall(dir:Direction);
|
||||
QuickFlammable;
|
||||
}
|
||||
|
||||
class TerrainData {
|
||||
public function new() {}
|
||||
|
||||
public function clone():TerrainData {
|
||||
var td = new TerrainData();
|
||||
for (field in TerrainData.getInstanceFields()) {
|
||||
try {
|
||||
td.setField(field, this.field(field));
|
||||
} catch (e) {
|
||||
// can't set functions in c++
|
||||
}
|
||||
}
|
||||
return td;
|
||||
}
|
||||
|
||||
public var name:String = "";
|
||||
public var default_script:String = "Unused";
|
||||
public var which_sheet:Int = 0;
|
||||
public var which_icon:Int = 0;
|
||||
public var icon_offset_x:Int = 0;
|
||||
public var icon_offset_y:Int = 0;
|
||||
// ADDED: stamp_icon for handling some corner walls
|
||||
public var stamp_icon:Int = -1;
|
||||
public var stamp_icon_offset_x:Int = 0;
|
||||
public var stamp_icon_offset_y:Int = 0;
|
||||
// TODO implement icon adjustments
|
||||
public var icon_adjust:Int = 0;
|
||||
public var ed_which_sheet:Int = 0;
|
||||
public var ed_which_icon:Int = 0;
|
||||
public var cutaway_which_sheet:Int = -1;
|
||||
public var cutaway_which_icon:Int = 0;
|
||||
public var cutaway_icon_adjust:Int = 0;
|
||||
public var second_icon:Int = -1;
|
||||
public var second_icon_offset_x:Int = 0;
|
||||
public var second_icon_offset_y:Int = 0;
|
||||
public var cutaway_second_icon:Int = -1;
|
||||
public var anim_steps:Int = 0;
|
||||
private var move_block_n:Int = 0;
|
||||
private var move_block_w:Int = 0;
|
||||
private var move_block_s:Int = 0;
|
||||
private var move_block_e:Int = 0;
|
||||
|
||||
static var HILL_DIR_MAP = [
|
||||
19 => Up(West),
|
||||
20 => Up(SouthWest),
|
||||
21 => Up(South),
|
||||
22 => Up(SouthEast),
|
||||
23 => Up(East),
|
||||
24 => Up(NorthEast),
|
||||
25 => Up(North),
|
||||
26 => Up(NorthWest),
|
||||
27 => Down(SouthEast),
|
||||
28 => Down(NorthEast),
|
||||
29 => Down(NorthWest),
|
||||
30 => Down(SouthWest)
|
||||
];
|
||||
|
||||
static var BEAM_DIR_MAP = [
|
||||
32 => North,
|
||||
33 => West,
|
||||
34 => South,
|
||||
35 => East
|
||||
];
|
||||
|
||||
public function moveBlock(dir:Direction) {
|
||||
return if (full_move_block == 1) {
|
||||
true;
|
||||
} else if (full_move_block == 0) {
|
||||
false;
|
||||
} else {
|
||||
(switch (dir) {
|
||||
case North:
|
||||
move_block_n;
|
||||
case West:
|
||||
move_block_w;
|
||||
case South:
|
||||
move_block_s;
|
||||
case East:
|
||||
move_block_e;
|
||||
default:
|
||||
throw 'bad move block direction';
|
||||
}) == 1;
|
||||
};
|
||||
}
|
||||
private var look_block_n:Int = 0;
|
||||
private var look_block_w:Int = 0;
|
||||
private var look_block_s:Int = 0;
|
||||
private var look_block_e:Int = 0;
|
||||
public function lookBlock(dir:Direction) {
|
||||
return if (full_look_block == 1) {
|
||||
true;
|
||||
} else if (full_look_block == 0) {
|
||||
false;
|
||||
} else {
|
||||
(switch (dir) {
|
||||
case North:
|
||||
look_block_n;
|
||||
case West:
|
||||
look_block_w;
|
||||
case South:
|
||||
look_block_s;
|
||||
case East:
|
||||
look_block_e;
|
||||
default:
|
||||
throw 'bad look block direction';
|
||||
}) == 1;
|
||||
};
|
||||
}
|
||||
private var blocks_view_n:Int = 0;
|
||||
private var blocks_view_w:Int = 0;
|
||||
private var blocks_view_s:Int = 0;
|
||||
private var blocks_view_e:Int = 0;
|
||||
public function blocksView(dir:Direction) {
|
||||
return (switch (dir) {
|
||||
case North:
|
||||
blocks_view_n;
|
||||
case West:
|
||||
blocks_view_w;
|
||||
case South:
|
||||
blocks_view_s;
|
||||
case East:
|
||||
blocks_view_e;
|
||||
default:
|
||||
throw 'bad blocks view direction';
|
||||
}) == 1;
|
||||
}
|
||||
public var height_adj_pixels:Int = 0;
|
||||
private var suppress_floor:Int = 0;
|
||||
public function suppressFloor() {
|
||||
return suppress_floor == 1;
|
||||
}
|
||||
public var light_radius:Int = 0;
|
||||
public var step_sound:Int = -1;
|
||||
private var shortcut_key:Int = -1;
|
||||
public function shortcutKey() {
|
||||
return if (shortcut_key == -1) {
|
||||
FlxKey.NONE;
|
||||
} else {
|
||||
FlxKey.A + shortcut_key;
|
||||
};
|
||||
}
|
||||
private var crumble_type:Int = 0;
|
||||
public function crumblesByMoveMountains(level:Int) {
|
||||
return if (crumble_type > 0) {
|
||||
level >= crumble_type;
|
||||
} else {
|
||||
false;
|
||||
}
|
||||
}
|
||||
public function crumblesByBeam() {
|
||||
return beam_hit_type == 3 && crumble_type != 0;
|
||||
}
|
||||
public var terrain_to_crumble_to:Int = 0;
|
||||
private var beam_hit_type:Int = 0;
|
||||
public function beamHitType() {
|
||||
return BeamHitType.createEnumIndex(beam_hit_type);
|
||||
}
|
||||
public var hidden_town_terrain:Int = -1;
|
||||
public var swap_terrain:Int = -1;
|
||||
private var is_bridge:Int = 0;
|
||||
public function isBridge() {
|
||||
return is_bridge == 1;
|
||||
}
|
||||
private var is_road:Int = 0;
|
||||
public function isRoad() {
|
||||
return is_road == 1;
|
||||
}
|
||||
// TODO allow highlighting the sprite instead of using the old-school lettered selection system
|
||||
private var can_look_at:Int = 0;
|
||||
public function canLookAt() {
|
||||
return can_look_at == 1;
|
||||
}
|
||||
private var special_property:Int = 0;
|
||||
function specialProperty() {
|
||||
return Kiss.exp('(case special_property
|
||||
((when (<= 0 prop 8) prop)
|
||||
(Floor (FloorSpecialProperty.createEnumIndex prop)))
|
||||
((when (HILL_DIR_MAP.exists prop) prop)
|
||||
(Hill (dictGet HILL_DIR_MAP prop)))
|
||||
(31 (Beam SwapTerrainWhenHit))
|
||||
((when (BEAM_DIR_MAP.exists prop) prop)
|
||||
(Beam
|
||||
(Fire
|
||||
(dictGet BEAM_DIR_MAP prop))))
|
||||
(36 (Beam (Mirror BackSlash)))
|
||||
(37 (Beam (Mirror ForwardSlash)))
|
||||
(38 (Beam PowerSource))
|
||||
(39 Sign)
|
||||
(40 Container)
|
||||
(41 Table)
|
||||
(42 ShimmerLightAndDark)
|
||||
(43 (Waterfall South))
|
||||
(44 (Waterfall East))
|
||||
(45 QuickFlammable)
|
||||
(otherwise (throw "undefined special_property")))');
|
||||
}
|
||||
// specialProperty() defined in .kiss file
|
||||
|
||||
public var special_strength:Int = 0;
|
||||
|
||||
private var draw_on_automap:Int = 0;
|
||||
public function drawOnAutomap() {
|
||||
return draw_on_automap == 1;
|
||||
}
|
||||
private var full_move_block:Int = -1;
|
||||
private var full_look_block:Int = -1;
|
||||
private var shimmers:Int = 0;
|
||||
public function shouldShimmer() {
|
||||
return shimmers == 1;
|
||||
}
|
||||
public var out_fight_town_used:Int = -1;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package data.blades;
|
||||
|
||||
typedef TileArray<T> = Array<Array<T>>;
|
||||
|
||||
typedef TownDetails = {
|
||||
wallSheet1:Int,
|
||||
wallSheet2:Int
|
||||
};
|
||||
|
||||
enum MapType {
|
||||
Town(underground:Bool, details:TownDetails);
|
||||
Outdoors(underground:Bool);
|
||||
}
|
||||
|
||||
class TileMap {
|
||||
// TODO might need encapsulation
|
||||
public var floorCodes:TileArray<Int>;
|
||||
public var floorHeights:TileArray<Int>;
|
||||
public var terrainCodes:TileArray<Int>;
|
||||
|
||||
public var width = 0;
|
||||
public var height = 0;
|
||||
|
||||
public var name = "";
|
||||
|
||||
private function tileArray<T>(defaultValue:T) {
|
||||
return [for (x in 0...width) [for (y in 0...height) defaultValue]];
|
||||
}
|
||||
|
||||
public function wallSheet(num:Int) {
|
||||
return switch (type) {
|
||||
case Town(underground, det):
|
||||
if (num == 1)
|
||||
det.wallSheet1;
|
||||
else
|
||||
det.wallSheet2;
|
||||
case Outdoors(true):
|
||||
614;
|
||||
case Outdoors(false):
|
||||
616;
|
||||
};
|
||||
}
|
||||
|
||||
public function new(width, height, name, type) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.name = name;
|
||||
floorCodes = tileArray(255);
|
||||
terrainCodes = tileArray(0);
|
||||
floorHeights = tileArray(9);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public var type:MapType;
|
||||
public var script = "";
|
||||
|
||||
public function setFloor(x, y, code) {
|
||||
floorCodes[x][y] = code;
|
||||
}
|
||||
|
||||
public function setFloorHeight(x, y, height) {
|
||||
floorHeights[x][y] = height;
|
||||
}
|
||||
|
||||
public function setTerrain(x, y, code) {
|
||||
terrainCodes[x][y] = code;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user