move standalone projects into own directory

This commit is contained in:
2023-04-27 11:39:41 -06:00
parent 5d593e22b0
commit acda704057
369 changed files with 1170 additions and 5 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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