remove all old files, update npm build

This commit is contained in:
Chen Asraf
2022-05-21 01:27:46 +03:00
parent b3ae8cdd76
commit 6b9060a036
47 changed files with 279 additions and 8805 deletions

25
.gitignore vendored
View File

@@ -1,3 +1,9 @@
#========================================================================
# Dart
#========================================================================
# See https://www.dartlang.org/guides/libraries/private-files
# Files and directories created by pub
.dart_tool/
.packages
@@ -9,18 +15,19 @@ pubspec.lock
# If you don't generate documentation locally you can remove this line.
doc/api/
# dotenv environment variables file
.env*
# Avoid committing generated Javascript files:
*.dart.js
*.info.json # Produced by the --dump-info flag.
*.js # When generated by dart2js. Don't specify *.js if your
# project includes source files written in JavaScript.
*.info.json # Produced by the --dump-info flag.
*.js # When generated by dart2js. Don't specify *.js if your
# project includes source files written in JavaScript.
*.js_
*.js.deps
*.js.map
# extras
doc/
.dart_tool/pub/bin/test
output.dart
npm_package/
npm_package/package.json
.flutter-plugins
.flutter-plugins-dependencies
web/dw_data.json

6
.prettierrc Normal file
View File

@@ -0,0 +1,6 @@
{
"printWidth": 100,
"semi": false,
"singleQuote": false,
"trailingComma": "all"
}

14
.vscode/launch.json vendored
View File

