Skip to content

Commit

Permalink
switch to webpack
Browse files Browse the repository at this point in the history
  • Loading branch information
valentine195 committed Jun 30, 2021
1 parent b5f2c63 commit d78d20d
Show file tree
Hide file tree
Showing 20 changed files with 1,664 additions and 614 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package-lock.json
# build
main.js
*.js.map
dist

# obsidian
data.json
Expand Down
6 changes: 0 additions & 6 deletions .vscode/settings.json

This file was deleted.

17 changes: 14 additions & 3 deletions @types/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import type { Creature } from "../src/utils/creature";

export interface InputValidate {
input: HTMLInputElement;
validate: (i: HTMLInputElement) => boolean;
}

export interface InitiativeTrackerData {
players: Creature[];
players: HomebrewCreature[];
homebrew: HomebrewCreature[];
version: string;
sync: boolean;
}
Expand Down Expand Up @@ -40,6 +39,18 @@ export interface SRDMonster {
source?: string;
}

export interface HomebrewCreature {
name?: string;
hp?: number;
ac?: number;
stats?: number[];
source?: string;
cr?: number | string;
modifier?: number;
note?: string;
player?: boolean;
}

export type ability =
| "strength"
| "dexterity"
Expand Down
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,45 @@ A new encounter (just player characters) can be started by clicking `New Encount

Players may be added in settings. Players created in this way will be automatically added to encounters.

### Players from Notes

When adding a new player, there is the option to add a player based on a note.

Currently, this functionality will only read the frontmatter of a note to pull in the relevant fields - hp, ac, and initiative modifier.

Frontmatter should be formatted like this:

```
---
hp: 23
ac: 17
modifier: 2
---
```

In the future, this will be used to display more information about the player during combat, and also will update the player's information when the frontmatter is changed.

## Homebrew Content

If the [5e Statblocks](https://github.com/valentine195/obsidian-5e-statblocks) plugin is installed, the homebrew creatures saved to that plugin can be used in this plugin by enabling the sync in settings.

# Roadmap

This is a list of features that are planned for the plugin. Some of these may or may not be developed.

- Wikilink Player Characters
- Automatically pull HP/AC from PC Note Wikilinked in settings
- Wikilink Creatures
- stat blocks on hover
- Wikilink Tags (e.g., condition tag to display condition rules, spell tags for spell effects, etc.)
- Creature stat blocks in separate moveable tab of sidebar
- auto-update displayed stat block based on active creature in the encounter
- An option to build an encounter in a Note and send it to Initiative Tracker on demand (e.g., in an Obsidian Note, create some code block indicating 3 Goblins and 1 Bugbear in an area; press a button, add the 3 Goblins and Bugbear to the Initiative tracker)
- Encounter difficulty/XP tracker for creatures with CR
- For the currently active creature, display any actions that would need a dice roll and an integrated dice roller with the specific dice and bonuses for the action already pre-loaded (e.g., for a Bugbear, display "Morningstar" and a to-hit dice with 1d20+4, as well as a damage dice of 2d8+2; Also display for the Javelin action)
- Support for multiple parties
- Integrated dice roller

# Installation

<!-- ## From within Obsidian
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "obsidian-initiative-tracker",
"name": "Obsidian Initiative Tracker",
"version": "0.0.1",
"version": "0.0.2",
"minAppVersion": "0.12.5",
"description": "TTRPG Initiative Tracker for Obsidian.md",
"author": "Jeremy Valentine",
Expand Down
19 changes: 15 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
{
"name": "obsidian-initiative-tracker",
"version": "0.0.1",
"version": "0.0.2",
"description": "TTRPG Initiative Tracker for Obsidian.md",
"main": "main.js",
"scripts": {
"dev": "rollup --config rollup.config-dev.js -w --environment BUILD:dev",
"build": "rollup --config rollup.config.js --environment BUILD:production"
"dev-rollup": "rollup --config rollup.config-dev.js -w --environment BUILD:dev",
"build-rollup": "rollup --config rollup.config.js --environment BUILD:production",
"build": "webpack",
"dev": "webpack --config webpack.dev.js -w"
},
"keywords": [],
"author": "",
Expand All @@ -17,12 +19,21 @@
"@rollup/plugin-typescript": "^8.2.1",
"@tsconfig/svelte": "^2.0.1",
"@types/node": "^14.14.37",
"babel-loader": "^8.2.2",
"copy-webpack-plugin": "^9.0.1",
"css-loader": "^5.2.6",
"obsidian": "^0.12.5",
"rollup": "^2.32.1",
"rollup-plugin-css-only": "^3.1.0",
"rollup-plugin-svelte": "^7.1.0",
"svelte-loader": "^3.1.2",
"svelte-preprocess": "^4.7.3",
"title-case": "^3.0.3",
"ts-loader": "^9.2.3",
"tslib": "^2.3.0",
"typescript": "^4.2.4"
"typescript": "^4.2.4",
"webpack": "^5.41.1",
"webpack-cli": "^4.7.2",
"webpack-node-externals": "^3.0.0"
}
}
199 changes: 199 additions & 0 deletions src/import/DnDAppFilesImport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/* import { Spell } from "../../common/Spell";
import { StatBlock } from "../../common/StatBlock"; */
/* import { SpellImporter } from "./SpellImporter";
import { StatBlockImporter } from "./StatBlockImporter"; */

import type { HomebrewCreature, Spell, Trait } from "../../@types";
import { titleCase } from "title-case";

export const ImportEntitiesFromXml = async (
...files: File[]
): Promise<HomebrewCreature[]> => {
return new Promise((resolve) => {
for (let xmlFile of files) {
const reader = new FileReader();

reader.onload = async (event: any) => {
const xml: string = event.target.result;

const dom = new DOMParser().parseFromString(
xml,
"application/xml"
);
const monsters = dom.getElementsByTagName("monster");
const importedMonsters: HomebrewCreature[] = [];
if (!monsters.length) return;
for (let monster of Array.from(monsters)) {
const importedMonster: HomebrewCreature = {
name: getParameter(monster, "name"),
ac: getAC(monster),
hp: Number(getHP(monster, "hp")),
stats: [
Number(getParameter(monster, "str")),
Number(getParameter(monster, "dex")),
Number(getParameter(monster, "con")),
Number(getParameter(monster, "int")),
Number(getParameter(monster, "wis")),
Number(getParameter(monster, "cha"))
],
cr: getParameter(monster, "cr"),
source: getSource(monster)
};

importedMonsters.push(importedMonster);
}
resolve(importedMonsters);
};

reader.readAsText(xmlFile);
}
});
};

function getParameter(monster: Element, tag: string): string {
const element = monster.getElementsByTagName(tag);
if (element && element.length) return element[0].textContent;
}
function getTraits(
monster: Element,
arg1: "trait" | "action" | "legendary" | "reaction"
): Trait[] {
if (!monster.getElementsByTagName(arg1)) return [];
const traits = monster.getElementsByTagName(arg1);
const traitList = [];
for (let trait of Array.from(traits)) {
const name = trait.getElementsByTagName("name");
if (!name) continue;
if (name[0].textContent.includes("Spellcasting")) continue;
const text = [];
const traitTexts = trait.getElementsByTagName("text");
for (let index in traitTexts) {
text.push(traitTexts[index].textContent);
}
traitList.push({
name: name[0].textContent,
desc: text.join(" ")
});
}
return traitList;
}

function getSpells(monster: Element): Spell[] {
if (!monster.getElementsByTagName("trait")) return [];
const traits = Array.from(monster.getElementsByTagName("trait"));
const spellcasting = traits.find((x) =>
x.getElementsByTagName("name")[0]?.textContent.includes("Spellcasting")
);
if (!spellcasting) return [];
return Array.from(spellcasting.getElementsByTagName("text"))
.map((x) => x.textContent.replace(/(&#8226;|)/u, "").trim())
.filter((x) => x.length);
}

function getSkillSaves(monster: Element): { [key: string]: number }[] {
if (!monster.getElementsByTagName("skill")) return [];
let saves = monster
.getElementsByTagName("skill")[0]
.textContent.split(", ");
let ret: { [key: string]: number }[] = [];
saves.forEach((save) => {
const skill = save.split(/\s[\+\-]/);
ret.push({ [skill[0]]: Number(skill[1]) });
});
return ret;
}

const SAVES: Record<
string,
| "strength"
| "dexterity"
| "constitution"
| "intelligence"
| "wisdom"
| "charisma"
> = {
Str: "strength",
Dex: "dexterity",
Con: "constitution",
Int: "intelligence",
Wis: "wisdom",
Cha: "charisma"
};

function getSaves(monster: Element): {
strength?: number;
dexterity?: number;
constitution?: number;
intelligence?: number;
wisdom?: number;
charisma?: number;
}[] {
if (!monster.getElementsByTagName("save")) return [];
let saves = monster.getElementsByTagName("save")[0].textContent.split(", ");
let ret: {
strength?: number;
dexterity?: number;
constitution?: number;
intelligence?: number;
wisdom?: number;
charisma?: number;
}[] = [];
saves.forEach((save) => {
const stat = save.split(/\s[\+\-]/);
ret.push({ [SAVES[stat[0]]]: Number(stat[1]) });
});
return ret;
}

function getHP(monster: Element, arg1: "hp" | "hit_dice"): string {
if (!monster.getElementsByTagName("hp")) return "";
let [, hp, hit_dice] = monster
.getElementsByTagName("hp")[0]
.textContent.match(/(\d+) \(([\s\S]+)\)/);
return { hp: hp, hit_dice: hit_dice }[arg1];
}
const SIZES: { [key: string]: string } = {
T: "tiny",
S: "small",
M: "medium",
L: "large",
H: "huge",
G: "gargantuan"
};
function getSize(monster: Element): string {
if (monster.getElementsByTagName("size")) {
return SIZES[monster.getElementsByTagName("size")[0].textContent] ?? "";
}
return "";
}

function getAC(monster: Element): number {
if (monster.getElementsByTagName("ac")) {
const [, ac] = monster
.getElementsByTagName("ac")[0]
?.textContent.match(/(\d+)/);
return Number(ac);
}
return 0;
}
function getSource(monster: Element): string {
let source = "Unknown";
const description = monster.getElementsByTagName("description");
if (description && description.length) {
const searchString = "Source: ";
const sourcePos = description[0].textContent.lastIndexOf(searchString);
const sources = description[0].textContent
.substr(sourcePos + searchString.length)
.split(/, ?/);
source = sources[0];
} else {
const types = monster.getElementsByTagName("type");
if (types && types.length) {
let type = types[0].textContent.split(/, ?/);
source = titleCase(
type.length > 1 ? type[type.length - 1] : source
);
}
}
return source;
}
Loading

0 comments on commit d78d20d

Please sign in to comment.