Automate project with gulp and transition to TypeScript.

Expand character data and sheet.
This commit is contained in:
mstein
2022-06-06 01:40:24 +02:00
parent 6a7d402395
commit e952dcb67a
62 changed files with 3271 additions and 410 deletions
+10
View File
@@ -0,0 +1,10 @@
import assert from "assert"
export default {
name: "midgard5",
isModule: false, // If you are developing a system rather than a module, change this to false
}
// Pop some fairly universal types that we might use
export type Pair<T> = [string, T];
+15
View File
@@ -0,0 +1,15 @@
import Globals from "./Globals"
const preloadTemplates = async (): Promise<Handlebars.TemplateDelegate<any>[]> => {
const rootPath = `${Globals.isModule ? "modules" : "systems"}/${Globals.name}/templates/`
// Place relative paths in array below, e.g.:
// const templates = [ rootPath + "actor/actor-sheet.hbs" ]
// This would map to our local folder of /Assets/Templates/Actor/actor-sheet.hbs
const templates: Array<string> = [
"sheets/character/base_values.hbs",
"sheets/character/skills.hbs"
]
return loadTemplates(templates.map(s => rootPath + s))
}
export default preloadTemplates
+37
View File
@@ -0,0 +1,37 @@
import Logger from "./utils/Logger"
import M5ItemSheet from "./module/sheets/M5ItemSheet"
import M5CharacterSheet from "./module/sheets/M5CharacterSheet"
import preloadTemplates from "./PreloadTemplates"
import M5Character from "./module/actors/M5Character"
Hooks.once("init", async () => {
Logger.log("M5 | Initialisierung Midgard 5")
Handlebars.registerHelper("localizeMidgard", (str: string) => {
const template = Handlebars.compile("{{localize value}}")
return template({
value: "midgard5." + str
})
})
// Default Sheet für Items definieren und das Standardsheet deaktivieren
Items.unregisterSheet("core", ItemSheet)
Items.registerSheet("midgard5", M5ItemSheet, { makeDefault: true })
// Default Sheet für Actors definieren und das Standardsheet deaktivieren
Actors.unregisterSheet("core", ActorSheet)
Actors.registerSheet("midgard5", M5CharacterSheet, { makeDefault: true })
CONFIG.Actor.documentClass = M5Character
//RegisterSettings();
await preloadTemplates()
})
Hooks.once("setup", () => {
Logger.log("Template module is being setup.")
})
Hooks.once("ready", () => {
Logger.Ok("Template module is now ready.")
})
+188
View File
@@ -0,0 +1,188 @@
export interface M5Skill {
label: string
skill: string
attribute: string
fw: number
ew: number
}
export interface M5Attribute {
value: number
bonus: number
}
export interface M5CharacterCalculatedData {
level: number,
stats: {
armor: number,
defense: number,
damageBonus: number,
attackBonus: number,
defenseBonus: number,
movementBonus: number,
resistanceMind: number,
resistanceBody: number,
spellCasting: number,
brawl: number,
poisonResistance: number,
enduranceBonus: number
},
skills: {
}
}
export default class M5Character extends Actor {
// constructor(
// data: ConstructorParameters<typeof foundry.documents.BaseActor>[0],
// context?: ConstructorParameters<typeof foundry.documents.BaseActor>[1]
// ) {
// super(data, context)
// this.prepareDerivedData()
// }
static attributeMinMax(attribute: M5Attribute) {
return Math.min(100, Math.max(0, attribute.value + attribute.bonus))
}
static attributeBonus(attribute: M5Attribute) {
const value = this.attributeMinMax(attribute)
if (value > 95)
return 2
if (value > 80)
return 1
if (value > 20)
return 0
if (value > 5)
return -1
return -2
}
prepareDerivedData() {
const context = (this as any).data;
//console.log(context)
context.data.calc = {
level: 0,
stats: {
armor: 0,
defense: 0,
damageBonus: 0,
attackBonus: 0,
defenseBonus: 0,
movementBonus: 0,
resistanceMind: 0,
resistanceBody: 0,
spellCasting: 0,
brawl: 0,
poisonResistance: 0,
enduranceBonus: 0
},
skills: {}
} as M5CharacterCalculatedData
const data = context.data
const st = M5Character.attributeMinMax(data.attributes.st)
const gs = M5Character.attributeMinMax(data.attributes.gs)
const gw = M5Character.attributeMinMax(data.attributes.gw)
const ko = M5Character.attributeMinMax(data.attributes.ko)
const calc = context.data.calc as M5CharacterCalculatedData
calc.level = M5Character.levelFromExp(data.es)
calc.stats.armor = 0
calc.stats.defense = M5Character.defenseFromLevel(calc.level)
calc.stats.damageBonus = Math.floor(st/20) + Math.floor(gs/30) - 3
calc.stats.attackBonus = M5Character.attributeBonus(data.attributes.gs)
calc.stats.defenseBonus = M5Character.attributeBonus(data.attributes.gw)
calc.stats.movementBonus = 0
calc.stats.resistanceMind = calc.stats.defense
calc.stats.resistanceBody = calc.stats.defense + 1
calc.stats.spellCasting = M5Character.spellCastingFromLevel(calc.level) + M5Character.attributeBonus(data.attributes.zt)
calc.stats.brawl = Math.floor((st + gw) / 20)
calc.stats.poisonResistance = 30 + Math.floor(ko / 2)
calc.stats.enduranceBonus = Math.floor(ko/10) + Math.floor(st/20)
}
static readonly levelThreshold: Array<number> = [0, 100, 250, 500, 750, 1000, 1250, 1500, 1750, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 6000, 7000, 8000, 9000, 10000, 12500, 15000, 17500, 20000, 22500, 25000, 30000, 35000, 40000, 45000, 50000, 55000, 60000, 65000, 70000, 75000, 80000, 85000, 90000, 95000, 100000, 105000, 110000, 115000, 120000, 125000, 130000, 135000, 140000, 145000, 150000, 155000, 160000, 165000, 170000, 175000, 180000, 185000, 190000, 195000, 200000, 205000, 210000, 215000, 220000, 225000, 230000, 235000, 240000, 245000, 250000, 255000, 260000, 265000, 270000, 275000, 280000]
static levelFromExp(exp: number): number {
const ret = M5Character.levelThreshold.findIndex(val => val > exp)
return ret === -1 ? M5Character.levelThreshold.length : ret
}
static readonly defenseThreshold: Array<[number, number]> = [
[1, 11],
[2, 12],
[5, 13],
[10, 14],
[15, 15],
[20, 16],
[25, 17],
[30, 18]
]
static defenseFromLevel(lvl: number): number {
const ret = M5Character.defenseThreshold.find(val => val[0] >= lvl)
return ret ? ret[1] : M5Character.defenseThreshold[M5Character.defenseThreshold.length - 1][1]
}
static readonly spellCastingThreshold: Array<[number, number]> = [
[1, 11],
[2, 12],
[4, 13],
[6, 14],
[8, 15],
[10, 16],
[15, 17],
[20, 18]
]
static spellCastingFromLevel(lvl: number): number {
const ret = M5Character.spellCastingThreshold.find(val => val[0] >= lvl)
return ret ? ret[1] : M5Character.spellCastingThreshold[M5Character.spellCastingThreshold.length - 1][1]
}
attributeStrength() {
const data = (this as any).data.data
return M5Character.attributeMinMax(data.data.attributes.st)
}
attributeDexterity() {
const data = (this as any).data.data
return M5Character.attributeMinMax(data.data.attributes.gs)
}
attributeAgility() {
const data = (this as any).data.data
return M5Character.attributeMinMax(data.data.attributes.gw)
}
attributeConstitution() {
const data = (this as any).data.data
return M5Character.attributeMinMax(data.data.attributes.ko)
}
attributeIntelligence() {
const data = (this as any).data.data
return M5Character.attributeMinMax(data.data.attributes.in)
}
attributeMagic() {
const data = (this as any).data.data
return M5Character.attributeMinMax(data.data.attributes.zt)
}
attributeBeauty() {
const data = (this as any).data.data
return M5Character.attributeMinMax(data.data.attributes.au)
}
attributeCharisma() {
const data = (this as any).data.data
return M5Character.attributeMinMax(data.data.attributes.pa)
}
attributeWillpower() {
const data = (this as any).data.data
return M5Character.attributeMinMax(data.data.attributes.wk)
}
}
+66
View File
@@ -0,0 +1,66 @@
import Logger from "../../utils/Logger"
import M5Character, { M5Attribute } from "../actors/M5Character"
export default class M5CharacterSheet extends ActorSheet {
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
template: "systems/midgard5/templates/sheets/character/main.hbs",
width: 600,
height: 400,
classes: ["midgard5", "sheet", "character"],
tabs: [{ navSelector: ".sheet-navigation", contentSelector: ".sheet-content", initial: "base_values" }]
})
}
// get template() {
// return "systems/midgard5/templates/character_sheet/main.hbs"
// }Options extends ActorSheet.Options = ActorSheet.Options, Data extends object = ActorSheet.Data<Options>
override getData(options?: Partial<ActorSheet.Options>): ActorSheet.Data<ActorSheet.Options> | Promise<ActorSheet.Data<ActorSheet.Options>> {
const actor = this.actor as M5Character
//console.log("Sheet getData", (actor as any).data)
return Promise.resolve(super.getData(options)).then(context => {
actor.prepareDerivedData()
const actorData = (actor as any).data.toObject(false)
context.actor = actorData;
context.data = actorData.data;
//console.log("Sheet Promise", context)
return context
})
}
override activateListeners(html: JQuery) {
super.activateListeners(html)
html.find(".roll-button").on("click", async (event) => {
const row = event.target.parentElement
let skillName = row.dataset["skill"]
const actor = this.actor as M5Character
const context = this.actor.data
const data = context.data
const skill = data.skills.general[skillName]
const attribute = data.attributes[skill.attribute]
console.log(skill, attribute)
const r = new Roll("1d20 + @fw + @bonus", {
fw: skill.fw + 12,
bonus: M5Character.attributeBonus(attribute)
})
await r.evaluate().then(value => {
const skillLocalized = (game as Game).i18n.localize("midgard5." + skillName)
const chatString = skillLocalized + ": " + value.result + " = " + value.total
//console.log(chatString)
const speaker = ChatMessage.getSpeaker({actor: actor})
let chatData = { content: (game as Game).i18n.format(chatString, {name: (actor as any).name}), speaker }
ChatMessage.applyRollMode(chatData, (r.options as any).rollMode)
return ChatMessage.create(chatData)
})
})
}
}
+14
View File
@@ -0,0 +1,14 @@
export default class M5ItemSheet extends ItemSheet {
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
width: 530,
height: 340,
classes: ["midgard5", "sheet", "item"]
});
}
get template() {
return 'systems/midgard5/templates/sheets/m5Item-Sheet.hbs';
}
}
+43
View File
@@ -0,0 +1,43 @@
// main: midgard5.less
.midgard5 {
.flexrow {
align-items: center;
}
h3 {
margin-top: 1rem;
}
.sheet.character {
form {
display: flex;
flex-direction: column;
}
.sheet-content {
height: 100%;
display: flex;
flex-direction: column;
.editor {
height: 100%;
}
}
.profile-img {
height: 64px;
width: 64px;
}
.description {
flex: 0 0 100%;
margin: 0;
}
}
.level-display {
text-align: right;
font-weight: bold;
}
}
+20
View File
@@ -0,0 +1,20 @@
// main: midgard5.less
.midgard5.sheet.item {
form {
display: flex;
flex-direction: column;
}
.sheet-content {
height: 100%;
display: flex;
flex-direction: column;
.editor {
height: 100%;
}
}
.item-img {
height: 64px;
width: 64px;
}
}
+66
View File
@@ -0,0 +1,66 @@
{
"name": "midgard5",
"title": "Midgard 5. Edition",
"description": "The German RPG Midgard 5. Edition",
"version": "1.0.0",
"minimumCoreVersion": "0.8.9",
"compatibleCoreVersion": "9",
"templateVersion": 3,
"author": "Hans-Joachim Maier, Michael Stein",
"scripts": ["bundle.js"],
"styles": ["bundle.css"],
"packs": [
{
"name": "blaupause-spielfiguren",
"label": "Blaupausen für Spielfiguren",
"path": "./packs/actors/blaupause-spielfiguren.db",
"type": "Actor"
},
{
"name": "blaupause-gegenstaende",
"label": "Blaupausen für Gegenstände",
"path": "./packs/actors/blaupause-gegenstaende.db",
"type": "Item"
},
{
"name": "tabellen-kritische-ereignisse",
"label": "Tabellen Kritische Ereignisse",
"path": "./packs/rolltables/tabellen-kritische-ereignisse.db",
"type": "RollTable"
},
{
"name": "makros-kritische-ereignisse",
"label": "Makros Kritische Ereignisse",
"path": "./packs/macros/makros-kritische-ereignisse.db",
"type": "Macro"
},
{
"name": "makros-standardwurfel",
"label": "Standardwürfel",
"path": "./packs/macros/makros-standardwurfel.db",
"type": "Macro"
},
{
"name": "scenes-midgard-karten",
"label": "Midgard-Karten",
"path": "./packs/scenes/scenes-midgard-karten.db",
"type": "Scene"
}
],
"languages": [
{
"lang": "de",
"name": "Deutsch",
"path": "lang/de.json"
}
],
"gridDistance": 1,
"gridUnits": "m",
"primaryTokenAttribute": "lp",
"secondaryTokenAttribute": "ap",
"url": "https://github.com/hjmaier/midgard5",
"manifest": "https://raw.githubusercontent.com/hjmaier/midgard5/main/system.json",
"download": "https://github.com/hjmaier/midgard5/archive/V0.06h.zip",
"initiative": "@gw.value + @gw.bonus",
"license": "LICENSE.txt"
}
+189
View File
@@ -0,0 +1,189 @@
{
"Actor": {
"types": ["npc", "character"],
"templates": {
"characterDescription": {
"info": {
"description": "",
"class": "",
"race": "",
"magic_using": false,
"gender": "",
"weight": "",
"height": "",
"shape": "",
"age": "",
"caste": "",
"occupation": "",
"origin": "",
"faith": ""
}
},
"characterBars": {
"lp": {
"value": 15,
"min": 0,
"max": 15
},
"ap": {
"value": 20,
"min": 0,
"max": 20
}
},
"characterHeader": {
"es": 0,
"ep": 0,
"gg": 0,
"sg": 0,
"gp": 2
},
"attributes": {
"attributes": {
"st": {
"value": 50,
"bonus": 0
},
"gs": {
"value": 50,
"bonus": 0
},
"gw": {
"value": 50,
"bonus": 0
},
"ko": {
"value": 50,
"bonus": 0
},
"in": {
"value": 50,
"bonus": 0
},
"zt": {
"value": 50,
"bonus": 0
},
"au": {
"value": 50,
"bonus": 0
},
"pa": {
"value": 50,
"bonus": 0
},
"wk": {
"value": 50,
"bonus": 0
}
}
},
"skills": {
"skills": {
"preferredCombatSkill": "",
"learned": [],
"general": {
"akrobatik": { "fw": 0, "attribute": "gw" },
"alchimie": { "fw": 0, "attribute": "in" },
"anfuehren": { "fw": 0, "attribute": "pa" },
"athletik": { "fw": 0, "attribute": "st" },
"balancieren": { "fw": 0, "attribute": "gw" },
"beidhaendigerKampf": { "fw": 0, "attribute": "gs" },
"beredsamkeit": { "fw": 0, "attribute": "pa" },
"betaeuben": { "fw": 0, "attribute": "gs" },
"bootfahren": { "fw": 0, "attribute": "gs" },
"ersteHilfe": { "fw": 0, "attribute": "gs" },
"etikette": { "fw": 0, "attribute": "in" },
"fallenEntdecken": { "fw": 0, "attribute": "in" },
"fallenmechanik": { "fw": 0, "attribute": "gs" },
"faelschen": { "fw": 0, "attribute": "gs" },
"fechten": { "fw": 0, "attribute": "gs" },
"gassenwissen": { "fw": 0, "attribute": "in" },
"gaukeln": { "fw": 0, "attribute": "gs" },
"gelaendelauf": { "fw": 0, "attribute": "gw" },
"geraetekunde": { "fw": 0, "attribute": "in" },
"geschaeftssinn": { "fw": 0, "attribute": "in" },
"gluecksspiel": { "fw": 0, "attribute": "gs" },
"heilkunde": { "fw": 0, "attribute": "in" },
"kampfInVollruestung": { "fw": 0, "attribute": "st" },
"klettern": { "fw": 0, "attribute": "st" },
"landeskunde": { "fw": 0, "attribute": "in" },
"laufen": { "fw": 0, "attribute": "ko" },
"lesenVonZauberschrift": { "fw": 0, "attribute": "in" },
"meditieren": { "fw": 0, "attribute": "wk" },
"menschenkenntnis": { "fw": 0, "attribute": "in" },
"meucheln": { "fw": 0, "attribute": "gs" },
"musizieren": { "fw": 0, "attribute": "gs" },
"naturkunde": { "fw": 0, "attribute": "in" },
"pflanzenkunde": { "fw": 0, "attribute": "in" },
"reiten": { "fw": 0, "attribute": "gw" },
"reiterkampf": { "fw": 0, "attribute": "gw" },
"scharfschiessen": { "fw": 0, "attribute": "gs" },
"schleichen": { "fw": 0, "attribute": "gw" },
"schloesserOeffnen": { "fw": 0, "attribute": "gs" },
"schwimmen": { "fw": 0, "attribute": "gw" },
"seilkunst": { "fw": 0, "attribute": "gs" },
"spurensuche": { "fw": 0, "attribute": "in" },
"stehlen": { "fw": 0, "attribute": "gs" },
"tarnen": { "fw": 0, "attribute": "gw" },
"tauchen": { "fw": 0, "attribute": "ko" },
"tierkunde": { "fw": 0, "attribute": "in" },
"ueberleben": { "fw": 0, "attribute": "in" },
"verfuehren": { "fw": 0, "attribute": "pa" },
"verhoeren": { "fw": 0, "attribute": "pa" },
"verstellen": { "fw": 0, "attribute": "pa" },
"wagenlenken": { "fw": 0, "attribute": "gs" },
"zauberkunde": { "fw": 0, "attribute": "in" }
},
"combat": {}
}
}
},
"character": {
"templates": ["characterBars", "attributes", "characterDescription", "characterHeader", "skills"]
},
"npc": {
"templates": ["characterBars", "attributes", "characterDescription"]
},
"vehicle": {
"templates": ["characterBars", "attributes"]
}
},
"Item": {
"types": ["item", "weapon", "armor", "spell"],
"templates": {
"itemDescription": {
"description": " "
},
"stats": {
"damageBonus": 0,
"attackBonus": 0,
"defenseBonus": 0,
"movementBonus": 0,
"resistanceMind": 0,
"resistanceBody": 0,
"spellBonus": 0
}
},
"item": {
"templates": ["itemDescription"],
"quantity": 1,
"value": 0,
"magic": false,
"onbody": false,
"attributes": {},
"groups": {}
},
"weapon": {
"templates": ["itemDescription", "stats"],
"skill": ""
},
"armor": {
"templates": ["itemDescription", "stats"],
"skill": ""
},
"spell": {
"templates": ["itemDescription"]
}
}
}
+47
View File
@@ -0,0 +1,47 @@
import Globals from "../Globals";
import Color from "color";
class Logger {
// static class
private constructor() {}
private static GetCurrentTime(): string {
return `[${(new Date().toLocaleTimeString())}] `;
}
static log(str: string, colour: Color = Color("white"), bold = false): void {
const time = ToConsole(Logger.GetCurrentTime(), Color("gray"), false)
const moduleName = ToConsole(Globals.name + " ", Color("cyan"), true);
const text = ToConsole(str, colour, bold);
console.log(time.str + moduleName.str + text.str, ...time.params.concat(moduleName.params, text.params));
}
static Err(str: string): void {
Logger.log(str, Color("orange"));
}
static Warn(str: string): void {
Logger.log(str, Color("yellow"));
}
static Ok(str: string): void {
Logger.log(str, Color("green"));
}
}
interface ConsoleColour {
str: string,
params: Array<string>;
}
const ToConsole = (str: string, col: Color, bold: boolean): ConsoleColour => {
return {
str: `%c` + str + `%c`,
params: [
"color: " + col.hex() + ";" + (bold ? "font-weight: bold;" : ""),
"color: unset; font-weight: unset;"
]
}
};
export default Logger;
+8
View File
@@ -0,0 +1,8 @@
import {ModuleData} from "@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/packages.mjs";
import Globals from "../Globals";
export const getModuleInformation = async (): Promise<ModuleData> => {
const systemOrModule = Globals.isModule ? "module" : "system"
const module = await fetch(systemOrModule + "s/" + Globals.name + "/" + systemOrModule + ".json")
return await module.json();
};