feat: only allow specific selectable values for the cooldown duration of spells

World data (including compendium packs) is migrated automatically. In order to also migrate packs
provided by modules, you can use the following macro:
```js
const pack = game.packs.get("<name-of-the-module>.<name-of-the-pack>");
game.ds4.migration.migrateCompendiumFromTo(pack, 4, 5);
```
This commit is contained in:
Johannes Loher 2022-02-14 00:58:23 +01:00
parent 73e2d44c55
commit da1f6999eb
20 changed files with 558 additions and 876 deletions

View file

@ -114,10 +114,12 @@
"DS4.ArmorMaterialTypeNaturalAbbr": "Natürlich", "DS4.ArmorMaterialTypeNaturalAbbr": "Natürlich",
"DS4.SpellType": "Zauberspruchtyp", "DS4.SpellType": "Zauberspruchtyp",
"DS4.SpellTypeAbbr": "T", "DS4.SpellTypeAbbr": "T",
"DS4.SpellTypeDescription": "Der Typ des Zauberspruchs.",
"DS4.SortBySpellType": "Nach Zauberspruchtyp sortieren", "DS4.SortBySpellType": "Nach Zauberspruchtyp sortieren",
"DS4.SpellTypeSpellcasting": "Zaubern", "DS4.SpellTypeSpellcasting": "Zaubern",
"DS4.SpellTypeTargetedSpellcasting": "Zielzaubern", "DS4.SpellTypeTargetedSpellcasting": "Zielzaubern",
"DS4.SpellCategory": "Kategorie", "DS4.SpellCategory": "Kategorie",
"DS4.SpellCategoryDescription": "Eine Kategorie, der der Zauberspruch zugehörig ist.",
"DS4.SpellCategoryHealing": "Heilung", "DS4.SpellCategoryHealing": "Heilung",
"DS4.SpellCategoryFire": "Feuer", "DS4.SpellCategoryFire": "Feuer",
"DS4.SpellCategoryIce": "Eis", "DS4.SpellCategoryIce": "Eis",
@ -127,18 +129,33 @@
"DS4.SpellCategoryElectricity": "Elektrizität", "DS4.SpellCategoryElectricity": "Elektrizität",
"DS4.SpellCategoryNone": "Keine", "DS4.SpellCategoryNone": "Keine",
"DS4.SpellCategoryUnset": "Nicht gesetzt", "DS4.SpellCategoryUnset": "Nicht gesetzt",
"DS4.SpellBonus": "Zauberbonus", "DS4.SpellModifier": "Zauberbonus",
"DS4.SpellBonusAbbr": "ZB", "DS4.SpellModifierAbbr": "ZB",
"DS4.SortBySpellBonus": "Nach Zauberbonus sortieren", "DS4.SpellModifierDescription": "Der Zauberbonus auf die Probe.",
"DS4.SpellMaxDistance": "Reichweite", "DS4.SortBySpellModifier": "Nach Zauberbonus sortieren",
"DS4.SpellEffectRadius": "Effektradius", "DS4.SpellDistance": "Distanz",
"DS4.SpellDuration": "Wirkdauer", "DS4.SpellDistanceDescription": "Die maximale Entfernung zum Ziel. „Selbst“ bedeutet, dass nur der Zauberwirker selbst das Ziel des Zaubers sein kann.",
"DS4.SpellCooldownDuration": "Abklingzeit", "DS4.SpellEffectRadius": "Wirkungsradius",
"DS4.SpellEffectRadiusDescription": "Der Wirkungsradius des Zaubers.",
"DS4.SpellDuration": "Dauer",
"DS4.SpellDurationDescription": "Die Wirkungszeit des Zaubers.",
"DS4.CooldownDuration": "Abklingzeit",
"DS4.CooldownDurationDescription": "Die Dauer, die der Zauber nach erfolgreichem Wirken nicht einsetzbar ist.",
"DS4.CooldownDuration0R": "0 Kampfrunden",
"DS4.CooldownDuration1R": "1 Kampfrunde",
"DS4.CooldownDuration2R": "2 Kampfrunden",
"DS4.CooldownDuration5R": "5 Kampfrunden",
"DS4.CooldownDuration10R": "10 Kampfrunden",
"DS4.CooldownDuration100R": "100 Kampfrunden",
"DS4.CooldownDuration1D": "1 Tag",
"DS4.CooldownDurationD20D": "W20 Tage",
"DS4.SpellMinimumLevel": "Zugangsstufe", "DS4.SpellMinimumLevel": "Zugangsstufe",
"DS4.SpellMinimumLevelDescription": "Die minimale Stufe, ab der ein Zauberwirker den Zauberspruch erlernen kann.",
"DS4.SpellCasterClassHealer": "Heiler", "DS4.SpellCasterClassHealer": "Heiler",
"DS4.SpellCasterClassSorcerer": "Schwarzmagier", "DS4.SpellCasterClassSorcerer": "Schwarzmagier",
"DS4.SpellCasterClassWizard": "Zauberer", "DS4.SpellCasterClassWizard": "Zauberer",
"DS4.SpellPrice": "Preis (Gold)", "DS4.SpellPrice": "Preis (Gold)",
"DS4.SpellPriceDescription": "Der Kaufpreis des Zauberspruchs.",
"DS4.EffectEnabled": "Aktiv", "DS4.EffectEnabled": "Aktiv",
"DS4.EffectEnabledAbbr": "A", "DS4.EffectEnabledAbbr": "A",
"DS4.EffectEffectivelyEnabled": "Effektiv Aktiv (unter Betrachtung, ob ein eventuelles Quellen-Item ausgerüstet ist usw.)", "DS4.EffectEffectivelyEnabled": "Effektiv Aktiv (unter Betrachtung, ob ein eventuelles Quellen-Item ausgerüstet ist usw.)",
@ -240,6 +257,7 @@
"DS4.ErrorSlayingDiceRecursionLimitExceeded": "Die maximale Rekursionstiefe für slayende Würfelwürfe wurde überschritten.", "DS4.ErrorSlayingDiceRecursionLimitExceeded": "Die maximale Rekursionstiefe für slayende Würfelwürfe wurde überschritten.",
"DS4.ErrorInvalidNumberOfDice": "Ungültige Anzahl an Würfeln.", "DS4.ErrorInvalidNumberOfDice": "Ungültige Anzahl an Würfeln.",
"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.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.ErrorDuringCompendiumMigration": "Fehler während der Aktualisierung Kompendiums '{pack}' für DS4 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.ErrorCannotRollUnownedItem": "Für das Item '{name}' ({id}) kann nicht gewürfelt werden, da es keinem Aktor gehört.", "DS4.ErrorCannotRollUnownedItem": "Für das Item '{name}' ({id}) kann nicht gewürfelt werden, da es keinem Aktor gehört.",
"DS4.ErrorRollingForItemTypeNotPossible": "Würfeln ist für Items vom Typ '{type}' nicht möglich.", "DS4.ErrorRollingForItemTypeNotPossible": "Würfeln ist für Items vom Typ '{type}' nicht möglich.",
"DS4.ErrorWrongItemType": "Ein Item vom Type '{expectedType}' wurde erwartet aber das Item '{name}' ({id}) ist vom Typ '{actualType}'.", "DS4.ErrorWrongItemType": "Ein Item vom Type '{expectedType}' wurde erwartet aber das Item '{name}' ({id}) ist vom Typ '{actualType}'.",
@ -255,9 +273,11 @@
"DS4.WarningItemIsNotRollable": "Für das Item '{name}' ({id}) vom Typ '{type}' kann nicht gewürfelt werden.", "DS4.WarningItemIsNotRollable": "Für das Item '{name}' ({id}) vom Typ '{type}' kann nicht gewürfelt werden.",
"DS4.WarningMacrosCanOnlyBeCreatedForOwnedItems": "Makros können nur für besessene Items angelegt werden.", "DS4.WarningMacrosCanOnlyBeCreatedForOwnedItems": "Makros können nur für besessene Items angelegt werden.",
"DS4.WarningInvalidCheckDropped": "Eine ungültige Probe wurde auf die Hotbar gezogen.", "DS4.WarningInvalidCheckDropped": "Eine ungültige Probe wurde auf die Hotbar gezogen.",
"DS4.InfoManuallyEnterSpellBonus": "Der korrekte Wert für den Zauberbonus '{spellBonus}' des Zaubers '{name}' muss manuell angegeben werden.", "DS4.InfoManuallyEnterSpellModifier": "Der korrekte Wert für den Zauberbonus '{spellModifier}' des Zaubers '{name}' muss manuell angegeben werden.",
"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.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.InfoSystemUpdateCompleted": "Aktualisierung des DS4 Systems von Migrationsversion {currentVersion} auf {targetVersion} erfolgreich!",
"DS4.InfoCompendiumMigrationStart": "Aktualisiere Kompendium '{pack}' für DS4 von Migrationsversion {currentVersion} auf {targetVersion}. Bitte haben Sie etwas Geduld, schließen Sie nicht das Spiel und fahren Sie nicht den Server herunter.",
"DS4.InfoCompendiumMigrationCompleted": "Aktualisierung des Kompendiums '{pack}' für DS4 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

