Merge remote-tracking branch 'origin/master' into add-special-creature-ability-compendium

This commit is contained in:
Johannes Loher 2021-01-21 01:29:09 +01:00
commit c5a5a7d5a2
19 changed files with 209 additions and 31 deletions

View file

@ -175,6 +175,9 @@
"DS4.WarningActorCannotOwnItem": "Der Aktor '{actorName}' vom Typ '{actorType}' kann das Item '{itemName}' vom Typ '{itemType}' nicht besitzen.", "DS4.WarningActorCannotOwnItem": "Der Aktor '{actorName}' vom Typ '{actorType}' kann das Item '{itemName}' vom Typ '{itemType}' nicht besitzen.",
"DS4.ErrorDiceCritOverlap": "Es gibt eine Überlappung zwischen Patzern und Immersiegen.", "DS4.ErrorDiceCritOverlap": "Es gibt eine Überlappung zwischen Patzern und Immersiegen.",
"DS4.ErrorExplodingRecursionLimitExceeded": "Die maximale Rekursionstiefe für slayende Würfelwürfe wurde überschritten.", "DS4.ErrorExplodingRecursionLimitExceeded": "Die maximale Rekursionstiefe für slayende Würfelwürfe wurde überschritten.",
"DS4.ErrorDuringMigration": "Fehler während der Aktualisierung des DS4 Systems von Migrationsversion {currentVersion} auf {targetVersion}. Der Fehler trat während der Ausführung des Migrationsskripts mit der Version {migrationVersion} auf. Spätere Migrationsskripte wurden nicht ausgeführt. Mehr Details finden Sie in der Entwicklerkonsole (F12).",
"DS4.InfoSystemUpdateStart": "Aktualisiere DS4 System von Migrationsversion {currentVersion} auf {targetVersion}. Bitte haben Sie etwas Geduld, schließen Sie nicht das Spiel und fahren Sie nicht den Server herunter.",
"DS4.InfoSystemUpdateCompleted": "Aktualisierung des DS4 Systems von Migrationsversion {currentVersion} auf {targetVersion} erfolgreich!",
"DS4.UnitRounds": "Runden", "DS4.UnitRounds": "Runden",
"DS4.UnitRoundsAbbr": "Rnd", "DS4.UnitRoundsAbbr": "Rnd",
"DS4.UnitMinutes": "Minuten", "DS4.UnitMinutes": "Minuten",

View file

@ -175,6 +175,9 @@
"DS4.WarningActorCannotOwnItem": "The actor '{actorName}' of type '{actorType}' cannot own the item '{itemName}' of type '{itemType}'.", "DS4.WarningActorCannotOwnItem": "The actor '{actorName}' of type '{actorType}' cannot own the item '{itemName}' of type '{itemType}'.",
"DS4.ErrorDiceCritOverlap": "There's an overlap between Fumbles and Coups", "DS4.ErrorDiceCritOverlap": "There's an overlap between Fumbles and Coups",
"DS4.ErrorExplodingRecursionLimitExceeded": "Maximum recursion depth for exploding dice roll exceeded", "DS4.ErrorExplodingRecursionLimitExceeded": "Maximum recursion depth for exploding dice roll exceeded",
"DS4.ErrorDuringMigration": "Error while migrating DS4 system from migration version {currentVersion} to {targetVersion}. The error occurred during execution of migration script with version {migrationVersion}. Later migrations have not been executed. For more details, please look at the development console (F12).",
"DS4.InfoSystemUpdateStart": "Migrating DS4 system from migration version {currentVersion} to {targetVersion}. Please be patient and do not close your game or shut down your server.",
"DS4.InfoSystemUpdateCompleted": "Migration of DS4 system from migration version {currentVersion} to {targetVersion} successful!",
"DS4.UnitRounds": "Rounds", "DS4.UnitRounds": "Rounds",
"DS4.UnitRoundsAbbr": "rnd", "DS4.UnitRoundsAbbr": "rnd",
"DS4.UnitMinutes": "Minutes", "DS4.UnitMinutes": "Minutes",

