mirror of
https://github.com/DungeonPaper/dungeon_world_data.git
synced 2026-05-17 18:08:01 +00:00
remove all old files, update npm build
This commit is contained in:
25
.gitignore
vendored
25
.gitignore
vendored
@@ -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
6
.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@@ -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",
|
||||
|
||||
@@ -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
BIN
doc/.DS_Store
vendored
Executable file
Binary file not shown.
@@ -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());
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
@@ -1 +0,0 @@
|
||||
export 'dw_data.dart' show DWEntity;
|
||||
@@ -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';
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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']));
|
||||
});
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
@@ -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!;
|
||||
}
|
||||
@@ -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?” they’ll 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 7–9, 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 you’ve lost; an emotion, a memory or some innate piece of your being. You may not use this move again until you’ve 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, they’ll 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, don’t. 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, they’re 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 you’ve 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 person’s 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 you’re 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',
|
||||
'Vercin’geto',
|
||||
'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 7–9 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 7–9 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:
|
||||
'You’ve traveled the wide world over. When you arrive someplace ask the GM about any important traditions, rituals, and so on, they’ll 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 7–9 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 7–9.',
|
||||
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 7–9 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 you’ve traveled or where you’re 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}')],
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
));
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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
1
npm/dw_data.json
Normal file
File diff suppressed because one or more lines are too long
3
npm/index.js
Normal file
3
npm/index.js
Normal 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
0
web/public/CHANGELOG.md → npm_package/CHANGELOG.md
Executable file → Normal file
40
npm_package/README.md
Normal file
40
npm_package/README.md
Normal 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
0
web/public/package.json → npm_package/package.json
Executable file → Normal file
5
npm_package/raw_data.js
Normal file
5
npm_package/raw_data.js
Normal file
File diff suppressed because one or more lines are too long
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
@@ -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.');
|
||||
}
|
||||
@@ -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(),
|
||||
);
|
||||
}
|
||||
31
scripts/parsers/dart_to_json.dart
Normal file
31
scripts/parsers/dart_to_json.dart
Normal 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");
|
||||
}
|
||||
@@ -25,6 +25,7 @@ final clsNameMap = {
|
||||
'Monsters': 'Monster',
|
||||
'Tags': 'Tag',
|
||||
};
|
||||
|
||||
final clsImportMap = {
|
||||
'Moves': 'move',
|
||||
'Races': 'race',
|
||||
@@ -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));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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'));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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'));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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 deity’s 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'));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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
6
web/CHANGELOG.md
Executable 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
126
web/index.d.ts
vendored
Normal 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
12
web/package.json
Executable 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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user