@ -114,10 +114,12 @@
"DS4.ArmorMaterialTypeNaturalAbbr": "Natural", "DS4.ArmorMaterialTypeNaturalAbbr": "Natural",
"DS4.SpellType": "Spell Type", "DS4.SpellType": "Spell Type",
"DS4.SpellTypeAbbr": "T", "DS4.SpellTypeAbbr": "T",
"DS4.SpellTypeDescription": "The type of the spell.",
"DS4.SortBySpellType": "Sort by Spell Type", "DS4.SortBySpellType": "Sort by Spell Type",
"DS4.SpellTypeSpellcasting": "Spellcasting", "DS4.SpellTypeSpellcasting": "Spellcasting",
"DS4.SpellTypeTargetedSpellcasting": "Targeted Spellcasting", "DS4.SpellTypeTargetedSpellcasting": "Targeted Spellcasting",
"DS4.SpellCategory": "Category", "DS4.SpellCategory": "Category",
"DS4.SpellCategoryDescription": "A category which the spell belongs to.",
"DS4.SpellCategoryHealing": "Healing", "DS4.SpellCategoryHealing": "Healing",
"DS4.SpellCategoryFire": "Fire", "DS4.SpellCategoryFire": "Fire",
"DS4.SpellCategoryIce": "Ice", "DS4.SpellCategoryIce": "Ice",
@ -127,18 +129,33 @@
"DS4.SpellCategoryElectricity": "Electricity", "DS4.SpellCategoryElectricity": "Electricity",
"DS4.SpellCategoryNone": "None", "DS4.SpellCategoryNone": "None",
"DS4.SpellCategoryUnset": "Unset", "DS4.SpellCategoryUnset": "Unset",
"DS4.SpellBonus": "Spell Bonus", "DS4.SpellModifier": "Spell Modifier",
"DS4.SpellBonusAbbr": "SB", "DS4.SpellModifierAbbr": "SM",
"DS4.SortBySpellBonus": "Sort by Spell Bonus", "DS4.SpellModifierDescription": "The spell modifier for the corresponding check.",
"DS4.SpellMaxDistance": "Range", "DS4.SortBySpellModifier": "Sort by Spell Modifier",
"DS4.SpellEffectRadius": "Radius", "DS4.SpellDistance": "Distance",
"DS4.SpellDistanceDescription": "The maximum distance to the target, “Self” meaning that only the caster can be the target of this spell.",
"DS4.SpellEffectRadius": "Area of Effect Radius",
"DS4.SpellEffectRadiusDescription": "The radius of the area of effect of the spell.",
"DS4.SpellDuration": "Duration", "DS4.SpellDuration": "Duration",
"DS4.SpellCooldownDuration": "Cooldown", "DS4.SpellDurationDescription": "The spells duration.",
"DS4.CooldownDuration": "Cooldown Period",
"DS4.CooldownDurationDescription": "The length of time to wait after a successful casting before the spell can be cast again.",
"DS4.CooldownDuration0R": "0 Rounds",
"DS4.CooldownDuration1R": "1 Round",
"DS4.CooldownDuration2R": "2 Rounds",
"DS4.CooldownDuration5R": "5 Rounds",
"DS4.CooldownDuration10R": "10 Rounds",
"DS4.CooldownDuration100R": "100 Rounds",
"DS4.CooldownDuration1D": "1 Day",
"DS4.CooldownDurationD20D": "D20 Days",
"DS4.SpellMinimumLevel": "Minimum Level", "DS4.SpellMinimumLevel": "Minimum Level",
"DS4.SpellMinimumLevelDescription": "The minimum level at which a spell caster may learn the spell.",
"DS4.SpellCasterClassHealer": "Healer", "DS4.SpellCasterClassHealer": "Healer",
"DS4.SpellCasterClassSorcerer": "Sorcerer", "DS4.SpellCasterClassSorcerer": "Sorcerer",
"DS4.SpellCasterClassWizard": "Wizard", "DS4.SpellCasterClassWizard": "Wizard",
"DS4.SpellPrice": "Price (Gold)", "DS4.SpellPrice": "Price (Gold)",
"DS4.SpellPriceDescription": "The price to purchase the spell.",
"DS4.EffectEnabled": "Enabled", "DS4.EffectEnabled": "Enabled",
"DS4.EffectEnabledAbbr": "E", "DS4.EffectEnabledAbbr": "E",
"DS4.EffectEffectivelyEnabled": "Effectively Enabled (taking into account whether a potential source item is equipped etc.)", "DS4.EffectEffectivelyEnabled": "Effectively Enabled (taking into account whether a potential source item is equipped etc.)",
@ -240,6 +257,7 @@
"DS4.ErrorSlayingDiceRecursionLimitExceeded": "Maximum recursion depth for slaying dice roll exceeded.", "DS4.ErrorSlayingDiceRecursionLimitExceeded": "Maximum recursion depth for slaying dice roll exceeded.",
"DS4.ErrorInvalidNumberOfDice": "Invalid number of dice.", "DS4.ErrorInvalidNumberOfDice": "Invalid number of dice.",
"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.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.ErrorDuringCompendiumMigration": "Error while migrating compendium '{pack}' for DS4 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.ErrorCannotRollUnownedItem": "Rolling for item '{name}' ({id})is not possible because it is not owned.", "DS4.ErrorCannotRollUnownedItem": "Rolling for item '{name}' ({id})is not possible because it is not owned.",
"DS4.ErrorRollingForItemTypeNotPossible": "Rolling is not possible for items of type '{type}'.", "DS4.ErrorRollingForItemTypeNotPossible": "Rolling is not possible for items of type '{type}'.",
"DS4.ErrorWrongItemType": "Expected an item of type '{expectedType}' but item '{name}' ({id}) is of type '{actualType}'.", "DS4.ErrorWrongItemType": "Expected an item of type '{expectedType}' but item '{name}' ({id}) is of type '{actualType}'.",
@ -255,9 +273,11 @@
"DS4.WarningItemIsNotRollable": "Item '{name}' ({id}) of type '{type}' is not rollable.", "DS4.WarningItemIsNotRollable": "Item '{name}' ({id}) of type '{type}' is not rollable.",
"DS4.WarningMacrosCanOnlyBeCreatedForOwnedItems": "Macros can only be created for owned items.", "DS4.WarningMacrosCanOnlyBeCreatedForOwnedItems": "Macros can only be created for owned items.",
"DS4.WarningInvalidCheckDropped": "An invalid check was dropped on the Hotbar.", "DS4.WarningInvalidCheckDropped": "An invalid check was dropped on the Hotbar.",
"DS4.InfoManuallyEnterSpellBonus": "The correct value of the spell bonus '{spellBonus}' of the spell '{name}' needs to be entered by manually.", "DS4.InfoManuallyEnterSpellModifier": "The correct value of the spell modifier '{spellModifier}' of the spell '{name}' needs to be entered by manually.",
"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.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.InfoSystemUpdateCompleted": "Migration of DS4 system from migration version {currentVersion} to {targetVersion} successful!",
"DS4.InfoCompendiumMigrationStart": "Migrating compendium '{pack}' for DS4 from migration version {currentVersion} to {targetVersion}. Please be patient and do not close your game or shut down your server.",
"DS4.InfoCompendiumMigrationCompleted": "Migration of compendium '{pack}' for DS4 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