View file

@ -1,6 +1,6 @@
import { ModifiableData } from "../common/common-data"; import { ModifiableData } from "../common/common-data";
import { DS4Item } from "../item/item"; import { DS4Item } from "../item/item";
import { DS4ItemDataType, ItemType } from "../item/item-data"; import { DS4Armor, DS4ItemDataType, DS4Shield, ItemType } from "../item/item-data";
import { DS4ActorDataType } from "./actor-data"; import { DS4ActorDataType } from "./actor-data";
export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item> { export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item> {
@ -15,12 +15,7 @@ export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item>
const traits = data.data.traits; const traits = data.data.traits;
Object.values(traits).forEach((trait: ModifiableData<number>) => (trait.total = trait.base + trait.mod)); Object.values(traits).forEach((trait: ModifiableData<number>) => (trait.total = trait.base + trait.mod));
const combatValues = data.data.combatValues; this._prepareCombatValues();
Object.values(combatValues).forEach(
(combatValue: ModifiableData<number>) => (combatValue.total = combatValue.base + combatValue.mod),
);
combatValues.hitPoints.max = combatValues.hitPoints.total;
} }
/** /**
@ -55,4 +50,44 @@ export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item>
canOwnItemType(itemType: ItemType): boolean { canOwnItemType(itemType: ItemType): boolean {
return this.ownableItemTypes.includes(itemType); return this.ownableItemTypes.includes(itemType);
} }
/**
* Prepares the combat values of the actor.
*/
private _prepareCombatValues(): void {
const data = this.data.data;
const armorValueOfEquippedItems = this._calculateArmorValueOfEquippedItems();
data.combatValues.hitPoints.base =
(data.attributes.body.total ?? 0) + (data.traits.constitution.total ?? 0) + 10;
data.combatValues.defense.base =
(data.attributes.body.total ?? 0) + (data.traits.constitution.total ?? 0) + armorValueOfEquippedItems;
data.combatValues.initiative.base = (data.attributes.mobility.total ?? 0) + (data.traits.agility.total ?? 0);
data.combatValues.movement.base = (data.attributes.mobility.total ?? 0) / 2 + 1;
data.combatValues.meleeAttack.base = (data.attributes.body.total ?? 0) + (data.traits.strength.total ?? 0);
data.combatValues.rangedAttack.base =
(data.attributes.mobility.total ?? 0) + (data.traits.dexterity.total ?? 0);
data.combatValues.spellcasting.base =
(data.attributes.mind.total ?? 0) + (data.traits.aura.total ?? 0) - armorValueOfEquippedItems;
data.combatValues.targetedSpellcasting.base =
(data.attributes.mind.total ?? 0) + (data.traits.dexterity.total ?? 0) - armorValueOfEquippedItems;
Object.values(data.combatValues).forEach(
(combatValue: ModifiableData<number>) => (combatValue.total = combatValue.base + combatValue.mod),
);
data.combatValues.hitPoints.max = data.combatValues.hitPoints.total;
}
/**
* Calculates the total armor value of all equipped items.
*/
private _calculateArmorValueOfEquippedItems(): number {
return this.items
.filter((item) => ["armor", "shield"].includes(item.type))
.map((item) => item.data.data as DS4Armor | DS4Shield)
.filter((itemData) => itemData.equipped)
.map((itemData) => itemData.armorValue)
.reduce((a, b) => a + b, 0);
}
} }

View file

@ -14,6 +14,7 @@ export class DS4ActorSheet extends ActorSheet<DS4ActorDataType, DS4Actor, DS4Ite
classes: ["ds4", "sheet", "actor"], classes: ["ds4", "sheet", "actor"],
width: 745, width: 745,
height: 600, height: 600,
scrollY: [".sheet-body"],
}); });
} }

View file