@@ -5,22 +5,16 @@
"version": "0.2.0",
"configurations": [
{
"name": "Dart - Current File",
"name": "Dump json -> dart",
"type": "dart",
"request": "launch",
"program": "${file}"
"program": "scripts/parsers/json_to_dart.dart"
},
{
"name": "Migrate v2 -> v3",
"name": "Dump dart -> json",
"type": "dart",
"request": "launch",
"program": "scripts/migrate/v2_to_v3.dart"
},
{
"name": "Migrate v3 json -> dart",
"type": "dart",
"request": "launch",
"program": "scripts/migrate/json_to_dart.dart"
"program": "scripts/parsers/dart_to_json.dart"
},
{
"name": "All Tests",

View File

@@ -1,6 +1,6 @@
import 'package:dungeon_world_data/_old/dw_data.dart';
import 'package:dungeon_world_data/dungeon_world_data.dart';
void main() {
print('Data version: ${dungeonWorld.version}');
print('Data version: ${dungeonWorldData.version}');
print('Please import this library and access `dungeonWorld`.');
}

BIN
doc/.DS_Store vendored Executable file

Binary file not shown.

View File

@@ -1,31 +0,0 @@
import 'dw_entity.dart';
class Alignment extends DWEntity {
/// Alignment name
String name;
/// Alignment description
String description;
Alignment({
String? key,
required this.name,
required this.description,
}) : super(key: key ?? DWEntity.generateKey(name));
factory Alignment.fromJSON(Map map) => Alignment(
key: map['key'],
name: map['name'],
description: map['description'],
);
@override
Map toJSON() => {
'key': key,
'name': name,
'description': description,
};
@override
Alignment copy() => Alignment.fromJSON(toJSON());
}

View File

@@ -1,182 +0,0 @@
import 'dart:math';
import 'package:quiver/core.dart';
class Dice {
// Amount of dice in set
num amount;
// No. of sides for the die
num sides;
// Modifier value for dice, can be added to `DiceResult` value
num? modifier;
// Last result rolled by this die
DiceResult? lastResult;
/// Simple dice, with sides, die count and modifier.
/// You can multiply, add or subtract Dice objects to change the amount of rolls (notice dice must
/// be with the same amount of sides).
///
/// To roll multiple dice at once, you may use the static `Dice.roll(<Dice>[...])`.
Dice(this.sides, [this.amount = 1, this.modifier = 0]);
static Dice d4 = Dice(4);
static Dice d6 = Dice(6);
static Dice d8 = Dice(8);
static Dice d10 = Dice(10);
static Dice d12 = Dice(12);
static Dice d20 = Dice(20);
Dice copyWith({int? sides, int? amount, int? modifier}) => Dice(
sides ?? this.sides,
amount ?? this.amount,
modifier ?? this.modifier,
);
/// Parse strings such as `d6`, or `2d20` into a `Dice` object.
static Dice parse(String str) {
List segs = str.split(RegExp('d', caseSensitive: true));
if (segs[0] != '') {
return Dice(num.parse(segs[1]), num.parse(segs[0]));
}
return Dice(num.parse(segs[1]));
}
@override
String toString() =>
// ignore: unnecessary_brace_in_string_interps
'${amount}d${sides}${modifier != null && modifier != 0 ? modRepr : ''}';
Dice operator *(obj) {
if (obj is Dice) {
if (obj.sides != sides) {
throw ("Can't multiply different sided die!");
}
return Dice(sides, obj.amount * amount, modifier);
}
if (obj is num) {
return Dice(sides, (amount * obj).toInt());
}
return this;
}
Dice operator /(obj) {
if (obj is Dice) {
if (obj.sides != sides) {
throw ("Can't divide different sided die!");
}
return Dice(sides, obj.amount / amount, modifier);
}
if (obj is num) {
return Dice(sides, amount ~/ obj, modifier);
}
return this;
}
Dice operator +(obj) {
if (obj is Dice) {
if (obj.sides != sides) {
throw ("Can't add different sided die!");
}
return Dice(sides, obj.amount + amount, modifier);
}
if (obj is num) {
return Dice(sides, amount + obj.toInt(), modifier);
}
return this;
}
Dice operator -(obj) {
if (obj is Dice) {
if (obj.sides != sides) {
throw ("Can't subtract different sided die!");
}
return Dice(sides, obj.amount - amount, modifier);
}
if (obj is num) {
return Dice(sides, amount - obj.toInt(), modifier);
}
return this;
}
@override
bool operator ==(dynamic other) {
if (other is Dice) {
return amount == other.amount && sides == other.sides && modifier == other.modifier;
}
return other.toString() == toString();
}
@override
int get hashCode => hash3(amount, sides, modifier);
/// Rolls the dice and returns the `DiceResult`.
DiceResult getRoll() {
var results = <num>[];
for (num i = 0; i < amount; i++) {
results.add(Random().nextInt(sides as int) + 1);
}
return lastResult = DiceResult(this, results);
}
Dice get single => this / amount;
Dice multiple(int amount) => single * amount;
/// Roll arbitrary amount of (possibly) different sided dice.
static List<DiceResult> roll(List<Dice> dice) {
var results = <DiceResult>[];
for (var die in dice) {
results.add(die.getRoll());
}
return results;
}
String get modRepr => ((modifier ?? 0) > 0 ? '+' : '') + modifier.toString();
}
class DiceResult {
/// The corresponding dice.
Dice dice;
/// List of results, by order of dice rolled.
List<num> results;
/// Represents a result of a die roll
DiceResult(this.dice, this.results);
@override
String toString() => '$dice${didHitNaturalMax ? '*' : ''} => $total';
// More detailed version of `toString`.
String get toDetailedString =>
'$dice${didHitNaturalMax ? '*' : ''} => $total\n $mappedResults\n ${didHitNaturalMax ? "Die no. $indexOfNaturalMax hit 20" : "Didn't hit 20"}';
// All results layed out with their respective die.
String get mappedResults {
var out = <String>[];
for (num i = 0; i < results.length; i++) {
out.add('${i + 1}: ${results[i as int]}');
}
return out.toString() + (dice.modifier != 0 ? ' (${dice.modRepr})' : '');
}
/// Total (accumulated) value of result, including modifiers.
num get total => results.reduce((tot, cur) => tot + cur) + (dice.modifier ?? 0);
// Boolean that represents whether any of the rolled dice hit their natural max value (e.g. 20 for d20)
bool get didHitNaturalMax => results.any((r) => r == dice.sides);
// Get first index of the die that hit natural max value.
num get indexOfNaturalMax => results.indexOf(dice.sides);
}

View File

@@ -1,14 +0,0 @@
import 'src/_dungeon_world_data.dart';
export 'src/_dungeon_world_data.dart';
export 'alignment.dart';
export 'dice.dart';
export 'equipment.dart';
export 'gear_choice.dart';
export 'mappers.dart';
export 'monster.dart';
export 'move.dart';
export 'player_class.dart';
export 'spell.dart';
export 'tag.dart';
final dungeonWorld = DungeonWorldData();

View File

@@ -1 +0,0 @@
export 'dw_data.dart' show DWEntity;

View File

@@ -1,55 +0,0 @@
import 'dw_entity.dart';
import 'tag.dart';
import 'mappers.dart';
class Equipment extends DWEntity {
/// Equipment name
String name;
/// Equipment name, in plural
String pluralName;
/// Item description
String? description;
/// Equipment tags
List<Tag> tags;
Equipment({
String? key,
required this.name,
String? pluralName,
this.description,
required this.tags,
}) : pluralName = pluralName ?? _calcPluralName(name),
super(key: key ?? DWEntity.generateKey(name));
@override
String toString() => '$name: $description (tags: $tags)';
factory Equipment.fromJSON(Map map) => Equipment(
key: map['key'],
name: map['name'],
pluralName: map['plural_name'],
description: map['description'],
tags: listMapper(map['tags'], (dynamic i) => Tag.fromJSON(i)),
);
@override
Map toJSON() {
return {
'name': name,
'tags': listMapper<Tag, dynamic, Tag>(tags, (t) => t.toJSON()),
'description': description,
'plural_name': pluralName,
'key': key,
};
}
@override
Equipment copy() {
return Equipment.fromJSON(toJSON());
}
static String _calcPluralName(String name) => '${name}s';
}

View File

@@ -1,92 +0,0 @@
import 'package:dungeon_world_data/_old/tag.dart';
import 'dw_entity.dart';
import 'mappers.dart';
class GearChoice extends DWEntity {
/// Description of choices, possibly with other benefits to the stats.
String label;
/// The list of options to choose from. `label` should specify how many to choose.
List<GearOption> gearOptions;
// [start, end] as indexes, [-1] for all, [] (default) for none
List<int> preselect;
int? maxSelections;
GearChoice({
String? key,
required this.label,
required this.gearOptions,
this.preselect = const [],
this.maxSelections,
}) : super(key: key);
factory GearChoice.fromJSON(Map map) => GearChoice(
key: map['key'],
label: map['label'],
gearOptions: gearOptionMapper(map['list']),
preselect: map['preselect'] ?? [],
maxSelections: map['maxSelections'],
);
@override
Map toJSON() => {
'key': key,
'label': label,
'list': listMapper<GearOption, dynamic, GearOption>(gearOptions, (i) => i.toJSON()),
'preselect': preselect,
'maxSelections': maxSelections,
};
List<GearOption> get preselectedGearOptions => preselect.isEmpty
? []
: preselect.first == -1
? gearOptions
: gearOptions.sublist(preselect.first, preselect.last);
@override
GearChoice copy() => GearChoice.fromJSON(toJSON());
}
class GearOption extends DWEntity {
// String key;
String name;
List<Tag> tags;
GearOption({
String? key,
required this.name,
required this.tags,
}) : super(key: key ?? DWEntity.generateKey(name));
factory GearOption.fromJSON(Map map) => GearOption(
key: map['key'],
name: map['name'],
tags: listMapper(map['tags'], (dynamic t) => Tag.fromJSON(t)),
);
factory GearOption.parse(dynamic str) {
if (str is Map) {
return GearOption.fromJSON(str);
}
final num openParen = str.indexOf('(');
final num? closeParen = str.indexOf(')');
final name = str.substring(0, openParen > -1 ? openParen : null);
final rawTags =
openParen > -1 && closeParen! > -1 ? str.substring(openParen + 1, closeParen) : '';
final tags = rawTags.isNotEmpty
? rawTags.split(',').map((tag) => Tag.fromJSON(tag.trim())).toList()
: [];
return GearOption(name: name, tags: tags);
}
@override
dynamic toJSON() => {
'name': name,
'tags': listMapper<Tag, dynamic, Tag>(tags, (t) => t.toJSON()),
};
@override
GearOption copy() => GearOption.fromJSON(toJSON());
}

View File

@@ -1,78 +0,0 @@
import 'spell.dart';
import 'alignment.dart';
import 'equipment.dart';
import 'gear_choice.dart';
import 'move.dart';
import 'monster.dart';
import 'player_class.dart';
import 'tag.dart';
List<R> listMapper<T, R, A>(List<T>? lst, R Function(A obj) mapper) =>
lst != null && lst.isNotEmpty
? (List<A>.from(lst.where((obj) => obj != null && obj is A))
.map<R>((obj) => mapper(obj))
.toList())
: <R>[];
Map<K, V> mapMapper<K, V>(
Map? map, MapEntry<K, V> Function(dynamic key, dynamic obj) mapper) =>
map != null ? Map.from(map).map<K, V>(mapper) : {};
List<Move> moveListMapper(List? lst) => lst == null || lst.isEmpty
? <Move>[]
: listMapper<Map, Move, dynamic>(
List<Map>.from(lst), (v) => Move.fromJSON(v));
Map<String, Move> moveMapMapper(Map map) => mapMapper<String, Move>(
map, (k, v) => MapEntry<String, Move>(k.toString(), Move.fromJSON(v)));
Map<String, PlayerClass> classMapper(Map map) => mapMapper(
map,
(k, v) =>
MapEntry<String, PlayerClass>(k.toString(), PlayerClass.fromJSON(v)));
Map<String, List<String>> nameMapper(Map? map) =>
mapMapper<String, List<String>>(
map,
(k, v) => MapEntry<String, List<String>>(k.toString(),
listMapper<dynamic, String, dynamic>(v, (j) => j.toString())));
List<List<String>> looksMapper(List? lst) =>
listMapper(lst, (dynamic i) => listMapper(i, (dynamic j) => j.toString()));
Map<String, Alignment> alignmentsMapper(Map? map) => mapMapper(map,
(k, v) => MapEntry<String, Alignment>(k.toString(), Alignment.fromJSON(v)));
List<GearChoice> gearChoiceMapper(List? lst) =>
listMapper(lst, (dynamic v) => GearChoice.fromJSON(v));
List<GearOption> gearOptionMapper(List? lst) =>
listMapper(lst, (dynamic v) => GearOption.parse(v));
Map<String, Equipment> equipmentMapper(Map map) => mapMapper(map,
(k, v) => MapEntry<String, Equipment>(k.toString(), Equipment.fromJSON(v)));
List<Spell> spellsMapper(List? lst) {
if (lst == null) {
return [];
}
return listMapper(lst, (dynamic v) => Spell.fromJSON(v));
}
Map<String, Monster> monsterMapper(Map map) => mapMapper(map,
(k, v) => MapEntry<String, Monster>(k.toString(), Monster.fromJSON(v)));
Map<String, Tag> tagMapper(Map map) => mapMapper(
map, (k, v) => MapEntry<String, Tag>(k.toString(), Tag.fromJSON(v)));
Map<String, Tag> tagInfoMapper(Map map) => mapMapper(map, (k, v) {
var cleanName = v['name']
.toString()
.replaceAll(RegExp(r'[^a-z]', caseSensitive: false), ' ')
.trim()
.replaceFirst(RegExp(r'^n\s'), '')
.trim();
return MapEntry<String, Tag>(
k.toString(), Tag(cleanName, null, v['description']));
});

View File

@@ -1,56 +0,0 @@
import 'dw_entity.dart';
import 'tag.dart';
import 'mappers.dart';
class Monster extends DWEntity {
/// Monster name
String name;
/// Monster description
String description;
/// Monster instinct
String instinct;
/// Monster tags
List<Tag> tags;
/// Monster moves
List<String> moves;
Monster({
String? key,
required this.name,
required this.description,
required this.instinct,
required this.tags,
required this.moves,
}) : super(key: key ?? DWEntity.generateKey(name));
@override
String toString() =>
'$name\n tags: $tags,\n moves: $moves\n instinct: $instinct';
static Monster fromJSON(Map map) => Monster(
key: map['key'],
name: map['name'],
description: map['description'],
instinct: map['instinct'],
tags: listMapper(map['tags'], (dynamic i) => Tag.fromJSON(i)),
moves: listMapper(map['moves'], (dynamic i) => i.toString()));
@override
Map toJSON() {
return {
'key': key,
'name': name,
'description': description,
'instinct': instinct,
'tags': listMapper<Tag, dynamic, Tag>(tags, (tag) => tag.toJSON()),
'moves': moves,
};
}
@override
Monster copy() => Monster.fromJSON(toJSON());
}

View File

@@ -1,47 +0,0 @@
import 'dw_entity.dart';
import 'mappers.dart';
class Move extends DWEntity {
/// Move name
String name;
/// Move description
String description;
/// Move explanation
String? explanation;
/// Classes that can use this move.
/// The keys correspond to the `PlayerClass` key.
List<String> classes;
Move({
String? key,
required this.name,
required this.description,
this.explanation,
required this.classes,
}) : super(key: key ?? DWEntity.generateKey(name));
factory Move.fromJSON(Map map) => Move(
key: map['key'],
name: map['name'],
classes: map['classes'] != null
? listMapper(map['classes'], (dynamic j) => j.toString())
: [],
description: map['description'],
explanation: map['explanation'],
);
@override
Map toJSON() => {
'key': key,
'name': name,
'classes': classes,
'description': description,
'explanation': explanation,
};
@override
Move copy() => Move.fromJSON(toJSON());
}

View File

@@ -1,133 +0,0 @@
import 'alignment.dart';
import 'dice.dart';
import 'dw_entity.dart';
import 'mappers.dart';
import 'move.dart';
import 'spell.dart';
import 'gear_choice.dart';
class PlayerClass extends DWEntity {
/// Class name
String name;
/// Class description
String description;
/// Base max. weight load, without the +STR.
num load;
/// Base HP
num baseHP;
/// Hit die
Dice damage;
/// Character name options, mapped by race
Map<String, List<String>> names;
/// Class Bonds
List<String> bonds;
/// Character look options
List<List<String>> looks;
/// Character alignment options. Map of `Alignment.key` => `Alignment`
Map<String, Alignment> alignments;
/// Race moves
List<Move> raceMoves;
/// Starting moves
List<Move> startingMoves;
/// Starting moves
List<Move> advancedMoves1;
/// Starting moves
List<Move> advancedMoves2;
/// Spells
List<Spell> spells;
/// Gear choices
List<GearChoice> gearChoices;
PlayerClass({
String? key,
required this.name,
required this.description,
required this.load,
required this.baseHP,
required this.damage,
required this.names,
required this.bonds,
required this.looks,
required this.alignments,
required this.raceMoves,
required this.startingMoves,
required this.advancedMoves1,
required this.advancedMoves2,
required this.spells,
required this.gearChoices,
}) : super(key: key ?? DWEntity.generateKey(name));
/// Combined list of `advancedMoves1` and `advancedMoves2`
List<Move> get advancedMoves => advancedMoves1 + advancedMoves2;
@override
String toString() {
return 'Class: $name, baseHP: $baseHP';
}
factory PlayerClass.fromJSON(Map map) => PlayerClass(
key: map['key'] ??
map['name']
.toString()
.toLowerCase()
.replaceAll(RegExp('[^a-z]'), '_'),
name: map['name'],
description: map['description'],
load: map['load'],
baseHP: map['base_hp'],
damage: Dice.parse(map['damage']),
names: nameMapper(map['names']),
bonds: listMapper(map['bonds'], (dynamic j) => j.toString()),
looks: looksMapper(map['looks']),
alignments: alignmentsMapper(map['alignments']),
raceMoves: moveListMapper(map['race_moves']),
startingMoves: moveListMapper(map['starting_moves']),
advancedMoves1: moveListMapper(map['advanced_moves_1']),
advancedMoves2: moveListMapper(map['advanced_moves_2']),
spells: spellsMapper(map['spells']),
gearChoices: gearChoiceMapper(map['gear_choices']),
);
@override
Map toJSON() => {
'key': key,
'name': name,
'description': description,
'load': load,
'base_hp': baseHP,
'damage': damage.toString(),
'names': names,
'bonds': bonds,
'looks': looks,
'alignments':
mapMapper(alignments, ((k, v) => MapEntry(k, v.toJSON()))),
'race_moves':
listMapper<Move, Map, Move>(raceMoves, (move) => move.toJSON()),
'starting_moves':
listMapper<Move, Map, Move>(startingMoves, (move) => move.toJSON()),
'advanced_moves_1': listMapper<Move, Map, Move>(
advancedMoves1, (move) => move.toJSON()),
'advanced_moves_2': listMapper<Move, Map, Move>(
advancedMoves2, (move) => move.toJSON()),
'spells': listMapper(spells, (dynamic v) => v.toJSON()),
'gear_choices': listMapper<GearChoice, Map, GearChoice>(
gearChoices, (choice) => choice.toJSON()),
};
@override
PlayerClass copy() => PlayerClass.fromJSON(toJSON());
}

View File

@@ -1,56 +0,0 @@
import 'dw_entity.dart';
import 'mappers.dart';
import 'tag.dart';
class Spell extends DWEntity {
/// Spell name
String name;
/// Spell description
String description;
/// Spell level
String level;
/// Spell tags
List<Tag> tags;
/// Spell classes
List<String> classKeys;
Spell({
String? key,
required this.name,
required this.description,
required this.level,
required this.tags,
required this.classKeys,
}) : super(key: key ?? DWEntity.generateKey(name));
static Spell fromJSON(Map map) => Spell(
key: map['key'],
name: map['name'],
description: map['description'],
classKeys: map['classKeys'] ?? [],
level: map['level'].toString(),
tags: listMapper(map['tags'], (dynamic i) => Tag.fromJSON(i)),
);
@override
String toString() => '$name ($level)';
@override
Map toJSON() {
return {
'key': key,
'name': name,
'description': description,
'classKeys': classKeys,
'level': level,
'tags': listMapper<Tag, dynamic, Tag>(tags, (tag) => tag.toJSON()),
};
}
@override
Spell copy() => Spell.fromJSON(toJSON());
}

View File

@@ -1,44 +0,0 @@
part of '_dungeon_world_data.dart';
abstract class DWEntity {
String? key;
DWEntity({String? key}) {
this.key = key ?? DWEntity.generateKey(null);
}
/// Generates unique key for identification.
///
/// If a non-empty String identifier is supplied, it is cleaned up of non-alphabetic characters
/// and turned to lowercase.
///
/// Otherwise, a `Uuid().v4()` is generated for it.
static String generateKey(String? identifier) =>
identifier != null && identifier.trim().isNotEmpty
? identifier
.trim()
.replaceAll(RegExp(r'[^a-z]+', caseSensitive: false), ' ')
.trim()
.replaceAll(RegExp('\\s+'), '_')
.toLowerCase()
: Uuid().v4();
@override
int get hashCode => hash2(runtimeType, key);
@override
bool operator ==(other) => other.hashCode == hashCode;
/// Returns a JSON representation of this object.
dynamic toJSON();
/// Creates a new instance of this identity, using the existing previous values.
DWEntity copy();
/// Helper method for getting any `DWEntity` by its key.
static T? getByKey<T extends DWEntity>(String key, Iterable<T> list) => list.firstWhereOrNull(
(element) => element.key == key,
);
}
// typedef Key = String Function(String);

View File

@@ -1,9 +0,0 @@
part of '_dungeon_world_data.dart';
List<Tag> tagList = [];
List<Equipment> equipmentList = [];
List<PlayerClass> playerClassList = [];
List<Monster> monsterList = [];
List<Move> basicMovesList = [];
List<Spell> spellList = [];
List<Move> specialMovesList = [];

File diff suppressed because it is too large Load Diff

View File

@@ -1,91 +0,0 @@
import 'package:collection/collection.dart' show IterableExtension;
import 'package:quiver/core.dart';
import 'package:uuid/uuid.dart';
import '../tag.dart';
import '../alignment.dart';
import '../equipment.dart';
import '../mappers.dart';
import '../monster.dart';
import '../move.dart';
import '../player_class.dart';
import '../spell.dart';
import '../gear_choice.dart';
import '../dice.dart';
part '_data.dart';
part '_cache.dart';
part '_base.dart';
part '_homebrew.dart';
// ignore: constant_identifier_names
const String VERSION = '2.0.0';
class DungeonWorldData {
late Map<String, dynamic> _raw;
/// Raw data
Map<String, dynamic> get raw => _raw;
/// Basic moves
late List<Move> basicMoves;
/// Special moves
late List<Move> specialMoves;
/// Classes
late List<PlayerClass> classes;
/// Equipment
late List<Equipment> equipment;
/// Spells
late List<Spell> spells;
/// Monsters
late List<Monster> monsters;
/// Tags
late List<Tag> tags;
/// Current version of data, corresponds to same version of https://www.npmjs.com/package/dungeonworld-data
final String version = VERSION;
DungeonWorldData() {
_initFromData();
_raw = toJSON();
}
void _initFromData() {
initData();
initHomebrew();
tags = tagList;
basicMoves = basicMovesList;
specialMoves = specialMovesList;
classes = playerClassList;
equipment = equipmentList;
monsters = monsterList;
spells = spellList;
}
Map<String, dynamic> toJSON() => {
'tags': listMapper<Tag, dynamic, Tag>(tags, (t) => t.toJSON()),
'basic_moves': listMapper<Move, dynamic, Move>(basicMoves, (t) => t.toJSON()),
'special_moves': listMapper<Move, dynamic, Move>(specialMoves, (t) => t.toJSON()),
'classes': listMapper<PlayerClass, dynamic, PlayerClass>(classes, (t) => t.toJSON()),
'equipment': listMapper<Equipment, dynamic, Equipment>(equipment, (t) => t.toJSON()),
'monsters': listMapper<Monster, dynamic, Monster>(monsters, (t) => t.toJSON()),
'spells': listMapper<Spell, dynamic, Spell>(spells, (t) => t.toJSON()),
};
static Map<String, T> listToMap<T extends DWEntity>(
Iterable<T> list, {
String Function(T) key = entryKey,
}) =>
Map<String, T>.fromEntries(
list.map((v) => MapEntry<String, T>(key(v), v)),
);
static String entryKey(DWEntity item) => item.key!;
}

View File

@@ -1,616 +0,0 @@
part of '_dungeon_world_data.dart';
void initHomebrew() {
playerClassList.add(PlayerClass(
key: 'immolator',
name: 'Immolator',
description: '',
load: 9,
baseHP: 4,
damage: Dice.parse('1d8'),
names: {
'human': [
'Solomon',
'Timothy',
'Kalil',
'Omen',
'Yohn',
'Hiko',
'Agasha',
'Elizabeth',
'Harald',
'Fatia',
'Khalwa',
'Adur',
'Ignis',
'Yajna',
'Umlilo',
],
'salamander': [
'Sulfurheart',
'Emberlash',
'Flamewalker',
'Cinderclaw',
'Charfiend',
'Bittertallow',
'Barrowblaze',
'Singescale',
'Candlewick',
'Coalfang',
]
},
bonds: [
'__________ has felt the hellish touch of fire, now they know my strength.',
'I will teach __________ the true meaning of sacrifice.',
'I cast something into the fire for __________ and still owe them their due.'
],
looks: [
['Smoldering eyes', 'Warm eyes', 'Searing eyes'],
['Strange brands', 'Ritual scars', 'Perfect skin'],
['Imperious bearing', 'Manic attitude', 'Barely-hidden rage'],
['Crackling voice', 'Whispering voice', 'Roaring voice']
],
alignments: {
'evil': Alignment(
key: 'evil',
name: 'Evil',
description: 'Sacrifice an unwilling victim to the flames.',
),
'chaotic': Alignment(
key: 'chaotic',
name: 'Chaotic',
description: 'Spread a dangerous new idea',
),
'neutral': Alignment(
key: 'neutral',
name: 'Neutral',
description: 'Exchange a sacrifice, freely given, for a service rendered.',
)
},
raceMoves: [
Move(
key: 'salamander',
name: 'Salamander',
description: 'Non-magical heat and fire cannot harm you.',
explanation: null,
classes: ['immolator'],
),
Move(
key: 'human',
name: 'Human',
description: 'When you Make Camp next to a large, open flame, regain all of your HP.',
explanation: null,
classes: ['immolator'],
)
],
startingMoves: [
Move(
key: 'burning_brand',
name: 'Burning Brand',
description:
'When you conjure a weapon of pure flame, roll+CON.\n* On a 10+ choose two of the following tags, on a 7-9 choose one. You may treat your INT as your STR or DEX in regards to making attacks with this weapon. The weapon always begins with the fiery, touch, dangerous, and 3 uses tags. Each attack with the weapon consumes one use.\n\n\n\n- hand\n\n- thrown, near\n\n- +1 damage\n\n- remove the dangerous tag',
explanation: null,
classes: ['immolator'],
),
Move(
key: 'fighting_fire_with_fire',
name: 'Fighting Fire With Fire',
description:
'When you take damage, and that damage is odd (after armor) the flames within you come to your aid. Roll 1d4 and either add that many uses to your burning brand (if active), take that result forward to summon your burning brand, or reduce the damage by that amount, your choice.',
explanation: null,
classes: ['immolator'],
),
Move(
key: 'zuko_style',
name: 'Zuko Style',
description:
'When you bend a flame to your will, roll+WIS.\n* On a 10+ it does as you command, taking the shape and movement you desire for as long as it has fuel on which to burn.\n* On a 7-9 the effect is short-lived, lasting only a moment.',
explanation: null,
classes: ['immolator'],
),
Move(
key: 'give_me_fuel_give_me_fire',
name: 'Give Me Fuel Give Me Fire',
description:
'When you **gaze intensely into someone eyes**, you may ask their player “what fuels the flames of your desire?” theyll answer with the truth, even if the character does not know or would otherwise keep this hidden.',
explanation: null,
classes: ['immolator'],
),
Move(
key: 'hand_crafted',
name: 'Hand Crafted',
description:
'You may use your hands in place of tools and fire to craft metal objects. Mundane weapons, armor and metal jewelry can all be formed from their raw components. You may unmake these things, as well, but to do so without time and safety might require that you Defy Danger first.',
explanation: null,
classes: ['immolator'],
)
],
advancedMoves1: [
Move(
key: 'lore_of_flame',
name: 'Lore of Flame',
description:
'When you **stare into a source of fire, looking for answers**, roll+WIS On a hit, the GM will tell you something new and interesting about the current situation.\n* On a 10+, the GM will give you good detail.\n* On a 79, the GM will give you an impression. If you already know all there is to know, the GM will tell you that.',
explanation: null,
classes: ['immolator'],
),
Move(
key: 'burns_twice_as_bright',
name: 'Burns Twice As Bright',
description:
'When you **channel the flames of fate**, you may treat a missed roll as a 7-9 or a 7-9 result as a 10+. This may be a roll you or another character has made. Tell the GM something youve lost; an emotion, a memory or some innate piece of your being. You may not use this move again until youve used Burns Half As Long.',
explanation: null,
classes: ['immolator'],
),
Move(
key: 'burns_half_as_long',
name: 'Burns Half As Long',
description:
'You gain this move when you gain Burns Twice as Bright.\n\nWhen you sacrifice a victory to the flames of fate, treat any roll of 10+ as a miss.',
explanation: null,
classes: ['immolator'],
),
Move(
key: 'this_killing_fire',
name: 'This Killing Fire',
description:
'Add the following tags to your options for Burning Brand: **messy, forceful, reach, near, far**',
explanation: null,
classes: ['immolator'],
),
Move(
key: 'firebrand',
name: 'Firebrand',
description:
'When you **introduce a new idea to an NPC**, roll+CHA.\n* On a 10+ They believe the idea to be their own and take to it with fervor On a 7-9, Their passion fades after a day or two. On a miss, they respond negatively, speaking out against the idea.',
explanation: null,
classes: ['immolator'],
),
Move(
key: 'ogdru_jahad',
name: 'Ogdru Jahad',
description:
'Gain the Wizard move Ritual. The GM will always tell you what you have to sacrifice to gain the effect you desire.',
explanation: null,
classes: ['immolator'],
),
Move(
key: 'moth_to_the_flame',
name: 'Moth To The Flame',
description:
'When you tempt a weak mind with your inner fire, roll+WIS.\n* On a 10+ their will is suppressed, theyll follow you and do as you desire, so long as nothing startles or surprises them.\n* On a 7-9, the effect is only strong enough to distract or confuse them. On a miss, they become agitated and upset, your fire having sparked their hidden desires.',
explanation: null,
classes: ['immolator'],
),
Move(
key: 'burning_bridges',
name: 'Burning Bridges',
description:
'When you would take your last breath, dont. Instead, you may erase one of your Bonds. This is permanent and lowers your total available Bonds forever. You are alive and have 1d6 hp. If you have no more Bonds, take your last breath as normal.',
explanation: null,
classes: ['immolator'],
),
Move(
key: 'the_enkindler',
name: 'The Enkindler',
description:
'When you bolster the courage of others roll+CHA.\n* On a 10+ they shake off all fear and doubt, becoming brave in an instant.\n* On a 7-9, this effect is fleeting, they realize its superficiality and resort to cowardice after a moment or two. On a miss, theyre cowed or terrified by your presence.',
explanation: null,
classes: ['immolator'],
),
Move(
key: 'sick_burn',
name: 'Sick Burn',
description:
'When you insult an NPC, roll + CHA.\n* On a 10+ you leave them no room to react, they bear your insult and the scorn of all who hear it.\n* On a 7-9 you cross a line, they will have their revenge, someday. On a miss youve gone too far, they blow up here and now.',
explanation: null,
classes: ['immolator'],
),
],
advancedMoves2: [
Move(
key: 'from_hell_s_heart',
name: "From Hell's Heart",
description:
'Whenever you summon fire with any of your moves, you can replace it with the black fires of hell itself. This fire does not burn with heat and ignores armor, scorching the soul itself. Those creatures without souls cannot be harmed by this type of flame.',
explanation: null,
classes: ['immolator'],
),
Move(
key: 'burning_ring_of_fire',
name: 'Burning Ring Of Fire',
description:
'When you **fuse a willing persons soul to yours**, roll+CHA. On a hit you are bound together, able to sense each other at any distance, as well as sharing your emotional state.\n* On a 7-9, the connection is unstable and dangerous, when you take a debility, so do they (and vice versa). On a miss, the branding is rejected and you both erase any Bonds you have to each other. You may write new Bonds with them at the End of Session as usual. This fusion, once performed, cannot be undone by mortal means.',
explanation: null,
classes: ['immolator'],
),
Move(
key: 'fanning_the_flame',
name: 'Fanning The Flame',
description:
'You may apply the effects of your Firebrand move to a group of people a dozen or so all at once.',
explanation: null,
classes: ['immolator'],
),
Move(
key: 'watch_the_world_burn',
name: 'Watch the World Burn',
description:
'When you **open a channel to the burning planes and call a firestorm**, tell the GM what youre sacrificing and roll+WIS. The sky opens up and fire pours like rain from it within an area about equal to a small village. Everyone and everything in the area takes damage as appropriate.\n* On a 10+ you can extinguish the storm with a little effort.\n* On a 7-9 the fires rage out of your control, spreading and gusting where they are carried by wind and weather. On a miss, something cruel, intelligent and hungry comes with the storm.',
explanation: null,
classes: ['immolator'],
)
],
spells: [],
gearChoices: [
GearChoice(
key: '76316345-ccad-43e2-b0f4-fc0e51fbf50b',
label:
'You carry no weapon and need no armor but the flames the burn within you. Choose two:',
maxSelections: 2,
gearOptions: [
GearOption(
key: 'dungeon_rations',
name: 'Dungeon Rations',
tags: [Tag.fromJSON('{uses: 5}'), Tag.fromJSON('{weight: 1}')],
),
GearOption(
key: 'healing_potion',
name: '1 Healing Potion',
tags: [Tag.fromJSON('{weight: 0}')],
),
GearOption(
key: 'coins',
name: '10 coins',
tags: [],
)
],
)
],
));
playerClassList.add(PlayerClass(
key: 'barbarian',
name: 'Barbarian',
description: '',
load: 8,
baseHP: 8,
damage: Dice.parse('1d10'),
names: {
'outsider': [
'Gorm',
'Si-Yi',
'Priscilla',
'Sen',
'Xia',
'Anneira',
'Haepha',
'Lur',
'Shar',
'Korrin',
'Nkosi',
'Fafnir',
'Qua',
'Sacer',
'Vercingeto',
'Barbozar',
'Clovis',
'Frael',
'Thra raxes',
'Sillius',
'Sha Sheena',
'Khamisi',
]
},
bonds: [
'______________________ is puny and foolish, but amusing to me.',
"______________________ 's ways are strange and confusing.",
'______________________ is always getting into trouble - I must protect them from themselves.',
'______________________ shares my hunger for glory; the earth will tremble at our passing!'
],
looks: [
['Tormented eyes', 'Haunted eyes', 'Wild eyes', 'Shrouded eyes'],
['Mighty thews', 'Long shanks', 'Scrawny body', 'Supple body'],
['Strange tattoos', 'Unusual jewelry', 'Unmarred by decoration'],
['Scraps', 'Silks', "Scavenger's outfit", 'Weather inappropriate clothes']
],
alignments: {
'chaotic': Alignment(
key: 'chaotic',
name: 'Chaotic',
description: 'You eschew a convention of the civilized world.',
),
'neutral': Alignment(
key: 'neutral',
name: 'Neutral',
description: 'Teach someone the ways of your people.',
)
},
raceMoves: [
Move(
key: 'outsider',
name: 'Outsider',
description:
'You may be elf, dwarf, halfling, or human, but you and your people are not from around here. At the beginning of each session, the GM will ask you something about your homeland, why you left, or what you left behind. If you answer them, mark XP.',
explanation: null,
classes: ['barbarian'],
)
],
startingMoves: [
Move(
key: 'herculean_appetites',
name: 'Herculean Appetites',
description:
'Others may content themselves with just a taste of wine, or dominion over a servant or two, but you want more. Choose two appetites. While pursuing one of your appetites if you would roll for a move, instead of rolling 2d6 you roll 1d6+1d8. If the d6 is the higher die of the pair, the GM will also introduce a complication or danger that comes about due to your heedless pursuits.\n\n* Pure destruction\n* Power over others\n* Mortal pleasures\n* Conquest\n* Riches and property\n* Fame and glory',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'the_upper_hand',
name: 'The Upper Hand',
description:
'You take +1 ongoing to last breath rolls. When you take your last breath, on a 79 you make an offer to Death in return for your life. If Death accepts he will return you to life. If not, you die.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'musclebound',
name: 'Musclebound',
description: 'While you wield a weapon it gains the forceful and messy tags.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'what_are_you_waiting_for',
name: 'What Are You Waiting For?',
description:
'When you cry out a challenge to your enemies, roll+Con.\n\n* On a 10+ they treat you as the most obvious threat to be dealt with and ignore your companions, take +2 damage ongoing against them.\n* On a 79 only a few (the weakest or most foolhardy among them) fall prey to your taunting.',
explanation: null,
classes: ['barbarian'],
)
],
advancedMoves1: [
Move(
key: 'full_plate_and_packing_steel',
name: 'Full Plate and Packing Steel',
description: 'You ignore the clumsy tag on armor you wear.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'unencumbered_unharmed',
name: 'Unencumbered, Unharmed',
description:
'So long as you are below your Load and neither wear armor nor carry a shield, take +1 armor.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'still_hungry',
name: 'Still Hungry',
description: 'Choose an additional appetite.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'appetite_for_destruction',
name: 'Appetite for Destruction',
description:
'Take a move from the fighter, bard or thief class list. You may not take multiclass moves from those classes.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'my_love_for_you_is_like_a_truck',
name: 'My Love For You Is Like a Truck',
description:
'When you perform a feat of strength, name someone present whom you have impressed and take +1 forward to parley with them.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'what_is_best_in_life',
name: 'What Is Best In Life',
description:
'At the end of a session, if during this session you have crushed your enemies, seen them driven before you, or have heard the lamentations of their kinfolk, mark XP.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'wide_wanderer',
name: 'Wide Wanderer',
description:
'Youve traveled the wide world over. When you arrive someplace ask the GM about any important traditions, rituals, and so on, theyll tell you what you need to know.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'usurper',
name: 'Usurper',
description:
'When you prove yourself superior to a person in power, take +1 forward with their followers, underlings, and hangers on.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'khan_of_khans',
name: 'Khan Of Khans',
description:
'Your hirelings always accept the gratuitous fulfillment of one of your appetites as payment.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'samson',
name: 'Samson',
description:
'You may take a debility to immediately break free of any physical or mental restraint.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'smash',
name: 'Smash!',
description:
'When you hack and slash, on a 12+ deal your damage and choose something physical your target has (a weapon, their position, a limb): they lose it.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'indestructible_hunger',
name: 'Indestructible Hunger',
description:
'When you take damage you can choose to take 1 ongoing until you sate one of your appetites instead of taking the damage. If you already have this penalty you cannot choose this option.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'eye_for_weakness',
name: 'Eye For Weakness',
description:
'When you discern realities add “What here is weak or vulnerable?” to the list of questions you can ask.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'on_the_move',
name: 'On The Move',
description:
'When you defy a danger caused by movement (maybe falling off a narrow bridge or rushing past an armed guard) take +1.',
explanation: null,
classes: ['barbarian'],
),
],
advancedMoves2: [
Move(
key: 'a_good_day_to_die',
name: 'A Good Day To Die',
description:
'As long as you have less than your Con in current HP (or 1, whichever is higher) take +1 ongoing.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'kill_em_all',
name: "Kill 'Em All",
description:
'**Requires:** Appetite for Destruction\n\nTake another move from the fighter, bard or thief class list. You may not take multiclass moves from those classes.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'war_cry',
name: 'War Cry',
description:
'When you enter battle with a show of force (a shout, a rallying cry, a battle dance) roll+Cha.\n\n* On a 10+ both,\n* On a 79 one or the other.\n\t* Your allies are rallied and take +1 forward\n\t* Your enemies feel fear and act accordingly (avoiding you, hiding, attacking with fear driven abandon)',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'mark_of_might',
name: 'Mark Of Might',
description:
'When you take this move and spend some uninterrupted time reflecting on your past glories you may mark yourself with a symbol of your power (a long braid tied with bells, ritual scars or tattoos, etc.) Any intelligent mortal creature who sees this symbol knows instinctively that you are a force to be reckoned with and treats you appropriately.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'more_always_more',
name: 'More! Always More!',
description:
'When you satisfy an appetite to the extreme (destroying something unique and significant, gaining enormous fame, riches, power, etc.) you may choose to resolve it. Cross it off the list and mark XP. While you may pursue that appetite again, you no longer feel the burning desire you once did. In its place, choose a new appetite from the list or write your own.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'the_one_who_knocks',
name: 'The One Who Knocks',
description:
'When you defy danger, on a 12+ you turn the danger back on itself, the GM will describe how.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'healthy_distrust',
name: 'Healthy Distrust',
description:
'Whenever the unclean magic wielded by mortal men causes you to defy danger, treat any result of 6 as a 79.',
explanation: null,
classes: ['barbarian'],
),
Move(
key: 'for_the_blood_god',
name: 'For The Blood God',
description:
'You are initiated in the old ways, the ways of sacrifice. Choose something your gods (or the ancestor spirits, or your totem, etc) value—gold, blood, bones or the like. When you sacrifice those things as per your rites and rituals, roll+Wis.\n\n* On a 10+ the GM will grant you insight into your current trouble or a boon to help you.\n* On a 79 the sacrifice is not enough and your gods take of your flesh as well, but still grant you some insight or boon.\n* On a miss, you earn the ire of the fickle spirits.',
explanation: null,
classes: ['barbarian'],
),
],
spells: [],
gearChoices: [
GearChoice(
key: '7b819c33-10da-41ae-b31e-fa48c39b4cdd',
label:
'You carry dungeon rations (5 uses, 1 weight), a dagger (hand, 1 weight) some token of where youve traveled or where youre from',
preselect: [-1],
gearOptions: [
GearOption(
key: 'dungeon_rations',
name: 'Dungeon Rations',
tags: [
Tag.fromJSON('{uses: 5}'),
Tag.fromJSON('{weight: 1}'),
],
),
GearOption(
key: 'dagger',
name: 'Dagger',
tags: [
Tag.fromJSON('{hand}'),
Tag.fromJSON('{weight: 1}'),
],
),
GearOption(
key: 'token_of_origin',
name: 'Token Of Origin',
tags: [],
),
],
),
GearChoice(
key: 'dbb50c21-6f20-44d5-8c4f-68bc77e7bc77',
label: 'Choose a weapon',
maxSelections: 1,
gearOptions: [
GearOption(
key: 'axe',
name: 'Axe',
tags: [Tag.fromJSON('close'), Tag.fromJSON('{weight:1}')],
),
GearOption(
key: 'two_handed_sword',
name: 'Two handed sword',
tags: [Tag.fromJSON('close'), Tag.fromJSON('{weight:2}'), Tag.fromJSON('{damage:1}')],
)
],
),
GearChoice(
key: 'fe326cb4-fa3f-4e16-830a-216280ef039d',
label: 'Choose one',
maxSelections: 1,
gearOptions: [
GearOption(
key: 'adventuring_gear_and_dungeon_rations',
name: 'Adventuring gear and dungeon rations',
tags: [Tag.fromJSON('{weight:2}'), Tag.fromJSON('{uses:5}')],
),
GearOption(
key: 'chainmail',
name: 'Chainmail',
tags: [Tag.fromJSON('{weight:1}'), Tag.fromJSON('{armor:1}')],
),
],
)
],
));
}

View File

@@ -1,71 +0,0 @@
import 'dw_entity.dart';
class Tag<T> extends DWEntity {
static Map<String, String > tagInfoCache = {};
/// Tag or feature name
String name;
/// Value, if applicable
T? value;
String? description;
Tag(this.name, [this.value, this.description]) : super(key: name) {
if (description != null &&
description!.isNotEmpty &&
!tagInfoCache.containsKey(key)) {
tagInfoCache[key] = description!;
} else if (description == null ||
description!.isEmpty && tagInfoCache.containsKey(key)) {
description = tagInfoCache[key];
}
}
@override
String toString() => hasValue ? '$name: $value' : name;
/// Returns whether this tag has a corresponding value or not
bool get hasValue => value != null;
@override
String get key => DWEntity.generateKey(name);
factory Tag.fromJSON(obj) {
if (obj is String) {
if (obj == '') {
throw TypeError();
}
var amountThenName = RegExp('([0-9]+)\\s(.*)');
if (amountThenName.hasMatch(obj)) {
var match = amountThenName.allMatches(obj).toList().first;
var name = match.group(2)!;
var value = int.tryParse(match.group(1)!);
return Tag(name, value as dynamic);
}
var mapLike = RegExp('\\{(.*):\\s?([+-]?[0-9]+)\\}');
if (mapLike.hasMatch(obj)) {
var match = mapLike.allMatches(obj).toList().first;
var name = match.group(1)!;
var value = int.tryParse(match.group(2)!);
return Tag(name, value as dynamic);
}
}
if (obj is Map) {
var key = obj.keys.first.toString();
return Tag(key, obj[key]);
}
return Tag(obj);
}
@override
dynamic toJSON() {
return hasValue ? {name: value} : name;
}
@override
Tag copy() => Tag.fromJSON(toJSON());
}

View File

@@ -72,25 +72,37 @@ class RepositoryMap<K, V> extends RepositoryItem<Map<K, V>> {
}
class DungeonWorldRepository extends RepositoryMap<String, dynamic> {
final version = '3.0.0';
DungeonWorldRepository([String? initialLocale]) : super(initialLocale);
@override
create() => <String, Map<String, dynamic>>{
'characterClasses': <String, CharacterClass>{},
'items': <String, Item>{},
'monsters': <String, Monster>{},
'moves': <String, Move>{},
'races': <String, Race>{},
'spells': <String, Spell>{},
'tags': <String, Tag>{},
'CharacterClasses': <String, CharacterClass>{},
'Items': <String, Item>{},
'Monsters': <String, Monster>{},
'Moves': <String, Move>{},
'Races': <String, Race>{},
'Spells': <String, Spell>{},
'Tags': <String, Tag>{},
};
Map<String, CharacterClass> get characterClasses =>
getItem('characterClasses').cast<String, CharacterClass>();
Map<String, Item> get items => getItem('items').cast<String, Item>();
Map<String, Monster> get monsters => getItem('monsters').cast<String, Monster>();
Map<String, Move> get moves => getItem('moves').cast<String, Move>();
Map<String, Race> get races => getItem('races').cast<String, Race>();
Map<String, Spell> get spells => getItem('spells').cast<String, Spell>();
Map<String, Tag> get tags => getItem('tags').cast<String, Tag>();
getItem('CharacterClasses').cast<String, CharacterClass>();
Map<String, Item> get items => getItem('Items').cast<String, Item>();
Map<String, Monster> get monsters => getItem('Monsters').cast<String, Monster>();
Map<String, Move> get moves => getItem('Moves').cast<String, Move>();
Map<String, Race> get races => getItem('Races').cast<String, Race>();
Map<String, Spell> get spells => getItem('Spells').cast<String, Spell>();
Map<String, Tag> get tags => getItem('Tags').cast<String, Tag>();
Map<String, dynamic> toJson() => {
"CharacterClasses": characterClasses.map(((key, value) => MapEntry(key, value.toJson()))),
"Items": items.map(((key, value) => MapEntry(key, value.toJson()))),
"Monsters": monsters.map(((key, value) => MapEntry(key, value.toJson()))),
"Moves": moves.map(((key, value) => MapEntry(key, value.toJson()))),
"Races": races.map(((key, value) => MapEntry(key, value.toJson()))),
"Spells": spells.map(((key, value) => MapEntry(key, value.toJson()))),
"Tags": tags.map(((key, value) => MapEntry(key, value.toJson()))),
};
}

1
npm/dw_data.json Normal file

File diff suppressed because one or more lines are too long

3
npm/index.js Normal file
View File

@@ -0,0 +1,3 @@
const dungeonWorldData = require("./dw_data.json");
exports.default = dungeonWorldData;

0
web/public/CHANGELOG.md → npm_package/CHANGELOG.md Executable file → Normal file
View File

40
npm_package/README.md Normal file
View File

@@ -0,0 +1,40 @@
# Dungeon World Data
This NPM package contains data for Dungeon World, such as classes, moves, spells, equipment, etc.
Homebew classes included:
* Immolator
* Barbarian.
## How to use
Import the default to access the entire data structure.
```javascript
import dungeonWorld from 'dw_data';
// or
const dungeonWorld = require('dw-data');
```
## Available data
| Name | Type | Description |
| ---- | ---- | ----------- |
| basicMoves | `Array<Move>` | Dungeon World's basic moves, such as Hack & Slash, Defy Danger, etc. |
| specialMoves | `Array<Move>` | Dungeon World's special moves, such as Make Camp, Take Watch, etc. |
| classes | `Array<PlayerClass>` | All of Dungeon World's classes, plus some homebrews. See `PlayerClass` class for a full description of the usable properties. |
| equipment | `Array<Equipment>` | Dungeon World's main list of items. |
| spells | `Array<Spell>` | Dungeon World's main spellbook list. Each class can have its own spells list, see `PlayerClass` in the docs for more information. |
| monsters | `Array<Monster>` | Dungeon World's main monster list. |
| tags | `Array<Tag>` | List of all basic tags, along with descriptions. |
## Credits
Credits to ~vindexus who created https://www.npmjs.com/package/dungeonworld-data.
The original data is from there and this is a direct export from the [Dart](https://pub.dev/packages/dungeon_world_data) variation, with a modified data set.
## Contributing
1. Make your changes
1. Make a PR, explain what your changes do, what could break.
1. ???
1. Profit!

0
web/public/package.json → npm_package/package.json Executable file → Normal file
View File

5
npm_package/raw_data.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,62 +0,0 @@
import 'dart:io';
import 'package:dart_style/dart_style.dart';
import 'package:dungeon_world_data/_old/dw_data.dart';
import 'package:path/path.dart';
import 'package:pedantic/pedantic.dart';
import 'dart_parsers.dart';
void main() async {
print('Parsing');
var arrays = [
ParseDef<Tag>('Tag', dungeonWorld.tags, parseInfoTags),
ParseDef<Equipment>('Equipment', dungeonWorld.equipment, parseEquipment),
ParseDef<PlayerClass>(
'PlayerClass', dungeonWorld.classes, parsePlayerClass),
ParseDef<Monster>('Monster', dungeonWorld.monsters, parseMonster),
ParseDef<Move>('Move', dungeonWorld.basicMoves, parseMove,
arrayName: 'basicMovesList'),
ParseDef<Spell>('Spell', dungeonWorld.spells, parseSpell),
ParseDef<Move>('Move', dungeonWorld.specialMoves, parseMove,
arrayName: 'specialMovesList'),
];
var arraysImports = ({"part of '_dungeon_world_data.dart';"}..addAll(Set.from(
arrays.map((a) => "import 'package:dungeon_world_data/${a.file}';"))))
.join('\n');
// String arraysDeclares =
// arrays.map((a) => "List<${a.name}> ${a.arrayName} = [];").join('\n');
var arrayFills = arrays
.map((arr) => arr.list
.map((item) => '${arr.arrayName}.add(${arr.parse(item)});')
.join('\n'))
.join('\n');
print('Writing string');
var str = '''
$arraysImports
void initData() {
$arrayFills
}
''';
var file = File(join(Directory.current.path, 'output.dart'));
var beforeFormatFile =
File(join(Directory.current.path, 'before-format.dart'));
print('Formatting');
try {
var formatter = DartFormatter();
var formatted = formatter.format(str);
print('Writing to file');
unawaited(file.writeAsString(formatted));
if (beforeFormatFile.existsSync()) {
print('Deleting before-format file');
unawaited(beforeFormatFile.delete());
}
} on FormatterException catch (e) {
print(e);
print('Writing before-format file');
unawaited(beforeFormatFile.writeAsString(str));
} catch (e) {
print(e);
}
}

View File

@@ -1,176 +0,0 @@
import 'package:dungeon_world_data/_old/dw_data.dart';
String dumpClass<T extends DWEntity>(
String className, T obj, String Function(T) parser,
{bool includeKey = true}) {
return """
$className(${includeKey && obj.key != null ? "key: ${parseValue(obj.key)}, " : ""}${parser(obj).trim()})
""";
}
String Function(T) createClassParser<T extends DWEntity>(
String name, String Function(T obj) parser,
{bool includeKey = true}) {
return (T obj) => dumpClass(name, obj, parser, includeKey: includeKey);
}
String Function(T) createAnyParser<T extends DWEntity>(
String Function(T obj) parser) {
return (T obj) => parser(obj);
}
String parseArray<T>(
String className, Iterable<T> list, String Function(T) parser) {
return "[${list.map(parser).join(', ')}${list.length > 5 ? ',' : ''}]";
}
String parseTag(Tag tag) {
return '''Tag.fromJSON(${parseValue(tag.toString())})''';
}
String parseTagsArray(List<Tag> list) {
return parseArray('Tag', list, parseTag);
}
String parseDice(Dice dice) {
return '''Dice.parse(${parseValue(dice.toString())})''';
}
String parseValue<T>(T value) {
if (value is String) {
String val = value;
if (val.contains('\n')) {
val = val.replaceAll(RegExp('\r?\n'), '\\n');
}
if (val.contains("'")) {
return '"${val.replaceAll('"', "\\\"")}"';
} else if (val.contains('"')) {
return "'${val.replaceAll("'", "\\'")}'";
}
return "'$val'";
}
return '$value';
}
String Function(Tag) parseTags = createAnyParser<Tag>(parseTag);
String Function(Tag) parseInfoTags = createClassParser<Tag>(
'Tag',
(i) => '''
${parseValue(i.name)},
${parseValue(i.value)},
${parseValue(i.description)},
''',
includeKey: false,
);
String Function(Alignment) parseAlignment = createClassParser<Alignment>(
'Alignment',
(i) => '''
name: ${parseValue(i.name)},
description: ${parseValue(i.description)},
''');
String Function(Equipment) parseEquipment = createClassParser<Equipment>(
'Equipment',
(i) => '''
name: ${parseValue(i.name)},
pluralName: ${parseValue(i.pluralName)},
description: ${parseValue(i.description)},
tags: ${parseTagsArray(i.tags)},
''');
String Function(PlayerClass) parsePlayerClass = createClassParser<PlayerClass>(
'PlayerClass',
(i) => """
name: ${parseValue(i.name)},
description: ${parseValue(i.description)},
load: ${parseValue(i.load)},
baseHP: ${parseValue(i.baseHP)},
damage: ${parseDice(i.damage)},
names: ${i.names.map((r, s) => MapEntry(parseValue(r), parseArray('String', s, parseValue)))},
bonds: ${parseArray('String', i.bonds, parseValue)},
looks: ${parseArray('List<String>', i.looks, (dynamic s) => parseArray('String', s, parseValue))},
alignments: ${i.alignments.map((k, v) => MapEntry(parseValue(v.name), parseAlignment(v)))},
raceMoves: ${parseArray('Move', i.raceMoves, parseMove)},
startingMoves: ${parseArray('Move', i.startingMoves, parseMove)},
advancedMoves1: ${parseArray('Move', i.advancedMoves1, parseMove)},
advancedMoves2: ${parseArray('Move', i.advancedMoves2, parseMove)},
spells: ${parseArray('Spell', i.spells, parseSpell)},
gearChoices: ${parseArray('GearChoice', i.gearChoices, parseGearChoice)},
""");
String Function(Move) parseMove = createClassParser<Move>(
'Move',
(i) => """
name: ${parseValue(i.name)},
description: ${parseValue(i.description)},
explanation: ${parseValue(i.explanation)},
classes: ${parseArray('String', i.classes, parseValue)},
""");
String Function(Spell) parseSpell = createClassParser<Spell>(
'Spell',
(i) => '''
name: ${parseValue(i.name)},
description: ${parseValue(i.description)},
level: ${parseValue(i.level)},
tags: ${parseTagsArray(i.tags)},
''');
String Function(GearChoice) parseGearChoice = createClassParser<GearChoice>(
'GearChoice',
(i) => """
label: ${parseValue(i.label)},
gearOptions: ${parseArray('GearOption', i.gearOptions, parseGearOption)},
""");
String Function(GearOption) parseGearOption = createClassParser<GearOption>(
'GearOption',
(i) => '''
name: ${parseValue(i.name)},
tags: ${parseTagsArray(i.tags)},
''');
String Function(Monster) parseMonster = createClassParser<Monster>(
'Monster',
(i) => """
name: ${parseValue(i.name)},
description: ${parseValue(i.description)},
instinct: ${parseValue(i.instinct)},
tags: ${parseTagsArray(i.tags)},
moves: ${parseArray('String', i.moves, parseValue)},
""");
class ParseDef<T> {
final String name;
final Iterable<T> list;
final String Function(T) parser;
final String _file;
final String _arrayName;
String get file => _file;
String get arrayName => _arrayName;
ParseDef(this.name, this.list, this.parser, {String? file, String? arrayName})
: _file = file ?? _fileFromName(name),
_arrayName = arrayName ?? _arrayNameFromName(name);
String parse(dynamic obj) {
if (obj is T) {
return parser(obj);
}
return '// TYPE MISMATCH: $name';
}
static String _fileFromName(String name) =>
name
.splitMapJoin(RegExp('([A-Z])'),
onMatch: (match) => '_' + match.group(1)!.toLowerCase(),
onNonMatch: (i) => i)
.substring(1) +
'.dart';
static String _arrayNameFromName(String name) =>
'${name[0].toLowerCase()}${name.substring(1)}List';
}

View File

@@ -1,70 +0,0 @@
// import 'dart:convert';
import 'dart:io';
// import 'package:dungeon_world_data/_old/dw_data.dart';
import 'package:path/path.dart';
// import 'package:quiver/pattern.dart';
void main() async {
final _scr = Platform.script.path;
final _dir = join(dirname(_scr), '..', '..');
Directory.current = Directory(join(dirname(_scr), '..'));
print("_dir: $_dir, current: ${Directory.current}");
return;
// print('Node: Building output file');
// final before = {
// 'tags': dungeonWorld.tags.map((el) => el.toJSON()).toList(),
// 'basicMoves': dungeonWorld.basicMoves.map((el) => el.toJSON()).toList(),
// 'specialMoves': dungeonWorld.specialMoves.map((el) => el.toJSON()).toList(),
// 'classes': dungeonWorld.classes.map((el) => el.toJSON()).toList(),
// 'equipment': dungeonWorld.equipment.map((el) => el.toJSON()).toList(),
// 'monsters': dungeonWorld.monsters.map((el) => el.toJSON()).toList(),
// 'spells': dungeonWorld.spells.map((el) => el.toJSON()).toList(),
// };
// var rawDataJsonString = jsonEncode(before);
// // .replaceAll('"', '\\"')
// // .replaceAll("'", "\\'")
// // .replaceAll('\\n', '\\\\n');
// print('Wrapping JSON string...');
// rawDataJsonString = '''
// const _dw =
// $rawDataJsonString;
// _dw.default = _dw;
// module.exports = _dw;
// '''
// .replaceAll(' ' * 6, '');
// print('Clearing build directory...');
// final outputDir = Directory(join(_dir, 'npm_package'));
// final inputDir = Directory(join(_dir, 'web', 'public'));
// await outputDir.delete(recursive: true);
// await outputDir.create();
// final blacklist = <String>[];
// await for (final file in inputDir.list(recursive: true)) {
// final relativePath = relative(file.path, from: inputDir.path);
// final outputPath = join(outputDir.path, relativePath);
// print('Found file: ${file.path}, relative: $relativePath');
// final inPattern =
// blacklist.any((l) => Glob(join(inputDir.path, l)).hasMatch(file.path));
// if (!inPattern && (await file.stat()).type == FileSystemEntityType.file) {
// final contents = await File(file.path).readAsString();
// print('Writing to file: $outputPath');
// if (!await Directory(dirname(outputPath)).exists()) {
// await Directory(dirname(outputPath)).create(recursive: true);
// }
// await File(outputPath).writeAsString(contents);
// }
// }
// final rawDataOutputPath = join(outputDir.path, 'raw_data.js');
// print('Writing file $rawDataOutputPath...');
// await File(rawDataOutputPath).writeAsString(rawDataJsonString);
// print('Done.');
}

View File

@@ -1,343 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:dungeon_world_data/_old/dw_data.dart' as old;
import 'package:dungeon_world_data/_utils/uuid.dart';
import 'package:dungeon_world_data/dungeon_world_data.dart';
import 'package:dungeon_world_data/gear_option.dart';
import 'package:path/path.dart' as path;
final _jsonOut = path.join(
path.dirname(Platform.script.path),
'dumps',
'all.json',
);
Map<String, String> strFixMap = {
"": "-",
"": "-",
"": "'",
"": "\"",
"": "\"",
};
final json = <String, dynamic>{
'Moves': <Map<String, dynamic>>[],
'Races': <Map<String, dynamic>>[],
'Classes': <Map<String, dynamic>>[],
'Spells': <Map<String, dynamic>>[],
'Items': <Map<String, dynamic>>[],
'Monsters': <Map<String, dynamic>>[],
'Tags': <Map<String, dynamic>>[],
'Names': <String, Map<String, List<String>>>{},
};
final defaultTags = <Tag>[
// Tag(name: "language", value: "EN"),
// Tag(name: "source", value: "repo"),
];
final defaultMeta = {
'language': 'EN',
'createdBy': '__repo__',
};
main() async {
print("Starting...");
// Basic moves
print("Adding ${old.dungeonWorld.basicMoves.length} basic moves");
for (var move in old.dungeonWorld.basicMoves) {
json['Moves']!.add(moveMapper(move, MoveCategory.basic).toJson());
}
// Special Moves
print("Adding ${old.dungeonWorld.specialMoves.length} special moves");
for (var move in old.dungeonWorld.specialMoves) {
json['Moves']!.add(moveMapper(move, MoveCategory.special).toJson());
}
// Spells
print("Adding ${old.dungeonWorld.spells.length} spells");
for (var spell in old.dungeonWorld.spells) {
json['Spells']!.add(spellMapper(spell).toJson());
}
// Items
print("Adding ${old.dungeonWorld.equipment.length} items");
for (var equip in old.dungeonWorld.equipment) {
json['Items']!.add(equipMapper(equip).toJson());
}
// Monsters
print("Adding ${old.dungeonWorld.monsters.length} monsters");
for (var mon in old.dungeonWorld.monsters) {
json['Monsters']!.add(monsterMapper(mon).toJson());
}
// Tags
print("Adding ${old.dungeonWorld.tags.length} tags");
for (var tag in old.dungeonWorld.tags) {
json['Tags']!.add(tagMapper(tag).toJson());
}
for (var cls in old.dungeonWorld.classes) {
// Starting moves
print("Adding ${cls.startingMoves.length} starting moves");
for (var move in cls.startingMoves) {
json['Moves']!.add(moveMapper(move, MoveCategory.starting).toJson());
}
// Advanced Moves 1
print("Adding ${cls.advancedMoves1.length} advanced1 moves");
for (var move in cls.advancedMoves1) {
json['Moves']!.add(moveMapper(move, MoveCategory.advanced1).toJson());
}
// Advanced Moves 1
print("Adding ${cls.advancedMoves2.length} advanced2 moves");
for (var move in cls.advancedMoves2) {
json['Moves']!.add(moveMapper(move, MoveCategory.advanced2).toJson());
}
// Races
print("Adding ${cls.raceMoves.length} races");
for (var race in cls.raceMoves) {
json['Races']!.add(raceMapper(race, cls.key ?? makeKey(cls.name)).toJson());
}
// Classes
json['Classes']!.add(classMapper(cls).toJson());
print("Adding ${cls.names.values.fold<int>(0, (t, c) => t + c.length)} ${cls.key} names");
final Map<String, List<String>> names = (json['Names'][cls.key] ??= <String, List<String>>{});
names.addAll(cls.names.map((key, value) => MapEntry(key, value.map(fix).toList())));
}
print("Total ${json['Classes']!.length} classes");
//
print("Running post-fixes...");
//
//
await File(_jsonOut).writeAsString(jsonEncode(json));
for (final e in json.entries) {
await File(path.join(path.dirname(_jsonOut), e.key.toLowerCase() + ".json"))
.writeAsString(jsonEncode(e.value));
}
print("Done");
}
String fix(String? str) {
if (str == null || str.trim().isEmpty) {
return "";
}
for (var _fix in strFixMap.entries) {
str = str!.replaceAll(_fix.key, _fix.value);
}
return str!.trim();
}
String makeKey(String str) {
return fix(str)
.replaceAll(RegExp('["\']'), "")
.replaceAll(RegExp(r'[^a-z]+', caseSensitive: false), '_')
.replaceFirst(RegExp(r'^_'), '')
.replaceFirst(RegExp(r'_$'), '')
.toLowerCase();
}
Set<Dice> guessDice(String str) {
var basicRollPattern = RegExp(r'\broll([+-][a-z]+)\b', caseSensitive: false);
var dicePattern = RegExp(r'\b\dd\d\b', caseSensitive: false);
var found = <Dice>{};
var basicRollMatches = basicRollPattern.allMatches(str);
for (var match in basicRollMatches) {
found.add(Dice.fromJson('2d6' + match.group(1)!.toUpperCase()));
}
var diceMatches = dicePattern.allMatches(str);
for (var match in diceMatches) {
found.add(Dice.fromJson(match.input.substring(match.start, match.end)));
}
return found;
}
Move moveMapper(old.Move move, MoveCategory category) => Move(
category: category,
classKeys: move.classes,
description: fix(move.description),
dice: guessDice(fix(move.description)).toList(),
explanation: fix(move.explanation ?? ""),
key: makeKey(move.name),
meta: defaultMeta,
name: fix(move.name),
tags: defaultTags,
);
Race raceMapper(old.Move move, String classKey) => Race(
classKeys: [classKey],
description: fix(move.description),
explanation: fix(move.explanation ?? ""),
key: makeKey(move.name),
meta: defaultMeta,
name: fix(move.name),
tags: defaultTags,
);
Spell spellMapper(old.Spell spell) => Spell(
classKeys: spell.classKeys,
description: fix(spell.description),
explanation: "",
key: makeKey(spell.name),
meta: defaultMeta,
name: fix(spell.name),
dice: guessDice(spell.description).toList(),
tags: [...defaultTags, ...spell.tags.map((t) => tagMapper(t))],
level: spell.level,
);
Tag tagMapper(old.Tag t) => Tag.fromJson({
"name": t.name[0].toUpperCase() + t.name.substring(1),
"value": t.value,
"description": fix(t.description)
});
Item equipMapper(old.Equipment equip) => Item(
key: makeKey(equip.name),
meta: defaultMeta,
name: fix(equip.name),
description: fix(equip.description),
tags: equip.tags.map((t) => tagMapper(t)).toList(),
);
Monster monsterMapper(old.Monster move) => Monster(
instinct: fix(move.instinct),
description: fix(move.description),
key: makeKey(move.name),
meta: defaultMeta,
name: fix(move.name),
tags: defaultTags,
moves: move.moves.map(fix).toList(),
);
CharacterClass classMapper(old.PlayerClass cls) => CharacterClass(
alignments: AlignmentValues(
chaotic: fix(cls.alignments['chaotic']?.description ?? ""),
neutral: fix(cls.alignments['neutral']?.description ?? ""),
evil: fix(cls.alignments['evil']?.description ?? ""),
good: fix(cls.alignments['good']?.description ?? ""),
lawful: fix(cls.alignments['lawful']?.description ?? ""),
),
bonds: cls.bonds.map(fix).toList(),
damageDice: Dice.fromJson(cls.damage.toString()),
description: fix(cls.description),
gearChoices: cls.gearChoices.map(
(c) {
return GearChoice(
key: c.key ?? uuid(),
description: fix(c.label),
preselect: c.preselect,
maxSelections: c.maxSelections,
selections: c.gearOptions.map(
(o) {
Map<String, dynamic>? found;
final generatedKey = makeKey(o.name);
final nameWithoutNum = o.name.substring(o.name.indexOf(RegExp(r'[^0-9 ]'))).trim();
final numFromName =
double.tryParse(o.name.substring(0, o.name.indexOf(RegExp(r'[^0-9 ]'))).trim());
try {
found = tryFindItem(o.key ?? '', generatedKey);
} catch (e) {
found = null;
}
final nameStartsWithNum = o.name.startsWith(RegExp(r'[0-9]+'));
final item = found != null
? Item.fromJson(found)
: Item(
key: makeKey(o.name),
meta: defaultMeta,
name: fix(o.name),
description: fix(o.name),
tags: [...defaultTags, ...o.tags.map(tagMapper)],
);
final isCoinsItem = makeKey(o.name) == 'coins';
final splitName = o.name.split(' and ');
var foundSplit = false;
final splitItems = splitName.length > 1
? splitName.map((n) {
Map<String, dynamic>? found;
try {
found = tryFindItem(n, makeKey(n));
foundSplit = true;
} catch (e) {
found = null;
}
final nameWithoutNum = n.substring(n.indexOf(RegExp(r'[^0-9 ]'))).trim();
final nameStartsWithNum = n.startsWith(RegExp(r'[0-9]+'));
return GearOption(
key: makeKey(nameStartsWithNum ? nameWithoutNum : n),
amount: nameStartsWithNum
? double.tryParse(
RegExp(r'[0-9]+').firstMatch(n)?.group(0) ?? "1.0") ??
1.0
: 1.0,
item: found != null ? Item.fromJson(found) : item,
);
}).toList()
: [
GearOption(
key: makeKey(nameStartsWithNum ? nameWithoutNum : o.name),
amount: nameStartsWithNum
? double.tryParse(
RegExp(r'[0-9]+').firstMatch(o.name)?.group(0) ?? "1.0") ??
1.0
: 1.0,
item: item,
),
];
var incomplete = found == null && !isCoinsItem && !foundSplit;
return GearSelection(
key: generatedKey,
description: fix(o.name + (incomplete ? ' (TODO: INCOMPLETE)' : '')),
options: isCoinsItem ? [] : splitItems,
coins: isCoinsItem ? numFromName ?? 0 : 0,
);
},
).toList(),
);
},
).toList(),
hp: cls.baseHP.toInt(),
key: makeKey(cls.name),
load: cls.load.toInt(),
name: fix(cls.name),
meta: defaultMeta,
);
final incompleteGearMap = {
'your_fathers_mandolin_repaired': 'fathers_mandolin',
'a_fine_lute_a_gift_from_a_noble': 'fine_lute',
'the_pipes_with_which_you_courted_your_first_love': 'memorable_pipes',
'a_stolen_horn': 'stolen_horn',
'a_fiddle_never_before_played': 'unplayed_fiddle',
'a_songbook_in_a_forgotten_tongue': 'forgotten_songbook',
};
Map<String, dynamic> tryFindItem(String key, String generatedKey) {
final keyStartsWithNum = key.startsWith(RegExp(r'[0-9]+'));
final keyWithoutNum =
keyStartsWithNum ? key.substring(key.indexOf(RegExp(r'[^0-9 ]'))).trim() : key.trim();
final keyWithoutNumSingular =
keyWithoutNum.isEmpty ? '' : keyWithoutNum.substring(0, keyWithoutNum.length - 1);
return json['Items']!.firstWhere(
(el) =>
el['key'].toLowerCase() == incompleteGearMap[key] ||
el['key'].toLowerCase() == key.toLowerCase() ||
el['key'].toLowerCase() == generatedKey.toLowerCase() ||
el['key'].toLowerCase() == keyWithoutNum.toLowerCase() ||
el['key'].toLowerCase() == keyWithoutNumSingular.toLowerCase(),
);
}

View File

@@ -0,0 +1,31 @@
import 'dart:convert';
import 'dart:io';
import 'package:dungeon_world_data/dungeon_world_data.dart';
import 'package:path/path.dart' as path;
final _jsonOut = path.join(
path.dirname(Platform.script.path),
'dumps',
'all.json',
);
final _jsonOutCopy = path.join(
path.dirname(Platform.script.path),
'..',
'..',
'web',
'dw_data.json',
);
main() async {
final contents = dungeonWorldData.toJson();
await File(_jsonOut).writeAsString(json.encode(contents));
await File(_jsonOutCopy).writeAsString(json.encode(contents));
for (final e in contents.entries) {
final filePath = path.join(path.dirname(_jsonOut), e.key + ".json");
print("Writing $filePath...");
await File(filePath).writeAsString(json.encode(e.value));
}
print("Done");
}

View File

@@ -25,6 +25,7 @@ final clsNameMap = {
'Monsters': 'Monster',
'Tags': 'Tag',
};
final clsImportMap = {
'Moves': 'move',
'Races': 'race',

View File

@@ -1,75 +0,0 @@
import 'package:dungeon_world_data/_old/dw_data.dart';
import 'package:test/test.dart';
void main() {
group('Dice', () {
test('Sides and amount', () {
var expected = Dice(6, 2);
expect(expected.sides, equals(6));
expect(expected.amount, equals(2));
});
test('String representation', () {
var dice1 = Dice(10, 4);
var dice2 = Dice(6, 2, 3);
var dice3 = Dice(20, 2, -4);
expect(dice1.toString(), equals('4d10'));
expect(dice2.toString(), equals('2d6+3'));
expect(dice3.toString(), equals('2d20-4'));
});
test('Multiplication', () {
num amt = 10;
var dice = Dice.d12 * amt;
expect(dice.amount, amt);
});
test('Equality', () {
var compare = {
Dice(4): Dice.d4,
Dice(6): Dice.d6,
Dice(8): Dice.d8,
Dice(10): Dice.d10,
Dice(12): Dice.d12,
Dice(20): Dice.d20,
};
for (var d in compare.keys) {
expect(d, equals(compare[d]));
expect(compare.values.where((el) => el == d).length, equals(1));
}
});
test('Roll', () {
var d1 = Dice.d6;
var d2 = Dice.d12 * 2;
var d3 = Dice.d8 * 3;
var roll1 = d1.getRoll();
var roll2 = d2.getRoll();
var roll3 = d3.getRoll();
expect(roll1.results[0], greaterThanOrEqualTo(1));
expect(roll1.results[0], lessThanOrEqualTo(6));
expect(roll2.results.length, equals(d2.amount));
expect(roll2.results[0], greaterThanOrEqualTo(1));
expect(roll2.results[0], lessThanOrEqualTo(12));
expect(roll2.results[1], greaterThanOrEqualTo(1));
expect(roll2.results[1], lessThanOrEqualTo(12));
expect(roll3.results.length, equals(d3.amount));
expect(roll3.results[0], greaterThanOrEqualTo(1));
expect(roll3.results[0], lessThanOrEqualTo(8));
expect(roll3.results[1], greaterThanOrEqualTo(1));
expect(roll3.results[1], lessThanOrEqualTo(8));
expect(roll3.results[2], greaterThanOrEqualTo(1));
expect(roll3.results[2], lessThanOrEqualTo(8));
});
test('Roll', () {
var dice = Dice.d6;
var roll = dice.getRoll();
expect(roll.results[0], greaterThanOrEqualTo(1));
expect(roll.results[0], lessThanOrEqualTo(6));
});
});
}

View File

@@ -1,41 +0,0 @@
import 'package:test/test.dart';
import 'package:dungeon_world_data/_old/dw_data.dart';
class Test1 extends DWEntity {
final int value;
Test1({
String? key,
required this.value,
}) : super(key: key);
@override
DWEntity copy() => Test1.fromJSON(toJSON());
@override
dynamic toJSON() => 1;
factory Test1.fromJSON(int value) => Test1(value: value);
}
void main() {
group('DWEntity', () {
test('hashCode', () {
var t1 = Test1(key: '1', value: 1);
var t2 = Test1(key: '1', value: 1);
expect(t1, equals(t2));
});
group('Helpers', () {
test('getByKey', () {
var classes = dungeonWorld.classes;
var wizard = dungeonWorld.classes.firstWhere((k) => k.key == 'wizard');
var found = DWEntity.getByKey<PlayerClass>('wizard', classes);
expect(wizard, equals(found));
var notFound = DWEntity.getByKey<PlayerClass>("doesn't exist", classes);
expect(notFound, equals(null));
});
});
});
}

View File

@@ -1,12 +0,0 @@
import 'package:test/test.dart';
import 'package:dungeon_world_data/_old/dw_data.dart';
void main() {
group('Equipment', () {
test('Key', () {
var equipment =
DWEntity.getByKey('tricksy_rope', dungeonWorld.equipment)!;
expect(equipment.key, equals('tricksy_rope'));
});
});
}

View File

@@ -1,11 +0,0 @@
import 'package:test/test.dart';
import 'package:dungeon_world_data/_old/dw_data.dart';
void main() {
group('PlayerClass', () {
test('Key', () {
var paladin = DWEntity.getByKey('paladin', dungeonWorld.classes)!;
expect(paladin.key, equals('paladin'));
});
});
}

View File

@@ -1,25 +0,0 @@
import 'package:test/test.dart';
import 'package:dungeon_world_data/_old/dw_data.dart';
void main() {
group('Spells', () {
test('Key', () {
var equipment = DWEntity.getByKey('plague', dungeonWorld.spells)!;
expect(equipment.key, equals('plague'));
});
test('toJSON', () {
var equipment = DWEntity.getByKey('plague', dungeonWorld.spells)!;
expect(equipment.toJSON()['tags'], contains('ongoing'));
var json = equipment.toJSON();
expect(json['key'], equals('plague'));
expect(json['name'], equals('Plague'));
expect(
json['description'],
equals(
'Name a city, town, encampment, or other place where people live. As long as this spell is active that place is beset by a plague appropriate to your deitys domains (locusts, death of the first born, etc.) While this spell is ongoing you take -1 to cast a spell.'));
expect(json['level'], equals('9'));
expect(json['tags'], contains('ongoing'));
});
});
}

View File

@@ -1,52 +0,0 @@
import 'package:dungeon_world_data/_old/dw_data.dart';
import 'package:test/test.dart';
void main() {
group('Tags', () {
test('key', () {
var tag = Tag.fromJSON('two-handed');
expect(tag.hasValue, equals(false));
expect(tag.name, equals('two-handed'));
expect(tag.key, equals('two_handed'));
});
test('Parse string', () {
var tag1 = Tag.fromJSON('close');
var tag2 = Tag.fromJSON('1 coins');
var tag3 = Tag.fromJSON('{weight:1}');
expect(tag1.hasValue, equals(false));
expect(tag1.name, equals('close'));
expect(tag2.hasValue, equals(true));
expect(tag2.value, equals(1));
expect(tag2.name, equals('coins'));
expect(tag3.hasValue, equals(true));
expect(tag3.value, equals(1));
expect(tag3.name, equals('weight'));
});
test('Parse map', () {
var tag = Tag.fromJSON({'weight': 1});
expect(tag.name, equals('weight'));
expect(tag.hasValue, equals(true));
expect(tag.value, equals(1));
});
test('toString with value', () {
var tag = Tag.fromJSON({'coins': 10});
expect(tag.toString(), equals('coins: 10'));
});
test('toString without value', () {
var tag = Tag.fromJSON('near');
expect(tag.toString(), equals('near'));
});
test('toJSON with value', () {
var tag = Tag.fromJSON({'weight': 10});
var json = tag.toJSON();
expect(json, equals({'weight': 10}));
});
test('toJSON without value', () {
var tag = Tag.fromJSON('two-handed');
expect(tag.toJSON(), equals('two-handed'));
});
});
}

6
web/CHANGELOG.md Executable file
View File

@@ -0,0 +1,6 @@
# CHANGELOG
## 3.0.0
- [BREAKING]: Complete overhaul of the data, and all types.
- Version number will stay up to date with the Dart package version from now on.

126
web/index.d.ts vendored Normal file
View File

@@ -0,0 +1,126 @@
// Generated by https://quicktype.io
export interface DungeonWorldRepository {
CharacterClasses: CharacterClasses
Items: Items
Monsters: Monsters
Moves: Moves
Races: Races
Spells: Moves
Tags: Tags
}
export type Races = Record<string, Race>
export type Items = Record<string, Item>
export type Monsters = Record<string, Monster>
export type Moves = Record<string, Move>
export type Spells = Record<string, Move>
export type Tags = Record<string, Tag>
export type CharacterClasses = Record<string, CharacterClass>
export interface CharacterClass {
_meta: Meta
name: string
key: ClassKey
description: string
damageDice: string
load: number
hp: number
alignments: Alignments
bonds: string[]
gearChoices: GearChoice[]
}
export interface Meta {
language: Language
createdBy: CreatedBy
}
export enum CreatedBy {
Repo = "__repo__",
}
export enum Language {
En = "EN",
}
export interface Alignments {
good: string
evil: string
lawful: string
neutral: string
chaotic: string
}
export interface GearChoice {
key: string
description: string
selections: GearSelection[]
preselect: number[]
maxSelections: number | null
}
export interface GearSelection {
key: string
description: string
options: GearOption[]
coins: number
}
export interface GearOption {
key: string
item: Item
amount: number
}
export interface Item {
_meta: Meta
key: string
name: string
description: string
tags: Tag[]
}
export interface Tag {
name: string
value: string | number | null
description: string
}
export interface Monster {
_meta: Meta
key: string
name: string
description: string
instinct: string
tags: Tag[]
moves: string[]
}
export interface Move {
_meta: Meta
key: string
name: string
description: string
explanation: string
dice?: string[]
classKeys: string[]
tags: Tag[]
category?: MoveCategory
level?: string
}
export enum MoveCategory {
Advanced1 = "advanced1",
Advanced2 = "advanced2",
Basic = "basic",
Special = "special",
Starting = "starting",
}
export type Race = Move

12
web/package.json Executable file
View File

@@ -0,0 +1,12 @@
{
"name": "dw-data",
"version": "3.0.0",
"main": "index.js",
"repository": "https://github.com/chenasraf/dungeon_world_data",
"author": "Chen Asraf <contact@casraf.blog>",
"license": "MIT",
"types": "./index.d.ts",
"scripts": {
"build": "cd ../.. && dart scripts/parsers/dart_to_json.dart"
}
}