@ -1881,10 +1881,7 @@
"value": "", "value": "",
"unit": "custom" "unit": "custom"
}, },
"cooldownDuration": { "cooldownDuration": "10r",
"value": "10",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": null, "wizard": null,
@ -3584,10 +3581,7 @@
"value": "Sofort", "value": "Sofort",
"unit": "custom" "unit": "custom"
}, },
"cooldownDuration": { "cooldownDuration": "10r",
"value": "10",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": 2, "healer": 2,
"wizard": 5, "wizard": 5,
@ -3629,10 +3623,7 @@
"value": "VE / 2", "value": "VE / 2",
"unit": "rounds" "unit": "rounds"
}, },
"cooldownDuration": { "cooldownDuration": "1d",
"value": "24",
"unit": "hours"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": 12, "wizard": 12,
@ -3674,10 +3665,7 @@
"value": "Sofort", "value": "Sofort",
"unit": "custom" "unit": "custom"
}, },
"cooldownDuration": { "cooldownDuration": "5r",
"value": "5",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": 16, "healer": 16,
"wizard": 10, "wizard": 10,
@ -3719,10 +3707,7 @@
"value": "Sofort", "value": "Sofort",
"unit": "custom" "unit": "custom"
}, },
"cooldownDuration": { "cooldownDuration": "10r",
"value": "10",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": 16, "healer": 16,
"wizard": 12, "wizard": 12,
@ -3764,10 +3749,7 @@
"value": "Prb.", "value": "Prb.",
"unit": "rounds" "unit": "rounds"
}, },
"cooldownDuration": { "cooldownDuration": "100r",
"value": "100",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": 4, "healer": 4,
"wizard": 8, "wizard": 8,
@ -3809,10 +3791,7 @@
"value": "Prb.", "value": "Prb.",
"unit": "rounds" "unit": "rounds"
}, },
"cooldownDuration": { "cooldownDuration": "100r",
"value": "100",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": 4, "healer": 4,
"wizard": 8, "wizard": 8,
@ -3854,10 +3833,7 @@
"value": "Konzentration", "value": "Konzentration",
"unit": "custom" "unit": "custom"
}, },
"cooldownDuration": { "cooldownDuration": "0r",
"value": "0",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": 6, "wizard": 6,
@ -3899,10 +3875,7 @@
"value": "Prb.", "value": "Prb.",
"unit": "minutes" "unit": "minutes"
}, },
"cooldownDuration": { "cooldownDuration": "1d",
"value": "24",
"unit": "hours"
},
"minimumLevels": { "minimumLevels": {
"healer": 20, "healer": 20,
"wizard": 12, "wizard": 12,
@ -3944,10 +3917,7 @@
"value": "Prb.", "value": "Prb.",
"unit": "rounds" "unit": "rounds"
}, },
"cooldownDuration": { "cooldownDuration": "10r",
"value": "10",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": 8, "healer": 8,
"wizard": 5, "wizard": 5,
@ -3990,10 +3960,7 @@
"value": "Prb.", "value": "Prb.",
"unit": "rounds" "unit": "rounds"
}, },
"cooldownDuration": { "cooldownDuration": "5r",
"value": "5",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": 1, "healer": 1,
"wizard": 5, "wizard": 5,
@ -16049,10 +16016,7 @@
"value": "Prb.", "value": "Prb.",
"unit": "rounds" "unit": "rounds"
}, },
"cooldownDuration": { "cooldownDuration": "100r",
"value": "100",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": 7, "healer": 7,
"wizard": 7, "wizard": 7,
@ -20114,10 +20078,7 @@
"value": "VE", "value": "VE",
"unit": "minutes" "unit": "minutes"
}, },
"cooldownDuration": { "cooldownDuration": "1d",
"value": "24",
"unit": "hours"
},
"minimumLevels": { "minimumLevels": {
"healer": 5, "healer": 5,
"wizard": 9, "wizard": 9,
@ -21132,7 +21093,7 @@
"name": "Gedankenzehrerstrahl", "name": "Gedankenzehrerstrahl",
"type": "spell", "type": "spell",
"data": { "data": {
"description": "<p>Gedankenzehrerstrahl (nicht sichtbar; verursacht mental Schaden und f&uuml;hrt zu <strong>Werteverlust</strong>)</p>", "description": "<p>Nicht sichtbar; verursacht mental Schaden und f&uuml;hrt zu <strong>Werteverlust</strong></p>",
"equipped": true, "equipped": true,
"spellType": "targetedSpellcasting", "spellType": "targetedSpellcasting",
"bonus": "", "bonus": "",
@ -21149,10 +21110,7 @@
"value": "", "value": "",
"unit": "custom" "unit": "custom"
}, },
"cooldownDuration": { "cooldownDuration": "0r",
"value": "",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": null, "wizard": null,
@ -25446,10 +25404,7 @@
"value": "VE x 2", "value": "VE x 2",
"unit": "rounds" "unit": "rounds"
}, },
"cooldownDuration": { "cooldownDuration": "1d",
"value": "24",
"unit": "hours"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": 10, "wizard": 10,
@ -25491,10 +25446,7 @@
"value": "VE", "value": "VE",
"unit": "minutes" "unit": "minutes"
}, },
"cooldownDuration": { "cooldownDuration": "d20d",
"value": "W20",
"unit": "days"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": 18, "wizard": 18,
@ -25536,10 +25488,7 @@
"value": "Sofort", "value": "Sofort",
"unit": "custom" "unit": "custom"
}, },
"cooldownDuration": { "cooldownDuration": "10r",
"value": "10",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": 2, "healer": 2,
"wizard": 5, "wizard": 5,
@ -25581,10 +25530,7 @@
"value": "VE", "value": "VE",
"unit": "rounds" "unit": "rounds"
}, },
"cooldownDuration": { "cooldownDuration": "1d",
"value": "24",
"unit": "hours"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": 15, "wizard": 15,
@ -25626,10 +25572,7 @@
"value": "Sofort", "value": "Sofort",
"unit": "custom" "unit": "custom"
}, },
"cooldownDuration": { "cooldownDuration": "10r",
"value": "10",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": 12, "wizard": 12,
@ -25671,10 +25614,7 @@
"value": "Prb. x 5", "value": "Prb. x 5",
"unit": "rounds" "unit": "rounds"
}, },
"cooldownDuration": { "cooldownDuration": "1d",
"value": "24",
"unit": "hours"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": 15, "wizard": 15,
@ -25716,10 +25656,7 @@
"value": "VE / 2", "value": "VE / 2",
"unit": "rounds" "unit": "rounds"
}, },
"cooldownDuration": { "cooldownDuration": "1d",
"value": "24",
"unit": "hours"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": 12, "wizard": 12,
@ -25761,10 +25698,7 @@
"value": "Bis erlöst", "value": "Bis erlöst",
"unit": "custom" "unit": "custom"
}, },
"cooldownDuration": { "cooldownDuration": "10r",
"value": "10",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": 8, "wizard": 8,
@ -25806,10 +25740,7 @@
"value": "Bis Schloss geöffnet", "value": "Bis Schloss geöffnet",
"unit": "custom" "unit": "custom"
}, },
"cooldownDuration": { "cooldownDuration": "5r",
"value": "5",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": 3, "healer": 3,
"wizard": 1, "wizard": 1,
@ -25851,10 +25782,7 @@
"value": "Prb. / 2", "value": "Prb. / 2",
"unit": "rounds" "unit": "rounds"
}, },
"cooldownDuration": { "cooldownDuration": "10r",
"value": "10",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": 4, "healer": 4,
"wizard": 9, "wizard": 9,
@ -25896,10 +25824,7 @@
"value": "Prb. / 2", "value": "Prb. / 2",
"unit": "rounds" "unit": "rounds"
}, },
"cooldownDuration": { "cooldownDuration": "5r",
"value": "5",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": 6, "wizard": 6,
@ -25941,10 +25866,7 @@
"value": "Sofort", "value": "Sofort",
"unit": "custom" "unit": "custom"
}, },
"cooldownDuration": { "cooldownDuration": "1d",
"value": "24",
"unit": "hours"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": null, "wizard": null,
@ -25986,10 +25908,7 @@
"value": "Sofort", "value": "Sofort",
"unit": "custom" "unit": "custom"
}, },
"cooldownDuration": { "cooldownDuration": "0r",
"value": "0",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": 15, "wizard": 15,
@ -26031,10 +25950,7 @@
"value": "Sofort", "value": "Sofort",
"unit": "custom" "unit": "custom"
}, },
"cooldownDuration": { "cooldownDuration": "10r",
"value": "10",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": 5, "healer": 5,
"wizard": 2, "wizard": 2,
@ -26076,10 +25992,7 @@
"value": "Sofort", "value": "Sofort",
"unit": "custom" "unit": "custom"
}, },
"cooldownDuration": { "cooldownDuration": "100r",
"value": "100",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": 4, "wizard": 4,
@ -26121,10 +26034,7 @@
"value": "VE / 2", "value": "VE / 2",
"unit": "hours" "unit": "hours"
}, },
"cooldownDuration": { "cooldownDuration": "100r",
"value": "100",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": 5, "wizard": 5,
@ -26166,10 +26076,7 @@
"value": "Prb.", "value": "Prb.",
"unit": "minutes" "unit": "minutes"
}, },
"cooldownDuration": { "cooldownDuration": "1d",
"value": "24",
"unit": "hours"
},
"minimumLevels": { "minimumLevels": {
"healer": 20, "healer": 20,
"wizard": 12, "wizard": 12,
@ -26211,10 +26118,7 @@
"value": "Prb.", "value": "Prb.",
"unit": "rounds" "unit": "rounds"
}, },
"cooldownDuration": { "cooldownDuration": "10r",
"value": "10",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": 8, "healer": 8,
"wizard": 5, "wizard": 5,
@ -26256,10 +26160,7 @@
"value": "Prb. / 2", "value": "Prb. / 2",
"unit": "rounds" "unit": "rounds"
}, },
"cooldownDuration": { "cooldownDuration": "100r",
"value": "100",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": 6, "wizard": 6,
@ -26301,10 +26202,7 @@
"value": "Prb. x 2", "value": "Prb. x 2",
"unit": "rounds" "unit": "rounds"
}, },
"cooldownDuration": { "cooldownDuration": "100r",
"value": "100",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": null, "wizard": null,
@ -26346,10 +26244,7 @@
"value": "Prb.", "value": "Prb.",
"unit": "rounds" "unit": "rounds"
}, },
"cooldownDuration": { "cooldownDuration": "d20d",
"value": "W20",
"unit": "days"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": 15, "wizard": 15,
@ -28333,10 +28228,7 @@
"value": "VE / 2", "value": "VE / 2",
"unit": "rounds" "unit": "rounds"
}, },
"cooldownDuration": { "cooldownDuration": "10r",
"value": "10",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": null, "wizard": null,
@ -28882,10 +28774,7 @@
"value": "Sofort", "value": "Sofort",
"unit": "custom" "unit": "custom"
}, },
"cooldownDuration": { "cooldownDuration": "1r",
"value": "1",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": 10, "healer": 10,
"wizard": 7, "wizard": 7,

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { DS4SpellDataSourceData, TemporalUnit, UnitData } from "../../../src/item/item-data-source"; import { CooldownDuration, DS4SpellDataSourceData } from "../../../src/item/item-data-source";
import { calculateSpellPrice } from "../../../src/item/type-specific-helpers/spell"; import { calculateSpellPrice } from "../../../src/item/type-specific-helpers/spell";
const defaultData: DS4SpellDataSourceData = { const defaultData: DS4SpellDataSourceData = {
@ -23,10 +23,7 @@ const defaultData: DS4SpellDataSourceData = {
value: "", value: "",
unit: "custom", unit: "custom",
}, },
cooldownDuration: { cooldownDuration: "0r",
value: "",
unit: "rounds",
},
minimumLevels: { minimumLevels: {
healer: null, healer: null,
wizard: null, wizard: null,
@ -167,65 +164,41 @@ function buildCombinedTestCases(): CombinedTestCase[] {
} }
describe("calculateSpellPrice", () => { describe("calculateSpellPrice", () => {
const cooldownDurations: (UnitData<TemporalUnit> & { factor: number })[] = [ const cooldownDurations: { cooldownDuration: CooldownDuration; factor: number }[] = [
{ value: "", unit: "rounds", factor: 1 }, { cooldownDuration: "0r", factor: 1 },
{ value: "foo", unit: "rounds", factor: 1 }, { cooldownDuration: "1r", factor: 1 },
{ value: "0", unit: "rounds", factor: 1 }, { cooldownDuration: "2r", factor: 1 },
{ value: "1", unit: "rounds", factor: 1 }, { cooldownDuration: "5r", factor: 1 },
{ value: "17279", unit: "rounds", factor: 1 }, { cooldownDuration: "10r", factor: 1 },
{ value: "17280", unit: "rounds", factor: 2 }, { cooldownDuration: "100r", factor: 1 },
{ value: "34559", unit: "rounds", factor: 2 }, { cooldownDuration: "1d", factor: 2 },
{ value: "34560", unit: "rounds", factor: 3 }, { cooldownDuration: "d20d", factor: 3 },
{ value: "", unit: "minutes", factor: 1 },
{ value: "foo", unit: "minutes", factor: 1 },
{ value: "0", unit: "minutes", factor: 1 },
{ value: "1", unit: "minutes", factor: 1 },
{ value: "1439", unit: "minutes", factor: 1 },
{ value: "1440", unit: "minutes", factor: 2 },
{ value: "2879", unit: "minutes", factor: 2 },
{ value: "2880", unit: "minutes", factor: 3 },
{ value: "", unit: "hours", factor: 1 },
{ value: "foo", unit: "hours", factor: 1 },
{ value: "0", unit: "hours", factor: 1 },
{ value: "1", unit: "hours", factor: 1 },
{ value: "23", unit: "hours", factor: 1 },
{ value: "24", unit: "hours", factor: 2 },
{ value: "47", unit: "hours", factor: 2 },
{ value: "48", unit: "hours", factor: 3 },
{ value: "", unit: "days", factor: 3 },
{ value: "foo", unit: "days", factor: 3 },
{ value: "0", unit: "days", factor: 1 },
{ value: "1", unit: "days", factor: 2 },
{ value: "2", unit: "days", factor: 3 },
]; ];
describe.each(cooldownDurations)("with cooldown duration set to $value $unit", ({ value, unit, factor }) => { describe.each(cooldownDurations)(
const dataWithCooldownDuration = { "with cooldown duration set to $cooldownDuration",
...defaultData, ({ cooldownDuration, factor }) => {
cooldownDuration: { const dataWithCooldownDuration = {
value, ...defaultData,
unit, cooldownDuration,
}, };
};
it.each(buildCombinedTestCases())( it.each(buildCombinedTestCases())(
`returns ${factor} × $expected if the minimum leves are $minimumLevels`, `returns ${factor} × $expected if the minimum leves are $minimumLevels`,
({ minimumLevels, expected }) => { ({ minimumLevels, expected }) => {
// given // given
const data: DS4SpellDataSourceData = { const data: DS4SpellDataSourceData = {
...dataWithCooldownDuration, ...dataWithCooldownDuration,
minimumLevels, minimumLevels,
}; };
// when // when
const spellPrice = calculateSpellPrice(data); const spellPrice = calculateSpellPrice(data);
// then // then
expect(spellPrice).toBe(expected !== null ? expected * factor : expected); expect(spellPrice).toBe(expected !== null ? expected * factor : expected);
}, },
); );
}); },
);
}); });

View file

@ -1,8 +0,0 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
export const secondsPerRound = 5;
export const secondsPerMinute = 60;
export const minutesPerHour = 60;
export const hoursPerDay = 24;

View file

@ -106,6 +106,17 @@ const i18nKeys = {
unset: "DS4.SpellCategoryUnset", unset: "DS4.SpellCategoryUnset",
}, },
cooldownDurations: {
"0r": "DS4.CooldownDuration0R",
"1r": "DS4.CooldownDuration1R",
"2r": "DS4.CooldownDuration2R",
"5r": "DS4.CooldownDuration5R",
"10r": "DS4.CooldownDuration10R",
"100r": "DS4.CooldownDuration100R",
"1d": "DS4.CooldownDuration1D",
d20d: "DS4.CooldownDurationD20D",
},
/** /**
* Define the set of actor types * Define the set of actor types
*/ */
@ -276,16 +287,6 @@ const i18nKeys = {
minutes: "DS4.UnitMinutes", minutes: "DS4.UnitMinutes",
hours: "DS4.UnitHours", hours: "DS4.UnitHours",
days: "DS4.UnitDays", days: "DS4.UnitDays",
},
/**
* Define translations for available duration units including "custom"
*/
customTemporalUnits: {
rounds: "DS4.UnitRounds",
minutes: "DS4.UnitMinutes",
hours: "DS4.UnitHours",
days: "DS4.UnitDays",
custom: "DS4.UnitCustom", custom: "DS4.UnitCustom",
}, },
@ -297,16 +298,6 @@ const i18nKeys = {
minutes: "DS4.UnitMinutesAbbr", minutes: "DS4.UnitMinutesAbbr",
hours: "DS4.UnitHoursAbbr", hours: "DS4.UnitHoursAbbr",
days: "DS4.UnitDaysAbbr", days: "DS4.UnitDaysAbbr",
},
/**
* Define abbreviations for available duration units including "custom"
*/
customTemporalUnitsAbbr: {
rounds: "DS4.UnitRoundsAbbr",
minutes: "DS4.UnitMinutesAbbr",
hours: "DS4.UnitHoursAbbr",
days: "DS4.UnitDaysAbbr",
custom: "DS4.UnitCustomAbbr", custom: "DS4.UnitCustomAbbr",
}, },