@ -4,7 +4,13 @@ export interface ModifiableData<T> {
total?: T; total?: T;
} }
export interface ResourceData<T> extends ModifiableData<T> { export interface ModifiableMaybeData<T> {
base?: T;
mod: T;
total?: T;
}
export interface ResourceData<T> extends ModifiableMaybeData<T> {
value: T; value: T;
max?: T; max?: T;
} }

View file

@ -7,6 +7,8 @@ import { DS4Check } from "./rolls/check";
import { DS4CharacterActorSheet } from "./actor/sheets/character-sheet"; import { DS4CharacterActorSheet } from "./actor/sheets/character-sheet";
import { DS4CreatureActorSheet } from "./actor/sheets/creature-sheet"; import { DS4CreatureActorSheet } from "./actor/sheets/creature-sheet";
import { createCheckRoll } from "./rolls/check-factory"; import { createCheckRoll } from "./rolls/check-factory";
import { registerSystemSettings } from "./settings";
import { migration } from "./migrations";
Hooks.once("init", async function () { Hooks.once("init", async function () {
console.log(`DS4 | Initializing the DS4 Game System\n${DS4.ASCII}`); console.log(`DS4 | Initializing the DS4 Game System\n${DS4.ASCII}`);
@ -16,6 +18,7 @@ Hooks.once("init", async function () {
DS4Item, DS4Item,
DS4, DS4,
createCheckRoll, createCheckRoll,
migration,
}; };
// Record configuration // Record configuration
@ -37,6 +40,9 @@ Hooks.once("init", async function () {
s: DS4Check, s: DS4Check,
}; };
// Register system settings
registerSystemSettings();
// Register sheet application classes // Register sheet application classes
Actors.unregisterSheet("core", ActorSheet); Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet("ds4", DS4CharacterActorSheet, { types: ["character"], makeDefault: true }); Actors.registerSheet("ds4", DS4CharacterActorSheet, { types: ["character"], makeDefault: true });
@ -123,3 +129,7 @@ Hooks.once("setup", function () {
}, {}); }, {});
} }
}); });
Hooks.once("ready", function () {
migration.migrate();
});

View file

@ -24,7 +24,7 @@ interface DS4Weapon extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable {
opponentDefense: number; opponentDefense: number;
} }
interface DS4Armor extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable, DS4ItemProtective { export interface DS4Armor extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable, DS4ItemProtective {
armorMaterialType: "cloth" | "leather" | "chain" | "plate"; armorMaterialType: "cloth" | "leather" | "chain" | "plate";
armorType: "body" | "helmet" | "vambrace" | "greaves" | "vambraceGreaves"; armorType: "body" | "helmet" | "vambrace" | "greaves" | "vambraceGreaves";
} }
@ -57,7 +57,7 @@ interface DS4Spell extends DS4ItemBase, DS4ItemEquipable {
scrollPrice: number; scrollPrice: number;
} }
interface DS4Shield extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable, DS4ItemProtective {} export interface DS4Shield extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable, DS4ItemProtective {}
interface DS4Trinket extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable {} interface DS4Trinket extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable {}
interface DS4Equipment extends DS4ItemBase, DS4ItemPhysical {} interface DS4Equipment extends DS4ItemBase, DS4ItemPhysical {}
type DS4RacialAbility = DS4ItemBase; type DS4RacialAbility = DS4ItemBase;

View file

@ -13,6 +13,7 @@ export class DS4ItemSheet extends ItemSheet<DS4ItemDataType, DS4Item> {
height: 400, height: 400,
classes: ["ds4", "sheet", "item"], classes: ["ds4", "sheet", "item"],
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }], tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }],
scrollY: [".sheet-body"],
}); });
} }

81
src/module/migrations.ts Normal file
View file

@ -0,0 +1,81 @@
import { migrate as migrate001 } from "./migrations/001";
async function migrate(): Promise<void> {
if (!game.user.isGM) {
return;
}
const oldMigrationVersion: number = game.settings.get("ds4", "systemMigrationVersion");
const targetMigrationVersion = migrations.length;
if (isFirstWorldStart(oldMigrationVersion)) {
game.settings.set("ds4", "systemMigrationVersion", targetMigrationVersion);
return;
}
return migrateFromTo(oldMigrationVersion, targetMigrationVersion);
}
async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion: number): Promise<void> {
if (!game.user.isGM) {
return;
}
const migrationsToExecute = migrations.slice(oldMigrationVersion, targetMigrationVersion);
if (migrationsToExecute.length > 0) {
ui.notifications.info(
game.i18n.format("DS4.InfoSystemUpdateStart", {
currentVersion: oldMigrationVersion,
targetVersion: targetMigrationVersion,
}),
{ permanent: true },
);
for (const [i, migration] of migrationsToExecute.entries()) {
const currentMigrationVersion = oldMigrationVersion + i + 1;
console.log("executing migration script ", currentMigrationVersion);
try {
await migration();
game.settings.set("ds4", "systemMigrationVersion", currentMigrationVersion);
} catch (err) {
ui.notifications.error(
game.i18n.format("DS4.ErrorDuringMigration", {
currentVersion: oldMigrationVersion,
targetVersion: targetMigrationVersion,
migrationVersion: currentMigrationVersion,
}),
{ permanent: true },
);
err.message = `Failed ds4 system migration: ${err.message}`;
console.error(err);
return;
}
}
ui.notifications.info(
game.i18n.format("DS4.InfoSystemUpdateCompleted", {
currentVersion: oldMigrationVersion,
targetVersion: targetMigrationVersion,
}),
{ permanent: true },
);
}
}
function getTargetMigrationVersion(): number {
return migrations.length;
}
const migrations: Array<() => Promise<void>> = [migrate001];
function isFirstWorldStart(migrationVersion: number): boolean {
return migrationVersion < 0;
}
export const migration = {
migrate: migrate,
migrateFromTo: migrateFromTo,
getTargetMigrationVersion: getTargetMigrationVersion,
};

View file

@ -0,0 +1,28 @@
export async function migrate(): Promise<void> {
for (const a of game.actors.entities) {
const updateData = getActorUpdateData();
console.log(`Migrating actor ${a.name}`);
await a.update(updateData, { enforceTypes: false });
}
}
function getActorUpdateData(): Record<string, unknown> {
const updateData = {
data: {
combatValues: [
"hitPoints",
"defense",
"initiative",
"movement",
"meleeAttack",
"rangedAttack",
"spellcasting",
"targetedSpellcasting",
].reduce((acc, curr) => {
acc[curr] = { "-=base": null };
return acc;
}, {}),
},
};
return updateData;
}

View file