View file

@ -18,7 +18,14 @@ export default function registerForSetupHooks(): void {
* Localizes all objects in {@link DS4.i18n} and sorts them unless they are explicitly excluded. * Localizes all objects in {@link DS4.i18n} and sorts them unless they are explicitly excluded.
*/ */
function localizeAndSortConfigObjects() { function localizeAndSortConfigObjects() {
const noSort = ["attributes", "traits", "combatValues", "creatureSizeCategories"]; const noSort = [
"attributes",
"combatValues",
"cooldownDurations",
"creatureSizeCategories",
"spellCategories",
"traits",
];
const localizeObject = <T extends { [s: string]: string }>(obj: T, sort = true): T => { const localizeObject = <T extends { [s: string]: string }>(obj: T, sort = true): T => {
const localized = Object.entries(obj).map(([key, value]): [string, string] => { const localized = Object.entries(obj).map(([key, value]): [string, string] => {

View file

@ -136,14 +136,16 @@ export interface DS4ShieldDataSourceData
DS4ItemDataSourceDataEquipable, DS4ItemDataSourceDataEquipable,
DS4ItemDataSourceDataProtective {} DS4ItemDataSourceDataProtective {}
export type CooldownDuration = keyof typeof DS4.i18n.cooldownDurations;
export interface DS4SpellDataSourceData extends DS4ItemDataSourceDataBase, DS4ItemDataSourceDataEquipable { export interface DS4SpellDataSourceData extends DS4ItemDataSourceDataBase, DS4ItemDataSourceDataEquipable {
spellType: keyof typeof DS4.i18n.spellTypes; spellType: keyof typeof DS4.i18n.spellTypes;
bonus: string; bonus: string;
spellCategory: keyof typeof DS4.i18n.spellCategories; spellCategory: keyof typeof DS4.i18n.spellCategories;
maxDistance: UnitData<DistanceUnit>; maxDistance: UnitData<DistanceUnit>;
effectRadius: UnitData<DistanceUnit>; effectRadius: UnitData<DistanceUnit>;
duration: UnitData<CustomTemporalUnit>; duration: UnitData<TemporalUnit>;
cooldownDuration: UnitData<TemporalUnit>; cooldownDuration: CooldownDuration;
minimumLevels: { minimumLevels: {
healer: number | null; healer: number | null;
wizard: number | null; wizard: number | null;
@ -158,9 +160,7 @@ export interface UnitData<UnitType> {
type DistanceUnit = keyof typeof DS4.i18n.distanceUnits; type DistanceUnit = keyof typeof DS4.i18n.distanceUnits;
type CustomTemporalUnit = keyof typeof DS4.i18n.customTemporalUnits; type TemporalUnit = keyof typeof DS4.i18n.temporalUnits;
export type TemporalUnit = keyof typeof DS4.i18n.temporalUnits;
export interface DS4EquipmentDataSourceData export interface DS4EquipmentDataSourceData
extends DS4ItemDataSourceDataBase, extends DS4ItemDataSourceDataBase,

View file

@ -148,17 +148,17 @@ export class DS4Item extends Item {
} }
const ownerDataData = this.actor.data.data; const ownerDataData = this.actor.data.data;
const spellBonus = Number.isNumeric(this.data.data.bonus) ? parseInt(this.data.data.bonus) : undefined; const spellModifier = Number.isNumeric(this.data.data.bonus) ? parseInt(this.data.data.bonus) : undefined;
if (spellBonus === undefined) { if (spellModifier === undefined) {
notifications.info( notifications.info(
getGame().i18n.format("DS4.InfoManuallyEnterSpellBonus", { getGame().i18n.format("DS4.InfoManuallyEnterSpellModifier", {
name: this.name, name: this.name,
spellBonus: this.data.data.bonus, spellModifier: this.data.data.bonus,
}), }),
); );
} }
const spellType = this.data.data.spellType; const spellType = this.data.data.spellType;
const checkTargetNumber = ownerDataData.combatValues[spellType].total + (spellBonus ?? 0); const checkTargetNumber = ownerDataData.combatValues[spellType].total + (spellModifier ?? 0);
const speaker = ChatMessage.getSpeaker({ actor: this.actor, ...options.speaker }); const speaker = ChatMessage.getSpeaker({ actor: this.actor, ...options.speaker });
await createCheckRoll(checkTargetNumber, { await createCheckRoll(checkTargetNumber, {

View file

@ -2,8 +2,7 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { hoursPerDay, minutesPerHour, secondsPerMinute, secondsPerRound } from "../../common/time-helpers"; import { CooldownDuration, DS4SpellDataSourceData } from "../item-data-source";
import { DS4SpellDataSourceData, TemporalUnit, UnitData } from "../item-data-source";
export function calculateSpellPrice(data: DS4SpellDataSourceData): number | null { export function calculateSpellPrice(data: DS4SpellDataSourceData): number | null {
const spellPriceFactor = calculateSpellPriceFactor(data.cooldownDuration); const spellPriceFactor = calculateSpellPriceFactor(data.cooldownDuration);
@ -16,39 +15,18 @@ export function calculateSpellPrice(data: DS4SpellDataSourceData): number | null
return baseSpellPrice === Infinity ? null : baseSpellPrice * spellPriceFactor; return baseSpellPrice === Infinity ? null : baseSpellPrice * spellPriceFactor;
} }
function calculateSpellPriceFactor(temporalData: UnitData<TemporalUnit>): number { function calculateSpellPriceFactor(cooldownDuration: CooldownDuration): number {
let days: number; switch (cooldownDuration) {
if (Number.isNumeric(temporalData.value)) { case "0r":
const value = Number.fromString(temporalData.value); case "1r":
switch (temporalData.unit) { case "2r":
case "days": { case "5r":
days = value; case "10r":
break; case "100r":
} return 1;
case "hours": { case "1d":
days = value / hoursPerDay; return 2;
break; case "d20d":
} return 3;
case "minutes": {
days = value / (hoursPerDay * minutesPerHour);
break;
}
case "rounds": {
days = (value * secondsPerRound) / (hoursPerDay * minutesPerHour * secondsPerMinute);
break;
}
}
} else {
switch (temporalData.unit) {
case "days": {
days = 2;
break;
}
default: {
days = 0;
break;
}
}
} }
return Math.clamped(Math.floor(days), 0, 2) + 1;
} }

View file

@ -4,10 +4,11 @@
import { getGame } from "./helpers"; import { getGame } from "./helpers";
import logger from "./logger"; import logger from "./logger";
import { migrate as migrate001 } from "./migrations/001"; import { migration as migration001 } from "./migrations/001";
import { migrate as migrate002 } from "./migrations/002"; import { migration as migration002 } from "./migrations/002";
import { migrate as migrate003 } from "./migrations/003"; import { migration as migration003 } from "./migrations/003";
import { migrate as migrate004 } from "./migrations/004"; import { migration as migration004 } from "./migrations/004";
import { migration as migration005 } from "./migrations/005";
import notifications from "./ui/notifications"; import notifications from "./ui/notifications";
async function migrate(): Promise<void> { async function migrate(): Promise<void> {
@ -43,11 +44,11 @@ async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion
{ permanent: true }, { permanent: true },
); );
for (const [i, migration] of migrationsToExecute.entries()) { for (const [i, { migrate }] of migrationsToExecute.entries()) {
const currentMigrationVersion = oldMigrationVersion + i + 1; const currentMigrationVersion = oldMigrationVersion + i + 1;
logger.info("executing migration script ", currentMigrationVersion); logger.info("executing migration script ", currentMigrationVersion);
try { try {
await migration(); await migrate();
getGame().settings.set("ds4", "systemMigrationVersion", currentMigrationVersion); getGame().settings.set("ds4", "systemMigrationVersion", currentMigrationVersion);
} catch (err) { } catch (err) {
notifications.error( notifications.error(
@ -73,18 +74,76 @@ async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion
} }
} }
async function migrateCompendiumFromTo(
pack: CompendiumCollection<CompendiumCollection.Metadata>,
oldMigrationVersion: number,
targetMigrationVersion: number,
): Promise<void> {
if (!getGame().user?.isGM) {
return;
}
const migrationsToExecute = migrations.slice(oldMigrationVersion, targetMigrationVersion);
if (migrationsToExecute.length > 0) {
notifications.info(
getGame().i18n.format("DS4.InfoCompendiumMigrationStart", {
pack: pack.title,
currentVersion: oldMigrationVersion,
targetVersion: targetMigrationVersion,
}),
{ permanent: true },
);
for (const [i, { migrateCompendium }] of migrationsToExecute.entries()) {
const currentMigrationVersion = oldMigrationVersion + i + 1;
logger.info("executing compendium migration ", currentMigrationVersion);
try {
await migrateCompendium(pack);
} catch (err) {
notifications.error(
getGame().i18n.format("DS4.ErrorDuringCompendiumMigration", {
pack: pack.title,
currentVersion: oldMigrationVersion,
targetVersion: targetMigrationVersion,
migrationVersion: currentMigrationVersion,
}),
{ permanent: true },
);
logger.error("Failed ds4 compendium migration:", err);
return;
}
}
notifications.info(
getGame().i18n.format("DS4.InfoCompendiumMigrationCompleted", {
pack: pack.title,
currentVersion: oldMigrationVersion,
targetVersion: targetMigrationVersion,
}),
{ permanent: true },
);
}
}
function getTargetMigrationVersion(): number { function getTargetMigrationVersion(): number {
return migrations.length; return migrations.length;
} }
const migrations: Array<() => Promise<void>> = [migrate001, migrate002, migrate003, migrate004]; interface Migration {
migrate: () => Promise<void>;
migrateCompendium: (pack: CompendiumCollection<CompendiumCollection.Metadata>) => Promise<void>;
}
const migrations: Migration[] = [migration001, migration002, migration003, migration004, migration005];
function isFirstWorldStart(migrationVersion: number): boolean { function isFirstWorldStart(migrationVersion: number): boolean {
return migrationVersion < 0; return migrationVersion < 0;
} }
export const migration = { export const migration = {
migrate: migrate, migrate,
migrateFromTo: migrateFromTo, migrateFromTo,
getTargetMigrationVersion: getTargetMigrationVersion, getTargetMigrationVersion,
migrateCompendiumFromTo,
}; };

View file

@ -10,7 +10,7 @@ import {
migrateScenes, migrateScenes,
} from "./migrationHelpers"; } from "./migrationHelpers";
export async function migrate(): Promise<void> { async function migrate(): Promise<void> {
await migrateActors(getActorUpdateData); await migrateActors(getActorUpdateData);
await migrateScenes(getSceneUpdateData); await migrateScenes(getSceneUpdateData);
await migrateCompendiums(migrateCompendium); await migrateCompendiums(migrateCompendium);
@ -39,3 +39,8 @@ function getActorUpdateData(): Record<string, unknown> {
const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData); const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
const migrateCompendium = getCompendiumMigrator({ getActorUpdateData, getSceneUpdateData }); const migrateCompendium = getCompendiumMigrator({ getActorUpdateData, getSceneUpdateData });
export const migration = {
migrate,
migrateCompendium,
};

View file

@ -12,7 +12,7 @@ import {
migrateScenes, migrateScenes,
} from "./migrationHelpers"; } from "./migrationHelpers";
export async function migrate(): Promise<void> { async function migrate(): Promise<void> {
await migrateItems(getItemUpdateData); await migrateItems(getItemUpdateData);
await migrateActors(getActorUpdateData); await migrateActors(getActorUpdateData);
await migrateScenes(getSceneUpdateData); await migrateScenes(getSceneUpdateData);
@ -32,3 +32,8 @@ const migrateCompendium = getCompendiumMigrator(
{ getItemUpdateData, getActorUpdateData, getSceneUpdateData }, { getItemUpdateData, getActorUpdateData, getSceneUpdateData },
{ migrateToTemplateEarly: false }, { migrateToTemplateEarly: false },
); );
export const migration = {
migrate,
migrateCompendium,
};

View file

@ -12,7 +12,7 @@ import {
migrateScenes, migrateScenes,
} from "./migrationHelpers"; } from "./migrationHelpers";
export async function migrate(): Promise<void> { async function migrate(): Promise<void> {
await migrateItems(getItemUpdateData); await migrateItems(getItemUpdateData);
await migrateActors(getActorUpdateData); await migrateActors(getActorUpdateData);
await migrateScenes(getSceneUpdateData); await migrateScenes(getSceneUpdateData);
@ -34,3 +34,8 @@ const migrateCompendium = getCompendiumMigrator(
{ getItemUpdateData, getActorUpdateData }, { getItemUpdateData, getActorUpdateData },
{ migrateToTemplateEarly: false }, { migrateToTemplateEarly: false },
); );
export const migration = {
migrate,
migrateCompendium,
};

View file

@ -12,7 +12,7 @@ import {
migrateScenes, migrateScenes,
} from "./migrationHelpers"; } from "./migrationHelpers";
export async function migrate(): Promise<void> { async function migrate(): Promise<void> {
await migrateItems(getItemUpdateData); await migrateItems(getItemUpdateData);
await migrateActors(getActorUpdateData); await migrateActors(getActorUpdateData);
await migrateScenes(getSceneUpdateData); await migrateScenes(getSceneUpdateData);
@ -21,6 +21,7 @@ export async function migrate(): Promise<void> {
function getItemUpdateData(itemData: Partial<foundry.data.ItemData["_source"]>) { function getItemUpdateData(itemData: Partial<foundry.data.ItemData["_source"]>) {
if (itemData.type !== "spell") return; if (itemData.type !== "spell") return;
// @ts-expect-error the type of cooldownDuration was UnitData<TemporalUnit> at the point for this migration, but it changed later on
const cooldownDurationUnit: string | undefined = itemData.data?.cooldownDuration.unit; const cooldownDurationUnit: string | undefined = itemData.data?.cooldownDuration.unit;
const updateData: Record<string, unknown> = { const updateData: Record<string, unknown> = {
@ -38,3 +39,8 @@ function getItemUpdateData(itemData: Partial<foundry.data.ItemData["_source"]>)
const getActorUpdateData = getActorUpdateDataGetter(getItemUpdateData); const getActorUpdateData = getActorUpdateDataGetter(getItemUpdateData);
const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData); const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
const migrateCompendium = getCompendiumMigrator({ getItemUpdateData, getActorUpdateData, getSceneUpdateData }); const migrateCompendium = getCompendiumMigrator({ getItemUpdateData, getActorUpdateData, getSceneUpdateData });
export const migration = {
migrate,
migrateCompendium,
};

119
src/migrations/005.ts Normal file
View file

@ -0,0 +1,119 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
import { CooldownDuration } from "../item/item-data-source";
import {
getActorUpdateDataGetter,
getCompendiumMigrator,
getSceneUpdateDataGetter,
migrateActors,
migrateCompendiums,
migrateItems,
migrateScenes,
} from "./migrationHelpers";
const secondsPerRound = 5;
const secondsPerMinute = 60;
const roundsPerMinute = secondsPerMinute / secondsPerRound;
const minutesPerHour = 60;
const roundsPerHour = minutesPerHour / roundsPerMinute;
const hoursPerDay = 24;
const roundsPerDay = hoursPerDay / roundsPerHour;
const secondsPerDay = secondsPerMinute * minutesPerHour * hoursPerDay;
async function migrate(): Promise<void> {
await migrateItems(getItemUpdateData);
await migrateActors(getActorUpdateData);
await migrateScenes(getSceneUpdateData);
await migrateCompendiums(migrateCompendium);
}
function getItemUpdateData(itemData: Partial<foundry.data.ItemData["_source"]>) {
if (itemData.type !== "spell") return;
// @ts-expect-error the type of cooldownDuration is changed from UnitData<TemporalUnit> to CooldownDuation with this migration
const cooldownDurationUnit: string | undefined = itemData.data?.cooldownDuration.unit;
// @ts-expect-error the type of cooldownDuration is changed from UnitData<TemporalUnit> to CooldownDuation with this migration
const cooldownDurationValue: string | undefined = itemData.data?.cooldownDuration.value;
const cooldownDuration = migrateCooldownDuration(cooldownDurationValue, cooldownDurationUnit);
const updateData: Record<string, unknown> = {
data: {
cooldownDuration,
},
};
return updateData;
}
function migrateCooldownDuration(cooldownDurationValue?: string, cooldownDurationUnit?: string) {
if (Number.isNumeric(cooldownDurationValue)) {
const value = Number.fromString(cooldownDurationValue!);
const rounds = getRounds(cooldownDurationUnit ?? "", value);
if (rounds * secondsPerRound > secondsPerDay) {
return "d20d";
} else if (rounds > 100) {
return "1d";
} else if (rounds > 10) {
return "100r";
} else if (rounds > 5) {
return "10r";
} else if (rounds > 2) {
return "5r";
} else if (rounds > 1) {
return "2r";
} else if (rounds > 0) {
return "1r";
} else {
return "0r";
}
} else {
// if the value is not numeric, we can only make a best guess
switch (cooldownDurationUnit) {
case "rounds": {
return "10r";
}
case "minutes": {
return "100r";
}
case "hours": {
return "1d";
}
case "days": {
return "d20d";
}
default: {
return "0r";
}
}
}
}
function getRounds(unit: string, value: number): number {
switch (unit) {
case "rounds": {
return value;
}
case "minutes": {
return value * roundsPerMinute;
}
case "hours": {
return value * roundsPerHour;
}
case "days": {
return value * roundsPerDay;
}
default: {
return 0;
}
}
}
const getActorUpdateData = getActorUpdateDataGetter(getItemUpdateData);
const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
const migrateCompendium = getCompendiumMigrator({ getItemUpdateData, getActorUpdateData, getSceneUpdateData });
export const migration = {
migrate,
migrateCompendium,
};

View file

@ -187,10 +187,7 @@
"value": "", "value": "",
"unit": "custom" "unit": "custom"
}, },
"cooldownDuration": { "cooldownDuration": "0r",
"value": "",
"unit": "rounds"
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": null, "wizard": null,

View file

@ -25,7 +25,7 @@ SPDX-License-Identifier: MIT
{{/inline}} {{/inline}}
{{!-- {{!--
!-- Three templates based on the "unit" template for displaying values with unit. !-- Two templates based on the "unit" template for displaying values with unit.
!-- Both accept a `config` object holding the unitNames and unitAbbr instead of !-- Both accept a `config` object holding the unitNames and unitAbbr instead of
!-- directly handing over the latter two. !-- directly handing over the latter two.
!-- @param titleKey: The key of the localized title to use. !-- @param titleKey: The key of the localized title to use.
@ -35,11 +35,6 @@ SPDX-License-Identifier: MIT
titleKey=titleKey}} titleKey=titleKey}}
{{/inline}} {{/inline}}
{{#*inline "customTemporalUnit"}}
{{> unit unitNames=config.i18n.customTemporalUnits unitAbbrs=config.i18n.customTemporalUnitsAbbr unitDatum=unitDatum
titleKey=titleKey}}
{{/inline}}
{{#*inline "distanceUnit"}} {{#*inline "distanceUnit"}}
{{> unit unitNames=config.i18n.distanceUnits unitAbbrs=config.i18n.distanceUnitsAbbr unitDatum=unitDatum {{> unit unitNames=config.i18n.distanceUnits unitAbbrs=config.i18n.distanceUnitsAbbr unitDatum=unitDatum
titleKey=titleKey}} titleKey=titleKey}}
@ -60,16 +55,16 @@ titleKey=titleKey}}
{{!-- spell bonus --}} {{!-- spell bonus --}}
<div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.bonus" <div class="ds4-embedded-document-list__clickable sort-items" data-data-path="data.bonus"
title="{{localize 'DS4.SortBySpellBonus'}}">{{localize 'DS4.SpellBonusAbbr'}}</div> title="{{localize 'DS4.SortBySpellModifier'}}">{{localize 'DS4.SpellModifierAbbr'}}</div>
{{!-- max. distance --}} {{!-- max. distance --}}
<div title="{{localize 'DS4.SpellMaxDistance'}}"><i class="fas fa-ruler"></i></div> <div title="{{localize 'DS4.SpellDistance'}}"><i class="fas fa-ruler"></i></div>
{{!-- duration --}} {{!-- duration --}}
<div title="{{localize 'DS4.SpellDuration'}}"><i class="far fa-clock"></i></div> <div title="{{localize 'DS4.SpellDuration'}}"><i class="far fa-clock"></i></div>
{{!-- cooldown duration --}} {{!-- cooldown duration --}}
<div title="{{localize 'DS4.SpellCooldownDuration'}}"><i class="fas fa-hourglass-half"></i></div> <div title="{{localize 'DS4.CooldownDuration'}}"><i class="fas fa-hourglass-half"></i></div>
{{/systems/ds4/templates/sheets/actor/components/item-list-header.hbs}} {{/systems/ds4/templates/sheets/actor/components/item-list-header.hbs}}
{{#each itemsByType.spell as |itemData id|}} {{#each itemsByType.spell as |itemData id|}}
@ -77,23 +72,24 @@ titleKey=titleKey}}
hideDescription=true}} hideDescription=true}}
{{!-- spell type --}} {{!-- spell type --}}
<img class="ds4-embedded-document-list__image" <img class="ds4-embedded-document-list__image"
src="{{lookup ../../config.icons.spellTypes itemData.data.spellType}}" src="{{lookup @root/config.icons.spellTypes itemData.data.spellType}}"
title="{{lookup ../../config.i18n.spellTypes itemData.data.spellType}}" /> title="{{lookup @root/config.i18n.spellTypes itemData.data.spellType}}" />
{{!-- spell bonus --}} {{!-- spell bonus --}}
<input class="ds4-embedded-document-list__editable change-item" type="text" data-dtype="String" <input class="ds4-embedded-document-list__editable change-item" type="text" data-dtype="String"
data-property="data.bonus" value="{{itemData.data.bonus}}" title="{{localize 'DS4.SpellBonus'}}" /> data-property="data.bonus" value="{{itemData.data.bonus}}" title="{{localize 'DS4.SpellModifier'}}" />
{{!-- max. distance --}} {{!-- max. distance --}}
{{> distanceUnit titleKey='DS4.SpellMaxDistance' unitDatum=itemData.data.maxDistance {{> distanceUnit titleKey='DS4.SpellDistance' unitDatum=itemData.data.maxDistance
config=../../config}} config=@root/config}}
{{!-- duration --}} {{!-- duration --}}
{{> customTemporalUnit titleKey='DS4.SpellDuration' unitDatum=itemData.data.duration config=../../config}} {{> temporalUnit titleKey='DS4.SpellDuration' unitDatum=itemData.data.duration config=@root/config}}
{{!-- cooldown duration --}} {{!-- cooldown duration --}}
{{> temporalUnit titleKey='DS4.SpellCooldownDuration' unitDatum=itemData.data.cooldownDuration <div title="{{localize 'DS4.CooldownDuration'}}">{{lookup @root/config.i18n.cooldownDurations
config=../../config}} itemData.data.cooldownDuration}}</div>
{{/systems/ds4/templates/sheets/actor/components/item-list-entry.hbs}} {{/systems/ds4/templates/sheets/actor/components/item-list-entry.hbs}}
{{/each}} {{/each}}
</ol> </ol>

View file

@ -7,12 +7,16 @@ SPDX-License-Identifier: MIT
<div class="ds4-item-properties ds4-item-properties--spell"> <div class="ds4-item-properties ds4-item-properties--spell">
<h4 class="ds4-item-properties__title">{{localize 'DS4.ItemPropertiesSpell'}}</h4> <h4 class="ds4-item-properties__title">{{localize 'DS4.ItemPropertiesSpell'}}</h4>
<div class="form-group"> <div class="form-group">
<label for="data.bonus-{{data._id}}">{{localize "DS4.SpellBonus"}}</label> <label for="data.bonus-{{data._id}}" title="{{localize 'DS4.SpellModifierDescription'}}">{{localize
<input id="data.bonus-{{data._id}}" data-dtype="String" type="text" name="data.bonus" placeholder="0" "DS4.SpellModifier"}}</label>
value="{{data.data.bonus}}" /> <div class="form-fields">
<input id="data.bonus-{{data._id}}" data-dtype="String" type="text" name="data.bonus" placeholder="0"
value="{{data.data.bonus}}" />
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="data.spellType-{{data._id}}">{{localize "DS4.SpellType"}}</label> <label for="data.spellType-{{data._id}}" title="{{localize 'DS4.SpellTypeDescription'}}">{{localize
"DS4.SpellType"}}</label>
<div class="form-fields"> <div class="form-fields">
<select id="data.spellType-{{data._id}}" name="data.spellType" data-dtype="String"> <select id="data.spellType-{{data._id}}" name="data.spellType" data-dtype="String">
{{#select data.data.spellType}} {{#select data.data.spellType}}
@ -24,7 +28,8 @@ SPDX-License-Identifier: MIT
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="data.spellCategory-{{data._id}}">{{localize "DS4.SpellCategory"}}</label> <label for="data.spellCategory-{{data._id}}" title="{{localize 'DS4.SpellCategoryDescription'}}">{{localize
"DS4.SpellCategory"}}</label>
<div class="form-fields"> <div class="form-fields">
<select id="data.spellCategory-{{data._id}}" name="data.spellCategory" data-dtype="String"> <select id="data.spellCategory-{{data._id}}" name="data.spellCategory" data-dtype="String">
{{#select data.data.spellCategory}} {{#select data.data.spellCategory}}
@ -36,7 +41,7 @@ SPDX-License-Identifier: MIT
</div> </div>
</div> </div>
<div class="form-group slim"> <div class="form-group slim">
<label>{{localize "DS4.SpellMaxDistance"}}</label> <label title="{{localize 'DS4.SpellDistanceDescription'}}">{{localize "DS4.SpellDistance"}}</label>
<div class="form-fields"> <div class="form-fields">
<input data-dtype="String" type="text" name="data.maxDistance.value" <input data-dtype="String" type="text" name="data.maxDistance.value"
value="{{data.data.maxDistance.value}}" /> value="{{data.data.maxDistance.value}}" />
@ -50,7 +55,7 @@ SPDX-License-Identifier: MIT
</div> </div>
</div> </div>
<div class="form-group slim"> <div class="form-group slim">
<label>{{localize "DS4.SpellEffectRadius"}}</label> <label title="{{localize 'DS4.SpellEffectRadiusDescription'}}">{{localize "DS4.SpellEffectRadius"}}</label>
<div class="form-fields"> <div class="form-fields">
<input data-dtype="String" type="text" name="data.effectRadius.value" <input data-dtype="String" type="text" name="data.effectRadius.value"
value="{{data.data.effectRadius.value}}" /> value="{{data.data.effectRadius.value}}" />
@ -64,25 +69,11 @@ SPDX-License-Identifier: MIT
</div> </div>
</div> </div>
<div class="form-group slim"> <div class="form-group slim">
<label>{{localize "DS4.SpellDuration"}}</label> <label title="{{localize 'DS4.SpellDurationDescription'}}">{{localize "DS4.SpellDuration"}}</label>
<div class="form-fields"> <div class="form-fields">
<input data-dtype="String" type="text" name="data.duration.value" value="{{data.data.duration.value}}" /> <input data-dtype="String" type="text" name="data.duration.value" value="{{data.data.duration.value}}" />
<select name="data.duration.unit" data-dtype="String"> <select name="data.duration.unit" data-dtype="String">
{{#select data.data.duration.unit}} {{#select data.data.duration.unit}}
{{#each config.i18n.customTemporalUnits as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
{{/select}}
</select>
</div>
</div>
<div class="form-group slim">
<label>{{localize "DS4.SpellCooldownDuration"}}</label>
<div class="form-fields">
<input data-dtype="String" type="text" name="data.cooldownDuration.value"
value="{{data.data.cooldownDuration.value}}" />
<select name="data.cooldownDuration.unit" data-dtype="String">
{{#select data.data.cooldownDuration.unit}}
{{#each config.i18n.temporalUnits as |value key|}} {{#each config.i18n.temporalUnits as |value key|}}
<option value="{{key}}">{{value}}</option> <option value="{{key}}">{{value}}</option>
{{/each}} {{/each}}
@ -90,8 +81,21 @@ SPDX-License-Identifier: MIT
</select> </select>
</div> </div>
</div> </div>
<div class="form-group">
<label for="data.cooldownDuration-{{data._id}}"
title="{{localize 'DS4.CooldownDurationDescription'}}">{{localize "DS4.CooldownDuration"}}</label>
<div class="form-fields">
<select id="data.cooldownDuration-{{data._id}}" name="data.cooldownDuration" data-dtype="String">
{{#select data.data.cooldownDuration}}
{{#each config.i18n.cooldownDurations as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
{{/select}}
</select>
</div>
</div>
<div class="form-group slim"> <div class="form-group slim">
<label>{{localize "DS4.SpellMinimumLevel"}}</label> <label title="{{localize 'DS4.SpellMinimumLevelDescription'}}">{{localize "DS4.SpellMinimumLevel"}}</label>
<div class="form-fields"> <div class="form-fields">
<label for="data.minimumLevels.healer-{{data._id}}">{{localize "DS4.SpellCasterClassHealer"}}</label> <label for="data.minimumLevels.healer-{{data._id}}">{{localize "DS4.SpellCasterClassHealer"}}</label>
<input id="data.minimumLevels.healer-{{data._id}}" data-dtype="Number" type="number" min="0" step="1" <input id="data.minimumLevels.healer-{{data._id}}" data-dtype="Number" type="number" min="0" step="1"
@ -105,7 +109,10 @@ SPDX-License-Identifier: MIT
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="data.price-{{data._id}}">{{localize "DS4.SpellPrice"}}</label> <label for="data.price-{{data._id}}" title="{{localize 'DS4.SpellPriceDescription'}}">{{localize
<span id="data.price-{{data._id}}">{{data.data.price}}</span> "DS4.SpellPrice"}}</label>
<div class="form-fields">
<span id="data.price-{{data._id}}">{{data.data.price}}</span>
</div>
</div> </div>
</div> </div>