@ -33,7 +33,7 @@ class CheckFactory {
private checkOptions: DS4CheckFactoryOptions; private checkOptions: DS4CheckFactoryOptions;
async execute(): Promise<ChatMessage | any> { async execute(): Promise<ChatMessage | unknown> {
const rollCls: typeof Roll = CONFIG.Dice.rolls[0]; const rollCls: typeof Roll = CONFIG.Dice.rolls[0];
const formula = [ const formula = [
@ -82,7 +82,7 @@ class CheckFactory {
export async function createCheckRoll( export async function createCheckRoll(
targetValue: number, targetValue: number,
options: Partial<DS4CheckFactoryOptions> = {}, options: Partial<DS4CheckFactoryOptions> = {},
): Promise<ChatMessage | any> { ): Promise<ChatMessage | unknown> {
// Ask for additional required data; // Ask for additional required data;
const gmModifierData = await askGmModifier(targetValue, options); const gmModifierData = await askGmModifier(targetValue, options);
@ -99,7 +99,7 @@ export async function createCheckRoll(
// Possibly additional processing // Possibly additional processing
// Execute roll // Execute roll
await cf.execute(); return cf.execute();
} }
/** /**

12
src/module/settings.ts Normal file
View file

@ -0,0 +1,12 @@
export function registerSystemSettings(): void {
/**
* Track the migrations version of the latest migration that has been applied
*/
game.settings.register("ds4", "systemMigrationVersion", {
name: "System Migration Version",
scope: "world",
config: false,
type: Number,
default: -1,
});
}

View file

@ -2,7 +2,7 @@
margin-top: $margin-sm; margin-top: $margin-sm;
.attribute { .attribute {
.attribute-label { .attribute-label {
font-family: $font-heading; @include font-heading-upper;
font-size: 2em; font-size: 2em;
text-align: center; text-align: center;
} }
@ -23,7 +23,7 @@
.trait { .trait {
.trait-label { .trait-label {
color: transparent; color: transparent;
font-family: $font-heading; @include font-heading-upper;
font-size: 2em; font-size: 2em;
text-align: center; text-align: center;
//text-shadow: -1px 1px 0 $c-black, 1px 1px 0 $c-black, 1px -1px 0 $c-black, -1px -1px 0 $c-black; //text-shadow: -1px 1px 0 $c-black, 1px 1px 0 $c-black, 1px -1px 0 $c-black, -1px -1px 0 $c-black;

View file

@ -8,7 +8,7 @@
padding-right: 3px; padding-right: 3px;
h2.progression-label { h2.progression-label {
font-family: $font-heading; @include font-heading-upper;
display: block; display: block;
height: 50px; height: 50px;
padding: 0; padding: 0;

View file

@ -41,8 +41,9 @@
.combat-value-formula { .combat-value-formula {
width: $size; width: $size;
input { text-align: center;
text-align: center; span {
line-height: $default-input-height;
} }
} }
} }

View file

@ -35,11 +35,11 @@ header.sheet-header {
border: none; border: none;
background-color: transparent; background-color: transparent;
} }
font-family: $font-heading; @include font-heading-upper;
display: block; display: block;
} }
h2.item-type { h2.item-type {
font-family: $font-heading; @include font-heading-upper;
display: block; display: block;
height: 50px; height: 50px;
padding: 0px; padding: 0px;

View file

@ -28,3 +28,8 @@
background-color: transparent; background-color: transparent;
} }
} }
@mixin font-heading-upper {
font-family: $font-heading;
text-transform: uppercase;
}

View file

@ -45,36 +45,28 @@
}, },
"combatValues": { "combatValues": {
"hitPoints": { "hitPoints": {
"base": 0,
"mod": 0, "mod": 0,
"value": 0 "value": 0
}, },
"defense": { "defense": {
"base": 0,
"mod": 0 "mod": 0
}, },
"initiative": { "initiative": {
"base": 0,
"mod": 0 "mod": 0
}, },
"movement": { "movement": {
"base": 0,
"mod": 0 "mod": 0
}, },
"meleeAttack": { "meleeAttack": {
"base": 0,
"mod": 0 "mod": 0
}, },
"rangedAttack": { "rangedAttack": {
"base": 0,
"mod": 0 "mod": 0
}, },
"spellcasting": { "spellcasting": {
"base": 0,
"mod": 0 "mod": 0
}, },
"targetedSpellcasting": { "targetedSpellcasting": {
"base": 0,
"mod": 0 "mod": 0
} }
} }

View file

@ -13,9 +13,9 @@
<div class="combat-value-with-formula"> <div class="combat-value-with-formula">
<div class="combat-value {{combat-value-key}}"><span class="combat-value-total">{{combat-value-data.total}}</span> <div class="combat-value {{combat-value-key}}"><span class="combat-value-total">{{combat-value-data.total}}</span>
</div> </div>
<div class="combat-value-formula flexrow"><input type="number" name="data.combatValues.{{combat-value-key}}.base" <div class="combat-value-formula flexrow"><span class="combat-value-base">{{combat-value-data.base}}</span><span>+</span><input
value='{{combat-value-data.base}}' data-dtype="Number" /><input type="number" type="number" name="data.combatValues.{{combat-value-key}}.mod" value='{{combat-value-data.mod}}'
name="data.combatValues.{{combat-value-key}}.mod" value='{{combat-value-data.mod}}' data-dtype="Number" /> data-dtype="Number" />
</div> </div>
</div> </div>
{{/inline}} {{/inline}}