diff --git a/README.md b/README.md
index 0ab02547..4267ab64 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,52 @@ See our current production goals and progress [here](https://github.com/StarWars
Release `CHANGELOG` can be found [here](https://github.com/StarWarsFoundryVTT/StarWarsFFG/releases)
+- 10/03/2021 - Cstadther - Fix 828 - Fixed issue where qualities were not being imported for vehicles correctly.
+- 09/03/2021 - Cstadther - Fix 820 - Updated RegEx for monetary value scrubbing to allow Safari.
+- 23/02/2021 - Cstadther - Fix OggDude Character import changing owned item ids when reimporting characters.
+- 17/02/2021 - Cstadther - Fix 803 - Fixed issue where specialization talents were duplicating modifiers on each import.
+- 15/02/2021 - Cstadther - Fix 797 - Fixed issue where talent activations on custom specializations were not displaying activation on talent list.
+- 15/02/2021 - Cstadther - Fix 795 - Fixed issue where custom one-off skills disappear on refresh.
+- 11/02/2021 - Cstadther - Enhancment 709 - Updated connectors CSS for Specializations, Force Powers and Signature Abilities
+- 11/02/2021 - Cstadther - Fix 701 - Fixed issue where in Standard UI destiny drop down doesn't expand up when list options expands off screen.
+- 08/02/2021 - Cstadther - Fix 774 - Fixed issue where setting characteristic to 0 was not setting the value to 0.
+- 08/02/2021 - Cstadther - Fix 773 - Fixed issue with Obligation/Duty table on GroupManager
+- 07/02/2021 - Cstadther - Fix 770 - Fixed issue where Knowledge skill modifiers were not being created correctly.
+- 05/02/2021 - Cstadther - Fix 764/765 - Fixed issues where skills were not sorting by translated name. Fixed issue where straggler skills were not being grouped together.
+- 05/02/2021 - Cstadhter - Fix 759 - Fixed issue where unable to add modifiers to item attachments.
+- 05/02/2021 - Cstadther - Fix 757 - Fixed issue where modifiers were ignored when rolling from macro.
+- 05/02/2021 - Cstadther - Fix 756 - Custom Destiny Pool Names were reverting on closing Group Manager.
+- 03/02/2021 - Cstadther - Enhancement 717 - Added ability to decode SWA dice symbols, example `:boost:`
+- 03/02/2021 - Cstadhter - Fix 744 - Fixed issue where roll and expanded data cards were displaying incorrect rank for qualities.
+- 03/02/2021 - Cstadther - Fix 743 - Fixed issue to where damaged weapons were not applying a difficulty die addition.
+- 03/02/2021 - Cstadther - Fix 723/724 - Fixed SWA Import issues with soak and added ability for minions (Rivals on Minion Sheet) to have criticals, shows up as a sheet option, but for import defaults to true.
+- 02/02/2021 - Cstadther - Enhancement 732 - Added Triumph and Despair to die roller.
+- 01/02/2021 - Cstadther - Fix 725 - Resolved issue with SW Adversary's soak, now will automatically disable soak calculation on import.
+- 01/02/2021 - Cstadther - Fix 727 - Fixed issue where adding multiple owned items to a character with the same id resulted in only 1 entry.
+- 01/02/2021 - Cstadther - Fix 720 - Fixed issue where additional modifiers were being incorrectly added.
+- 01/02/2021 - Cstadther - Fix 705 - Switched character import compendium item lookup to lookup specific template type.
+- 01/02/2021 - Cstadther - Fix 718 - Reimporting was causing duplicate modifiers.
+- 30/01/2021 - Cstadther - Fix 726 - Fixed issue with available hardpoint calculation.
+- 30/01/2021 - Cstadther - Fix 710 - Fixed issue where talent boxes split button was hidden behind the box.
+- 30/01/2021 - Cstadther - Fix 708 - Fixed issue where specialization modifiers were not able to be deleted.
+- 30/01/2021 - Cstadther - Fix 707 - Fixed issue with Lightsabers using brawn not calculation damage correctly.
+- 29/01/2021 - Cstadther - Enhancement 616 - Added Roll Only modifiers to weapons under `Roll Modifiers`, `Dice Modifiers` and `Result Modifiers`
+- 26/01/2021 - Cstadther - Added a new Hook `ffgDiceMessage` which fires after a dice .tomessage function call.
+- 25/01/2021 - Cstadther - Enhancement 638 / Enhancement 639 - Added setting for Destiny Pool labels and Enable Force Die.
+- 25/01/2021 - Cstadther - Enhancement 672 - Added saving sound path on item roll (ie Weapons)
+- 25/01/2021 - Cstadther - Enhancement 659 - Created drop down menu on destiny pool, with group manager and roll for destiny options. Removed top and bottom bar and add dragging to entire box, except for clickable items, adjusted cursors to match.
+- 24/01/2021 - Cstadther - Enhancement 640 - Changed location of UI settings to its own form, add customizable Paused image setting.
+- 23/01/2021 - Cstadther - Enhancement 646 - Added ability to change default stylesheet, added @Mandar theme as option.
+- 22/01/2021 - Cstadther - Refactored OggDude importer, and changed import based on import key instead of name.
+- 20/01/2021 - Cstadther - Enhancement @Shyster Effd#5441 Provided an enhancement for SWA Importer to correct create armor.
+- 19/01/2021 - Cstadther - Fixed issue where Force Modifer was not being calculated correctly.
+- 18/01/2021 - Cstadther - Fix 644 - Fixed issue with import vehicle encumbrance.
+- 17/01/2021 - Cstadther - Fix 651 - Fixed issue where users could not display read-only journal entries.
+- 17/01/2021 - Cstadther - Fix 648 - Fixed issue where qualities were reset on drag/drop.
+- 16/01/2021 - Cstadther - Fix 636 - Fixed issues where Social Skill category is not longer being displays, also affected Labels on some of the skills.
+- 16/01/2021 - Cstadther - Added migration for alternate skill lists.
+- 16/01/2021 - Cstadther - Enhancement 633 - Added quality rendering to `Send To Chat` template.
+- 16/01/2021 - Cstadther - Added Item Attachments and styling to support it.
- 15/01/2021 - Cstadther - Added code to fix save/display issues with compendium items.
- 08/01/2021 - Cstadther - Added new paused image.
- 04/01/2021 - Cstadther - Fix for Melee-* skills not calculating damage correctly when based on Brawn.
diff --git a/images/bgSheet.jpg b/images/bgSheet.jpg
new file mode 100644
index 00000000..b04f29d1
Binary files /dev/null and b/images/bgSheet.jpg differ
diff --git a/images/bgWindow.jpg b/images/bgWindow.jpg
new file mode 100644
index 00000000..82e176e6
Binary files /dev/null and b/images/bgWindow.jpg differ
diff --git a/images/mod-all.png b/images/mod-all.png
new file mode 100644
index 00000000..e825b939
Binary files /dev/null and b/images/mod-all.png differ
diff --git a/images/mod-armor.png b/images/mod-armor.png
new file mode 100644
index 00000000..19fde283
Binary files /dev/null and b/images/mod-armor.png differ
diff --git a/images/mod-gear.png b/images/mod-gear.png
new file mode 100644
index 00000000..f56e014c
Binary files /dev/null and b/images/mod-gear.png differ
diff --git a/images/mod-vehicle.png b/images/mod-vehicle.png
new file mode 100644
index 00000000..5c59f259
Binary files /dev/null and b/images/mod-vehicle.png differ
diff --git a/images/mod-weapon.png b/images/mod-weapon.png
new file mode 100644
index 00000000..1d5a2811
Binary files /dev/null and b/images/mod-weapon.png differ
diff --git a/lang/de.json b/lang/de.json
index 1e0476fc..a33e29a4 100644
--- a/lang/de.json
+++ b/lang/de.json
@@ -115,7 +115,7 @@
"SWFFG.VehicleDefensePort": "BACKB",
"SWFFG.VehicleDefenseStarboard": "STEUERB",
"SWFFG.VehicleAttachmentHardPointRequired": "AP ben.",
- "SWFFG.SkillsNameAstrogation": "Astrognavigation",
+ "SWFFG.SkillsNameAstrogation": "Astronavigation",
"SWFFG.SkillsNameAthletics": "Athletik",
"SWFFG.SkillsNameBrawl": "Handgemenge",
"SWFFG.SkillsNameBrawlAbbreviation": "H",
@@ -138,7 +138,7 @@
"SWFFG.SkillsNameKnowledgeSociety": "Wissen: Gesellschaft",
"SWFFG.SkillsNameKnowledgeTheNet": "Wissen: Das Netz",
"SWFFG.SkillsNameKnowledgeUnderworld": "Wissen: Unterwelt",
- "SWFFG.SkillsNameKnowledgeWarfare": "Wissen: Warfare",
+ "SWFFG.SkillsNameKnowledgeWarfare": "Wissen: Kriegskunst",
"SWFFG.SkillsNameKnowledgeXenology": "Wissen: Xenologie",
"SWFFG.SkillsNameKnowledgeCoreWorldsStripped": "Kernwelten",
"SWFFG.SkillsNameKnowledgeEducationStripped": "Allgemeinbildung",
@@ -148,7 +148,7 @@
"SWFFG.SkillsNameKnowledgeWarfareStripped": "Kriegskunst",
"SWFFG.SkillsNameKnowledgeXenologyStripped": "Xenologie",
"SWFFG.SkillsNameLeadership": "Führungsqualität",
- "SWFFG.SkillsNameLightsaber": "Lichtschwert",
+ "SWFFG.SkillsNameLightsaber": "Lichtschwerter",
"SWFFG.SkillsNameLightsaberAbbreviation": "LS",
"SWFFG.SkillsNameMechanics": "Mechanik",
"SWFFG.SkillsNameMedicine": "Medizin",
@@ -186,9 +186,9 @@
"SWFFG.VehicleFiringArcDorsal": "Rückseite",
"SWFFG.VehicleFiringArcVentral": "Vorderseite",
"SWFFG.VehicleFiringArcAll": "Alle",
- "SWFFG.DifficultySimple": "Simpel",
+ "SWFFG.DifficultySimple": "Trivial",
"SWFFG.DifficultyEasy": "Einfach",
- "SWFFG.DifficultyAverage": "Durchschnittlich",
+ "SWFFG.DifficultyAverage": "Mittelschwer",
"SWFFG.DifficultyHard": "Schwer",
"SWFFG.DifficultyDaunting": "Sehr schwer",
"SWFFG.DifficultyFormidable": "Extrem",
@@ -275,8 +275,8 @@
"SWFFG.TalentSource": "Talent Rank Sources for",
"SWFFG.EnableObligation": "Aktiviere Verpflichtung",
"SWFFG.EnableObligationHint": "Zeigt das Verpflichtung-Feld auf dem Charakterblatt an",
- "SWFFG.EnableDuty": "Enable Duty",
- "SWFFG.EnableDutyHint": "Enables the Duty fields on the character sheet",
+ "SWFFG.EnableDuty": "Aktiviere Pflicht",
+ "SWFFG.EnableDutyHint": "Zeigt das Plicht-Feld auf dem Charakterblatt an",
"SWFFG.EnableMorality": "Aktiviere Moral",
"SWFFG.EnableMoralityHint": "Zeigt das Moral-Feld auf dem Charakterblatt an",
"SWFFG.EnableConflict": "Aktiviere Konflikt",
@@ -287,5 +287,174 @@
"SWFFG.Consumables": "Verpflegung",
"SWFFG.Days": "Tage",
"SWFFG.Months": "Monate",
- "SWFFG.Years": "Jahre"
+ "SWFFG.Years": "Jahre",
+ "SWFFG.InitiativeRollResult": "rollt Initiative!",
+ "SWFFG.RollResultSuccess" : "Erfolge",
+ "SWFFG.RollResultFailure" : "Misserfolge",
+ "SWFFG.RollResultAdvantage" : "Vorteile",
+ "SWFFG.RollResultThreat" : "Nachteile",
+ "SWFFG.RollResultTriumph" : "Triumphe",
+ "SWFFG.RollResultDespair" : "Verzweiflung",
+ "SWFFG.RollResultLight" : "Hell",
+ "SWFFG.RollResultDark" : "Dunkel",
+ "SWFFG.TalentsCurrentTier": "Current Tier",
+ "SWFFG.Ranked": "Ranked",
+ "SWFFG.Unequipped": "Nicht ausgerüstet",
+ "SWFFG.SettingsDiceTheme": "Wähle das Würfelthema",
+ "SWFFG.SettingsDiceThemeHint": "Switch between Star Wars or Genesys style dice.",
+ "SWFFG.ForceTalent": "Machttalent",
+ "SWFFG.SkillChangeCharacteristicContextItem": "Ändere Characteristik",
+ "SWFFG.SkillAddContextItem": "Füge Fähigkeit hinzu",
+ "SWFFG.SkillAddDialogTitle": "Erzeuge neue Fähigkeit",
+ "SWFFG.SkillRemoveContextItem": "Entferne Fähigkeit",
+ "SWFFG.DefenseRangedLabel": "Verteidigung (Fernkampf)",
+ "SWFFG.DefenseMeleeLabel": "Verteidigung (Nahkampf)",
+ "SWFFG.ModTypeCareerSkill": "Karrierefähigkeit",
+ "SWFFG.ModTypeSkillSetback": "Fähigkeitsnachteil",
+ "SWFFG.ModTypeSkillRemoveSetback": "Fähigkeitsnachteil entfernen",
+ "SWFFG.ModTypeSkillForceBoost": "Machtfähigkeit Boost",
+ "SWFFG.ItemsVehicles": "Fahrzeuge",
+ "SWFFG.ItemDescriptors": "Qualität der Gegenstände",
+ "SWFFG.SettingsSkillTheme": "Wähle Fähigkeitsthema",
+ "SWFFG.SettingsSkillThemeHint": "Wechsel zwischen Fähigkeitssätzen",
+ "SWFFG.SettingsOggDudeImporter": "Datenimport",
+ "SWFFG.SettingsOggDudeImporterLabel": "OggDude Dataset Importer",
+ "SWFFG.SettingsOggDudeImporterHint": "Importiere Daten eines OggDude Datasets zu Foundry",
+ "SWFFG.SettingsSWAdversariesImporter": "Gegner Import",
+ "SWFFG.SettingsSWAdversariesImporterLabel": "Gegner Importer",
+ "SWFFG.SettingsSWAdversariesImporterHint": "Import data from an SW Adversaries typed data sets into Foundry",
+ "SWFFG.SettingsSkillListImporter": "Skill List Import",
+ "SWFFG.SettingsSkillListImporterLabel": "Skill List Importer",
+ "SWFFG.SettingsSkillListImporterHint": "Import a skill list file to add to skill themes",
+ "SWFFG.SkillsNameAlchemy": "Alchemie",
+ "SWFFG.SkillsNameAstrocartography": "Astrokartographie",
+ "SWFFG.SkillsNameDriving": "Fahren",
+ "SWFFG.SkillsNameOperating": "Operating",
+ "SWFFG.SkillsNamePiloting": "Pilotieren",
+ "SWFFG.SkillsNameRiding": "Reiten",
+ "SWFFG.SkillsNameArcana": "Arcana",
+ "SWFFG.SkillsNameDivine": "Divine",
+ "SWFFG.SkillsNamePrimal": "Primal",
+ "SWFFG.SkillsNameMeleeHeavyAbbreviation": "FS",
+ "SWFFG.SkillsNameMeleeHeavy": "Fenrkampf: Schwer",
+ "SWFFG.SkillsNameMeleeLightAbbreviation": "FL",
+ "SWFFG.SkillsNameMeleeLight": "Fernkampf: Leicht",
+ "SWFFG.SkillsNameRangedAbbreviation": "E",
+ "SWFFG.SkillsNameRanged": "Entfernt",
+ "SWFFG.SkillsNameKnowledge": "Wissen",
+ "SWFFG.SkillsMagic": "Magiefähigkeiten",
+ "SWFFG.SkillsSocial": "Sozial",
+ "SWFFG.TalentTier": "Stufe",
+ "SWFFG.TalentCurrentTier": "Derzeitige Stufe",
+ "SWFFG.TalentsConflict": "Konflikt?",
+ "SWFFG.ConsumablesAmount": "Menge",
+ "SWFFG.ConsumablesDuration": "Dauer",
+ "SWFFG.UseBrawnBaseDamage": "Stärke?",
+ "SWFFG.SignatureAbilityName": "Signaturfähigkeitsname",
+ "SWFFG.SignatureAbilityBaseAbility": "Basisfähigkeit",
+ "SWFFG.SignatureAbilityUpgradeName": "Fähigkeitssteigerungsname",
+ "SWFFG.SignatureAbility": "Signaturfähigkeit",
+ "SWFFG.SignatureAbilityUpgrades": "Signaturfähigkeitssteigerungen",
+ "SWFFG.DowngradeAbility": "Verschlechtern der Fähigkeit",
+ "SWFFG.DowngradeDifficulty": "Verkleinern der Schwierigkeit",
+ "SWFFG.SendToChat": "Zum Chat senden",
+ "SWFFG.ChooseInitiative": "Wähle die Initiativemethode",
+ "SWFFG.ResetSkillList": "Reset Skills List",
+ "SWFFG.Advantage": "Vorteil",
+ "SWFFG.Success": "Erfolg",
+ "SWFFG.Threat": "Nachteil",
+ "SWFFG.Failure": "Fehlschlag",
+ "SWFFG.SkillImportInstructions1": "You can IMPORT a skill list JSON data file to add a new skill theme or update an existing one.",
+ "SWFFG.SkillImportInstructions2": "You can RESET the skills lists to system default by clicking the Reset Skills List button.",
+ "SWFFG.SkillImportInstructions3": "Please see documentation for skill list schema",
+ "SWFFG.SkillImportSource": "Skill List JSON Source File",
+ "SWFFG.SkillImportTheme": "Skill Theme",
+ "SWFFG.SkillImportActive": "Active?",
+ "SWFFG.SkillDownload": "Download",
+ "SWFFG.ModTypeSkillAddAdvantage": "Skill Add Advantage",
+ "SWFFG.ModTypeSkillAddDark": "Skill Add Dark",
+ "SWFFG.ModTypeSkillAddFailure": "Skill Add Failure",
+ "SWFFG.ModTypeSkillAddLight": "Skill Add Light",
+ "SWFFG.ModTypeSkillAddSuccess": "Skill Add Success",
+ "SWFFG.ModTypeSkillAddThreat": "Skill Add Threat",
+ "SWFFG.SendForceRollToChat": "Würfle Machtwurf",
+ "SWFFG.InitiativeRoll": "Würfle Initiative",
+ "SWFFG.InitiativePoolSelectorHint": "Select the base skill to use for Initiative",
+ "SWFFG.InitiativePoolAddsHint": "Add Success/Advantages to Roll. Left click to increase dice, right click to decrease.",
+ "SWFFG.RequestDestinyRoll": "Verlange Würfelwurf",
+ "SWFFG.DestinyAlreadyRolled": "Du hast für den Schicksalspool bereits gewürfelt!",
+ "SWFFG.DestinyPoolRoll": "Hier clicken um für den Schicksalspool zu würfeln",
+ "SWFFG.SkillAddAsInitiative": "Toggle for use as initiative",
+ "SWFFG.ItemWeaponStatus": "Status",
+ "SWFFG.ItemStatusNone": "Kein Schaden",
+ "SWFFG.ItemStatusMinor": "Geringer Schaden",
+ "SWFFG.ItemStatusModerate": "Mittlerer Schaden",
+ "SWFFG.ItemStatusMajor": "Starker Schaden",
+ "SWFFG.ItemTooDamagedToUse": "zu beschädigt um es zu nutzen",
+ "SWFFG.Gender": "Geschlecht",
+ "SWFFG.Age": "Alter",
+ "SWFFG.Height": "Höhe",
+ "SWFFG.Build": "Figur",
+ "SWFFG.Hair": "Haare",
+ "SWFFG.Eyes": "Augen",
+ "SWFFG.NotableFeature": "Markante Züge",
+ "SWFFG.Notes": "Anderes",
+ "SWFFG.Motivations": "Motivationen",
+ "SWFFG.MotivationCategory": "Kategorie",
+ "SWFFG.MotivationType": "Typ",
+ "SWFFG.MotivationDescription": "Beschreibung",
+ "SWFFG.EmotionalStrength": "Emotionale Stärke",
+ "SWFFG.EmotionalWeakness": "Emotionale Schwäche",
+ "SWFFG.Strength": "Stärke",
+ "SWFFG.Flaw": "Schwäche",
+ "SWFFG.Desire": "Verlangen",
+ "SWFFG.Fear": "Angst",
+ "SWFFG.Type": "Typ",
+ "SWFFG.Magnitude": "Ausmaß",
+ "SWFFG.AdversariesImportInstructions1": "Import data based on Stoogoff's data format (http://swa.stoogoff.com/).",
+ "SWFFG.AdversariesImportInstructions2": "Download the entire source as a zip file to use for import.",
+ "SWFFG.Skills": "Fähigkeiten",
+ "SWFFG.GroupManager": "Gruppen Manager",
+ "SWFFG.EnableRollAudio": "Roll - Allow users to add roll audio",
+ "SWFFG.EnableRollAudioHint": "Allow users to add roll audio from the rolling dialog",
+ "SWFFG.EnableRollAudioPlaylist": "Roll - Playlist users use for roll audio",
+ "SWFFG.EnableRollAudioPlaylistHint": "This is the playlist that users are given access to, from which they can add audio to rolls.",
+ "SWFFG.SentDicePoolRoll": "Hier klicken um den Würfelpool zu würfeln.",
+ "SWFFG.SentDicePoolRollHint": "Ein Würfelpool wurde für Dich zum Würfeln zugewiesen.",
+ "SWFFG.TabGeneral": "Basis Information",
+ "SWFFG.TabObligationDutyMorality": "Obligation/Verpflichtung/Moral",
+ "SWFFG.SkillsNameKnowledgeAdventuring": "Auf Abenteuer",
+ "SWFFG.SkillsNameKnowledgeAdventuringAbbrev": "Abent",
+ "SWFFG.SkillsNameKnowledgeForbidden": "Verboten",
+ "SWFFG.SkillsNameKnowledgeForbiddenAbbrev": "Vbtn",
+ "SWFFG.SkillsNameKnowledgeGeography": "Geographie",
+ "SWFFG.SkillsNameKnowledgeGeographyAbbrev": "Geogr",
+ "SWFFG.SkillsNameRunes": "Runen",
+ "SWFFG.SkillsNameVerse": "Verse",
+ "SWFFG.SkillsNameAember": "Æmbercraft",
+ "SWFFG.SkillsNameAemberAbbrev": "ÆmCrft",
+ "SWFFG.SkillsNameKnowledgeAember": "Æmber",
+ "SWFFG.SkillsNameKnowledgeCrucible": "Crucible",
+ "SWFFG.SkillsNameKnowledgeCulture": "Kultur",
+ "SWFFG.ModifierAllSkills": "Alle Fertigkeiten",
+ "SWFFG.IsRestricted": "Eingeschränkt?",
+ "SWFFG.EnableSortTalentsByActivationGlobal": "Sortiere Talente (Global)",
+ "SWFFG.EnableSortTalentsByActivation": "Sortiere Talente",
+ "SWFFG.EnableSortTalentsByActivationHint": "Gruppiere Talente nach Aktivierung",
+ "SWFFG.UseGlobalSetting": "Benutze Global",
+ "SWFFG.OptionValueYes": "Ja",
+ "SWFFG.OptionValueNo": "Nein",
+ "SWFFG.DestinyAddString1" : "Fügte einen ",
+ "SWFFG.DestinyAddString2" : "n Punkt hinzu.",
+ "SWFFG.DestinyRemString1" : "Entfernte einen ",
+ "SWFFG.DestinyRemString2" : "n Punkt",
+ "SWFFG.DestinyLightside": "Heller",
+ "SWFFG.DestinyDarkside": "Dunkler",
+ "SWFFG.DestinyTurned" : "Schicksalspunkt umgedreht: ",
+ "SWFFG.DestinyAdd" : "Fügte einen Punkt hinzu: ",
+ "SWFFG.DestinyRem" : "Entfernte einen Punkt: ",
+ "SWFFG.DestinyRemainL" : "Helle Punkte verbleibend: ",
+ "SWFFG.DestinyRemainD" : "Dunkle Punkte verbleibend: ",
+ "SWFFG.RollSucceeded" : "Probe erfolgreich!",
+ "SWFFG.RollFailure" : "Probe misslungen!"
}
diff --git a/lang/en.json b/lang/en.json
index 62e2d0d7..2fec59a1 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -236,6 +236,11 @@
"SWFFG.GrantXP": "Grant XP",
"SWFFG.GrantXPTo": "Grant XP to",
"SWFFG.GrantXPToAllCharacters": "Grant XP to all characters",
+ "SWFFG.ObligationTable": "Obligation Table",
+ "SWFFG.DutyTable": "Duty Table",
+ "SWFFG.Range": "Range",
+ "SWFFG.Triggered": "Triggered",
+ "SWFFG.For": "For",
"SWFFG.Cancel": "Cancel",
"SWFFG.Specialization": "Specialization",
"SWFFG.SkillCharacteristicDialogTitle": "Change Characteristic For:",
@@ -270,6 +275,8 @@
"SWFFG.Cost": "COST:",
"SWFFG.EnableSoakCalc": "Enable Soak Auto-Calculation",
"SWFFG.EnableSoakCalcHint": "Enable soak auto-calculation and unload field for manual entry",
+ "SWFFG.EnablePrivateTriggers": "Enable Private Group Manager Table Rolls",
+ "SWFFG.EnablePrivateTriggersHint": "Enable making Obligation/Duty/Morality Table Rolls from group manager private so that players do not see the result",
"SWFFG.EnableDebug": "Enable console debugging",
"SWFFG.EnableDebugHint": "Enable debugging messages in console, use for diagnostic purposes only",
"SWFFG.Talent": "Talent",
@@ -373,12 +380,12 @@
"SWFFG.InitiativeRollResult": "rolls for Initiative!",
"SWFFG.InitiativePoolSelectorHint": "Select the base skill to use for Initiative",
"SWFFG.InitiativePoolAddsHint": "Add Success/Advantages to Roll. Left click to increase dice, right click to decrease.",
- "SWFFG.RequestDestinyRoll": "Request Roll",
+ "SWFFG.RequestDestinyRoll": "Request Destiny Roll",
"SWFFG.DestinyAlreadyRolled": "You have already rolled for the Destiny Pool!",
"SWFFG.DestinyPoolRoll": "Click here to roll for the Destiny Pool",
"SWFFG.SkillAddAsInitiative": "Toggle for use as initiative",
"SWFFG.ItemWeaponStatus" : "Status",
- "SWFFG.ItemStatusNone" : "No Damage",
+ "SWFFG.ItemStatusNone" : "Undamaged",
"SWFFG.ItemStatusMinor" : "Minor Damage",
"SWFFG.ItemStatusModerate" : "Moderate Damage",
"SWFFG.ItemStatusMajor" : "Major Damage",
@@ -437,11 +444,67 @@
"SWFFG.SkillsNameKnowledgeCrucible": "Crucible",
"SWFFG.SkillsNameKnowledgeCulture": "Culture",
"SWFFG.ModifierAllSkills": "All Skills",
- "SWFFG.IsRestricted": "Restricted?",
+ "SWFFG.IsRestricted": "Restricted",
"SWFFG.EnableSortTalentsByActivationGlobal": "Sort Talents (Global)",
"SWFFG.EnableSortTalentsByActivation": "Sort Talents",
"SWFFG.EnableSortTalentsByActivationHint": "Enable grouping Talents by activation",
"SWFFG.UseGlobalSetting": "Use Global",
"SWFFG.OptionValueYes": "Yes",
- "SWFFG.OptionValueNo": "No"
+ "SWFFG.OptionValueNo": "No",
+ "SWFFG.ModTypeAll" : "All",
+ "SWFFG.ItemQualities": "Qualities",
+ "SWFFG.ItemAttachments": "Attachments",
+ "SWFFG.TabConfiguration": "Configuration",
+ "SWFFG.TabBaseModifiers": "Base Mods",
+ "SWFFG.TabAdditionalModifiers": "Add Mods",
+ "SWFFG.ModCount" : "Count",
+ "SWFFG.ModTypeResultModifiers": "Result Mods",
+ "SWFFG.ModTypeDiceModifiers": "Dice Mods",
+ "SWFFG.ModTypeRollModifiers": "Roll Mods",
+ "SWFFG.ModTypeAddBoost": "Add Boost",
+ "SWFFG.ModTypeAddSetback": "Add Setback",
+ "SWFFG.ModTypeAddDifficulty": "Add Diffculty",
+ "SWFFG.ModTypeUpgradeDifficulty": "Upgrade Diffculty",
+ "SWFFG.ModTypeDowngradeDifficulty": "Downgrade Diffculty",
+ "SWFFG.ModTypeUpgradeAbility": "Upgrade Ability",
+ "SWFFG.ModTypeDowngradeAbility": "Downgrade Ability",
+ "SWFFG.ModTypeAddAdvantage": "Add Advantage",
+ "SWFFG.ModTypeAddDark": "Add Dark",
+ "SWFFG.ModTypeAddFailure": "Add Failure",
+ "SWFFG.ModTypeAddLight": "Add Light",
+ "SWFFG.ModTypeAddSuccess": "Add Success",
+ "SWFFG.ModTypeAddThreat": "Add Threat",
+ "SWFFG.ModTypeStatArmor" : "Armor Stat",
+ "SWFFG.FromAttachment": "From Attachment",
+ "SWFFG.WeaponCardCritical" : "Critical",
+ "SWFFG.ItemModifiers" : "Item Modifiers",
+ "SWFFG.BaseMods": "Base Mods",
+ "SWFFG.Count": "Count",
+ "SWFFG.SettingsUITheme": "Choose UI Theme",
+ "SWFFG.SettingsUIThemeHint": "Switch between different UI styles.",
+ "SWFFG.UISettings": "User Interface Settings",
+ "SWFFG.UISettingsHint": "Modify default UI behavior.",
+ "SWFFG.UISettingsLabel": "UI Settings Manager",
+ "SWFFG.SettingsDestinyLight": "Destiny Light Side",
+ "SWFFG.SettingsDestinyLightHint": "Change the text on the Destiny Pool for Light Side",
+ "SWFFG.SettingsDestinyDark": "Destiny Dark Side",
+ "SWFFG.SettingsDestinyDarkHint": "Change the text on the Destiny Pool for Dark Side",
+ "SWFFG.SettingsEnableForceDie" : "Enable Force Die in Roller",
+ "SWFFG.SettingsEnableForceDieHint" : "Enables/Disables White Force Die in Roller (NOTE: If Dice theme is Star Wars Force Die will automatically be enabled)",
+ "SWFFG.SettingsPausedImage": "Paused Icon",
+ "SWFFG.SettingsPausedImageHint": "Change the paused icon by selecting an image replacement.",
+ "SWFFG.Triumph" : "Triumph",
+ "SWFFG.Despair" : "Despair",
+ "SWFFG.EnableCriticalInjuries" : "Enable Critical Injuries",
+ "SWFFG.EnableCriticalInjuriesHint" : "Enable the Critical Injuries section",
+ "SWFFG.ModTypeSkillAddTriumph" : "Skill Add Triumph",
+ "SWFFG.ModTypeAddTriumph" : "Add Triumph",
+ "SWFFG.ModTypeSkillAddDespair" : "Skill Add Despair",
+ "SWFFG.ModTypeAddDespair" : "Add Despair",
+ "SWFFG.AdversariesImportInstructions3" : "Filter the incoming data by a tag (ie setting:Fantasy), leave blank to import all",
+ "SWFFG.DicePoolAddsHint": "Left click to increase dice, right click to decrease.",
+ "SWFFG.RollSucceeded": "Check succeeded!",
+ "SWFFG.RollFailure": "Check failed!",
+ "SWFFG.DestinyTrackerHint": "Left click to flip, SHIFT+left click to add, CTRL+left click to remove."
}
+
diff --git a/lang/es.json b/lang/es.json
index 7b1bf28b..add1b52e 100644
--- a/lang/es.json
+++ b/lang/es.json
@@ -215,7 +215,7 @@
"SWFFG.Modifier": "Modificador",
"SWFFG.ModifierValue": "Valor",
"SWFFG.DestinyPool": "Reserva de Destino",
- "SWFFG.Lightside": "Luz",
+ "SWFFG.Lightside": "Luminoso",
"SWFFG.Darkside": "Oscuro",
"SWFFG.PlayerCharacters": "Personajes",
"SWFFG.DescriptionObligationAbrev": "Obl",
@@ -236,6 +236,11 @@
"SWFFG.GrantXP": "Repartir PE",
"SWFFG.GrantXPTo": "Dar PE a",
"SWFFG.GrantXPToAllCharacters": "Dar PE a todos los PJs",
+ "SWFFG.ObligationTable": "Tabla Obligación",
+ "SWFFG.DutyTable": "Tabla Deber",
+ "SWFFG.Range": "Alcance",
+ "SWFFG.Triggered": "Activa",
+ "SWFFG.For": "Para",
"SWFFG.Cancel": "Cancelar",
"SWFFG.Specialization": "Especialización",
"SWFFG.SkillCharacteristicDialogTitle": "Cambiar Característica a:",
@@ -270,6 +275,8 @@
"SWFFG.Cost": "PRECIO:",
"SWFFG.EnableSoakCalc": "Activar Protección Auto",
"SWFFG.EnableSoakCalcHint": "Activar cálculo automático de protección, sin posibilidad de entrada manual.",
+ "SWFFG.EnablePrivateTriggers": "Activar Tirada Privada en Tablas del Grupo",
+ "SWFFG.EnablePrivateTriggersHint": "Activar la tirada en privado de Obligación/Deber/Moral del grupo para que los jugadores no vean el resultado",
"SWFFG.EnableDebug": "Activar consola depuración",
"SWFFG.EnableDebugHint": "Activar los mensajes de depuración en consola, utilizar sólo para diagnósticos",
"SWFFG.Talent": "Talento",
@@ -420,5 +427,78 @@
"SWFFG.EnableRollAudioPlaylist": "Tirada - Lista de sonidos a utilizar por el usuario",
"SWFFG.EnableRollAudioPlaylistHint": "Esta es la lista de reproducción permitida para los efectos de sonido.",
"SWFFG.SentDicePoolRoll": "Pulsa aquí para tirar la reserva de dados.",
- "SWFFG.SentDicePoolRollHit": "Envía una reserva de dados para su tirada."
+ "SWFFG.SentDicePoolRollHit": "Envía una reserva de dados para su tirada.",
+ "SWFFG.TabGeneral": "Información Básica",
+ "SWFFG.TabObligationDutyMorality": "Obligación/Deber/Moralidad",
+ "SWFFG.SkillsNameKnowledgeAdventuring": "Aventura",
+ "SWFFG.SkillsNameKnowledgeAdventuringAbbrev": "Aven.",
+ "SWFFG.SkillsNameKnowledgeForbidden": "Prohibido",
+ "SWFFG.SkillsNameKnowledgeForbiddenAbbrev": "Proh.",
+ "SWFFG.SkillsNameKnowledgeGeography": "Geografía",
+ "SWFFG.SkillsNameKnowledgeGeographyAbbrev": "Geog.",
+ "SWFFG.SkillsNameRunes": "Runas",
+ "SWFFG.SkillsNameVerse": "Verso",
+ "SWFFG.SkillsNameAember": "Æmbercraft",
+ "SWFFG.SkillsNameAemberAbbrev": "ÆmCrft",
+ "SWFFG.SkillsNameKnowledgeAember": "Æmber",
+ "SWFFG.SkillsNameKnowledgeCrucible": "Crisol",
+ "SWFFG.SkillsNameKnowledgeCulture": "Cultura",
+ "SWFFG.ModifierAllSkills": "Todas Hab.",
+ "SWFFG.IsRestricted": "Restringido?",
+ "SWFFG.EnableSortTalentsByActivationGlobal": "Orden Talentos (Global)",
+ "SWFFG.EnableSortTalentsByActivation": "Ordenar Talentos",
+ "SWFFG.EnableSortTalentsByActivationHint": "Activar grupo Talentos por activación",
+ "SWFFG.UseGlobalSetting": "Uso Global",
+ "SWFFG.OptionValueYes": "Si",
+ "SWFFG.OptionValueNo": "No",
+ "SWFFG.ModTypeAll" : "Todas",
+ "SWFFG.ItemQualities": "Opciones",
+ "SWFFG.ItemAttachments": "Accesorios",
+ "SWFFG.TabConfiguration": "Configuración",
+ "SWFFG.TabBaseModifiers": "Mods Base",
+ "SWFFG.TabAdditionalModifiers": "Añade Mods",
+ "SWFFG.ModCount" : "Cálculo",
+ "SWFFG.ModTypeResultModifiers": "Mods Resultado",
+ "SWFFG.ModTypeDiceModifiers": "Mods Dados",
+ "SWFFG.ModTypeRollModifiers": "Mods Tirada",
+ "SWFFG.ModTypeAddBoost": "Añade Beneficio",
+ "SWFFG.ModTypeAddSetback": "Añade Complicación",
+ "SWFFG.ModTypeAddDifficulty": "Añade Dificultad",
+ "SWFFG.ModTypeUpgradeDifficulty": "Aumenta Dificultad",
+ "SWFFG.ModTypeDowngradeDifficulty": "Disminuye Dificultad",
+ "SWFFG.ModTypeUpgradeAbility": "Aumenta Capacidad",
+ "SWFFG.ModTypeDowngradeAbility": "Disminuye Capacidad",
+ "SWFFG.ModTypeAddAdvantage": "Añade Ventaja",
+ "SWFFG.ModTypeAddDark": "Añade Oscuridad",
+ "SWFFG.ModTypeAddFailure": "Añade Fallo",
+ "SWFFG.ModTypeAddLight": "Añade Luz",
+ "SWFFG.ModTypeAddSuccess": "Añade Éxito",
+ "SWFFG.ModTypeAddThreat": "Añade Amenaza",
+ "SWFFG.ModTypeStatArmor" : "Atr. Armadura",
+ "SWFFG.FromAttachment": "De Accesorio",
+ "SWFFG.WeaponCardCritical" : "Crítico",
+ "SWFFG.ItemModifiers" : "Modificador Objeto",
+ "SWFFG.BaseMods": "Mods Base",
+ "SWFFG.Count": "Cálculo",
+ "SWFFG.SettingsUITheme": "Escoge Tema IU",
+ "SWFFG.SettingsUIThemeHint": "Cambiar entre los diferentes estilos del interfaz.",
+ "SWFFG.UISettings": "Configuración Interfaz Usuario",
+ "SWFFG.UISettingsHint": "Modificar funcionamiento defecto IU.",
+ "SWFFG.UISettingsLabel": "Gestor Configuración IU",
+ "SWFFG.SettingsDestinyLight": "Destino Lado Luminoso",
+ "SWFFG.SettingsDestinyLightHint": "Cambia el texto en la reserva de Destino Lado Luminoso",
+ "SWFFG.SettingsDestinyDark": "Destino Lado Oscuro",
+ "SWFFG.SettingsDestinyDarkHint": "Cambia el texto en la reserva de Destino Lado Oscuro",
+ "SWFFG.SettingsEnableForceDie" : "Permite usar el Dado de la Fuerza",
+ "SWFFG.SettingsEnableForceDieHint" : "Activa/Desactiva Dado Blanco de la Fuerza en tiradas (NOTA: Si el tema de dados es Star Wars el Dado de la Fuerza se activa automáticamente)",
+ "SWFFG.SettingsPausedImage": "Icono de Pausa",
+ "SWFFG.SettingsPausedImageHint": "Cambia el icono de la pausa seleccionando una nueva imagen.",
+ "SWFFG.Triumph" : "Triunfo",
+ "SWFFG.Despair" : "Desesperación",
+ "SWFFG.EnableCriticalInjuries" : "Activa Heridas Críticas",
+ "SWFFG.EnableCriticalInjuriesHint" : "Activa la sección para Heridas Críticas",
+ "SWFFG.ModTypeSkillAddTriumph" : "Hab. Añade Triunfo",
+ "SWFFG.ModTypeAddTriumph" : "Añade Triunfo",
+ "SWFFG.ModTypeSkillAddDespair" : "Hab. Añade Desesperación",
+ "SWFFG.ModTypeAddDespair" : "Añade Desesperación"
}
diff --git a/lang/fr.json b/lang/fr.json
index a2509d3e..119d3ba1 100644
--- a/lang/fr.json
+++ b/lang/fr.json
@@ -132,16 +132,16 @@
"SWFFG.SkillsNameDiscipline": "Sang-froid",
"SWFFG.SkillsNameGunnery": "Artillerie",
"SWFFG.SkillsNameGunneryAbbreviation": "Art.",
- "SWFFG.SkillsNameKnowledgeCoreWorlds": "Connaissance: Mondes du Noyau",
- "SWFFG.SkillsNameKnowledgeEducation": "Connaissance: Education",
- "SWFFG.SkillsNameKnowledgeLore": "Connaissance: Culture",
- "SWFFG.SkillsNameKnowledgeOuterRim": "Connaissance: Bordure Extérieure",
- "SWFFG.SkillsNameKnowledgeScience": "Connaissance: Science",
- "SWFFG.SkillsNameKnowledgeSociety": "Connaissance: Société",
- "SWFFG.SkillsNameKnowledgeTheNet": "Connaissance: Le Réseau",
- "SWFFG.SkillsNameKnowledgeUnderworld": "Connaissance: Pègre",
- "SWFFG.SkillsNameKnowledgeWarfare": "Connaissance: Stratégie",
- "SWFFG.SkillsNameKnowledgeXenology": "Connaissance: Xénologie",
+ "SWFFG.SkillsNameKnowledgeCoreWorlds": "Mondes du Noyau",
+ "SWFFG.SkillsNameKnowledgeEducation": "Education",
+ "SWFFG.SkillsNameKnowledgeLore": "Culture",
+ "SWFFG.SkillsNameKnowledgeOuterRim": "Bordure Extérieure",
+ "SWFFG.SkillsNameKnowledgeScience": "Science",
+ "SWFFG.SkillsNameKnowledgeSociety": "Société",
+ "SWFFG.SkillsNameKnowledgeTheNet": "Le Réseau",
+ "SWFFG.SkillsNameKnowledgeUnderworld": "Pègre",
+ "SWFFG.SkillsNameKnowledgeWarfare": "Stratégie",
+ "SWFFG.SkillsNameKnowledgeXenology": "Xénologie",
"SWFFG.SkillsNameKnowledgeCoreWorldsStripped": "Mondes du Noyau",
"SWFFG.SkillsNameKnowledgeEducationStripped": "Education",
"SWFFG.SkillsNameKnowledgeLoreStripped": "Culture",
@@ -158,7 +158,7 @@
"SWFFG.SkillsNameMeleeAbbreviation": "CàC",
"SWFFG.SkillsNameNegotiation": "Négociation",
"SWFFG.SkillsNamePerception": "Perception",
- "SWFFG.SkillsNamePilotingPlanetary": "Pilotage : Planétaire",
+ "SWFFG.SkillsNamePilotingPlanetary": "Pilotage : Planétaire",
"SWFFG.SkillsNamePilotingSpace": "Pilotage : Spatial",
"SWFFG.SkillsNameRangedHeavy": "Armes lourdes",
"SWFFG.SkillsNameRangedHeavyAbbreviation": "A.Lo",
@@ -265,8 +265,8 @@
"SWFFG.SettingsDiceThemeHint": "Alterner entre les dés Star Wars ou Genesys.",
"SWFFG.TalentsForce": "Talent de Force ?",
"SWFFG.ForceTalent": "Talent de Force",
- "SWFFG.LoadFile" : "Charger Fichier",
- "SWFFG.ImportFile" : "Importer",
+ "SWFFG.LoadFile": "Charger Fichier",
+ "SWFFG.ImportFile": "Importer",
"SWFFG.Cost": "Coût:",
"SWFFG.EnableSoakCalc": "Activer l'auto-calcul de l'Encaissement.",
"SWFFG.EnableSoakCalcHint": "Activer le calcul automatique de l'encaissement, et enlever la possibilité d'editer manuelement la valeur dans les feuilles.",
@@ -337,7 +337,7 @@
"SWFFG.SkillsSocial": "Social",
"SWFFG.TalentTier": "Niveau",
"SWFFG.TalentCurrentTier": "Niveau actuel",
- "SWFFG.TalentsConflict" : "Conflit ?",
+ "SWFFG.TalentsConflict": "Conflit ?",
"SWFFG.ConsumablesAmount": "Quantité",
"SWFFG.ConsumablesDuration": "Durée",
"SWFFG.UseBrawnBaseDamage": "Pugilat?",
@@ -354,5 +354,136 @@
"SWFFG.Advantage": "Avantage",
"SWFFG.Success": "Succès",
"SWFFG.Threat": "Menace",
- "SWFFG.Failure": "Échec"
-}
+ "SWFFG.Failure": "Échec",
+ "SWFFG.SkillImportInstructions1": "Vous pouvez IMPORTER un fichier de données JSON de liste de compétences pour ajouter un nouveau thème de compétences ou mettre à jour un thème existant.",
+ "SWFFG.SkillImportInstructions2": "Vous pouvez RESET les listes de compétences au système par défaut en cliquant sur le bouton Reset la liste de compétences.",
+ "SWFFG.SkillImportInstructions3": "Veuillez consulter la documentation pour le schéma de la liste des compétences",
+ "SWFFG.SkillImportSource": "Fichier source JSON pour la liste de compétences",
+ "SWFFG.SkillImportTheme": "Thème de compétences",
+ "SWFFG.SkillImportActive": "Actif ?",
+ "SWFFG.SkillDownload": "Télécharger ",
+ "SWFFG.ModTypeSkillAddAdvantage": "Ajouter un avantage",
+ "SWFFG.ModTypeSkillAddDark": "Ajouter un point Obscur",
+ "SWFFG.ModTypeSkillAddFailure": "Ajouter un point échec",
+ "SWFFG.ModTypeSkillAddLight": "Ajouter un point Lumineux",
+ "SWFFG.ModTypeSkillAddSuccess": "Ajouter un succès",
+ "SWFFG.ModTypeSkillAddThreat": "Ajouter une menace",
+ "SWFFG.SendForceRollToChat": "Lancer un Pouvoir de Force",
+ "SWFFG.InitiativeRoll": "Lancer l'initiative",
+ "SWFFG.InitiativeRollResult": "Jets d'initiative !",
+ "SWFFG.InitiativePoolSelectorHint": "Sélectionnez la compétence de base à utiliser pour l'initiative",
+ "SWFFG.InitiativePoolAddsHint": "Ajouter un succès/avantages à votre Jet. Cliquer à gauche pour augmenter les dés, à droite pour les diminuer",
+ "SWFFG.RequestDestinyRoll": "Demander un Jet",
+ "SWFFG.DestinyAlreadyRolled": "Vous avez déjà lancé vos points de Destin !",
+ "SWFFG.DestinyPoolRoll": "Cliquez ici pour participer aux points de Destin",
+ "SWFFG.SkillAddAsInitiative": "Basculer pour utiliser comme initiative",
+ "SWFFG.ItemWeaponStatus": "État",
+ "SWFFG.ItemStatusNone": "Aucun dommage",
+ "SWFFG.ItemStatusMinor": "Dommages mineurs",
+ "SWFFG.ItemStatusModerate": "Dommages modérés",
+ "SWFFG.ItemStatusMajor": "Dommages importants",
+ "SWFFG.ItemTooDamagedToUse": "est trop endommagé pour être utilisé",
+ "SWFFG.RollResultSuccess": "Succès",
+ "SWFFG.RollResultFailure": "Échec(s)",
+ "SWFFG.RollResultAdvantage": "Avantage(s)",
+ "SWFFG.RollResultThreat": "Menace(s)",
+ "SWFFG.RollResultTriumph": "Triomphe(s)",
+ "SWFFG.RollResultDespair": "Désastre(s)",
+ "SWFFG.RollResultLight": "Point(s) Lumineux",
+ "SWFFG.RollResultDark": "Point(s) Obscur",
+ "SWFFG.Gender": "Genre",
+ "SWFFG.Age": "Age",
+ "SWFFG.Height": "Hauteur",
+ "SWFFG.Build": "Gabarit",
+ "SWFFG.Hair": "Cheveux",
+ "SWFFG.Eyes": "Yeux",
+ "SWFFG.NotableFeature": "Faits notables",
+ "SWFFG.Notes": "Autres",
+ "SWFFG.Motivations": "Motivations",
+ "SWFFG.MotivationCategory": "Catégorie",
+ "SWFFG.MotivationType": "Type",
+ "SWFFG.MotivationDescription": "Description",
+ "SWFFG.EmotionalStrength": "Force émotionnelle",
+ "SWFFG.EmotionalWeakness": "Faiblesse émotionnelle",
+ "SWFFG.Strength": "Force",
+ "SWFFG.Flaw": "Défaut ",
+ "SWFFG.Desire": "Désir",
+ "SWFFG.Fear": "Peur",
+ "SWFFG.Type": "Type",
+ "SWFFG.Magnitude": "Magnitude",
+ "SWFFG.AdversariesImportInstructions1": "Importer des données en utilisant le format de données de Stoogoff (http://swa.stoogoff.com/).",
+ "SWFFG.AdversariesImportInstructions2": "Télécharger la source complète sous forme de fichier zip à utiliser pour l'importation.",
+ "SWFFG.Skills": "Compétences",
+ "SWFFG.GroupManager": "Gestion du groupe",
+ "SWFFG.EnableRollAudio": "Jet - Permettre aux utilisateurs d'ajouter des sons sur les jets",
+ "SWFFG.EnableRollAudioHint": "Permettre aux utilisateurs d'ajouter des pistes audio à partir du dialogue de lancement",
+ "SWFFG.EnableRollAudioPlaylist": "Roll - Playlist users use for roll audio",
+ "SWFFG.EnableRollAudioPlaylistHint": "Il s'agit de la liste de lecture à laquelle les utilisateurs ont accès et à partir de laquelle ils peuvent ajouter de l'audio aux Jets.",
+ "SWFFG.SentDicePoolRoll": "Cliquez ici pour lancer le jeu de dés.",
+ "SWFFG.SentDicePoolRollHint": "Envoyé un jeu de dés pour que vous le lanciez.",
+ "SWFFG.TabGeneral": "Informations de base",
+ "SWFFG.TabObligationDutyMorality": "Obligation/Devoir/Moralité",
+ "SWFFG.SkillsNameKnowledgeAdventuring": "Aventurier",
+ "SWFFG.SkillsNameKnowledgeAdventuringAbbrev": "Avent.",
+ "SWFFG.SkillsNameKnowledgeForbidden": "Interdit",
+ "SWFFG.SkillsNameKnowledgeForbiddenAbbrev": "Inter.",
+ "SWFFG.SkillsNameKnowledgeGeography": "Géographie",
+ "SWFFG.SkillsNameKnowledgeGeographyAbbrev": "Géogr.",
+ "SWFFG.SkillsNameRunes": "Runes",
+ "SWFFG.SkillsNameVerse": "Verse",
+ "SWFFG.SkillsNameAember": "Æmbercraft",
+ "SWFFG.SkillsNameAemberAbbrev": "ÆmCrft",
+ "SWFFG.SkillsNameKnowledgeAember": "Æmber",
+ "SWFFG.SkillsNameKnowledgeCrucible": "Crucible",
+ "SWFFG.SkillsNameKnowledgeCulture": "Culture",
+ "SWFFG.ModifierAllSkills": "Toutes les compétences",
+ "SWFFG.IsRestricted": "Restreindre",
+ "SWFFG.EnableSortTalentsByActivationGlobal": "Trier les talents (Global)",
+ "SWFFG.EnableSortTalentsByActivation": "Trier les talents",
+ "SWFFG.EnableSortTalentsByActivationHint": "Activer le regroupement des talents par activation",
+ "SWFFG.UseGlobalSetting": "Utilisation Globale",
+ "SWFFG.OptionValueYes": "Oui",
+ "SWFFG.OptionValueNo": "Non",
+ "SWFFG.ModTypeAll": "Tous",
+ "SWFFG.ItemQualities": "Qualités",
+ "SWFFG.ItemAttachments": "Attachements",
+ "SWFFG.TabConfiguration": "Configuration",
+ "SWFFG.TabBaseModifiers": "Modifications de base",
+ "SWFFG.TabAdditionalModifiers": "Ajouter des Mods",
+ "SWFFG.ModCount": "Compte",
+ "SWFFG.ModTypeResultModifiers": "Resultat des Mods",
+ "SWFFG.ModTypeDiceModifiers": "Dés des Mods",
+ "SWFFG.ModTypeRollModifiers": "Jets des Mods",
+ "SWFFG.ModTypeAddBoost": "Ajouter une fortune",
+ "SWFFG.ModTypeAddSetback": "Ajouter une infortune",
+ "SWFFG.ModTypeAddDifficulty": "Ajouter de la Difficulté",
+ "SWFFG.ModTypeUpgradeDifficulty": "Augmenter la diffculté",
+ "SWFFG.ModTypeDowngradeDifficulty": "Diminuer la difficulté",
+ "SWFFG.ModTypeUpgradeAbility": "Augmenter l'Aptitude",
+ "SWFFG.ModTypeDowngradeAbility": "Diminuer l'Aptitude",
+ "SWFFG.ModTypeAddAdvantage": "Ajouter un avantage",
+ "SWFFG.ModTypeAddDark": "Ajouter un point Obscur",
+ "SWFFG.ModTypeAddFailure": "Ajouter un Échec",
+ "SWFFG.ModTypeAddLight": "Ajouter un point Lumineux",
+ "SWFFG.ModTypeAddSuccess": "Ajouter un Succès",
+ "SWFFG.ModTypeAddThreat": "Ajouter un Menace",
+ "SWFFG.ModTypeStatArmor": "État de l'armure",
+ "SWFFG.FromAttachment": "De l'Attachement",
+ "SWFFG.WeaponCardCritical": "Critique",
+ "SWFFG.ItemModifiers": "Modificateurs d'objet",
+ "SWFFG.BaseMods": "Modifications de base",
+ "SWFFG.Count": "Compte",
+ "SWFFG.SettingsUITheme": "Choisissez le thème de l'UI",
+ "SWFFG.SettingsUIThemeHint": "Basculer entre différents styles d'interface utilisateur",
+ "SWFFG.UISettings": "Réglages de l'interface utilisateur",
+ "SWFFG.UISettingsHint": "Modifiez le comportement par défaut de l'UI",
+ "SWFFG.UISettingsLabel": "Gestionnaire des paramètres de l'UI",
+ "SWFFG.SettingsDestinyLight": "Côté lumineux du destin",
+ "SWFFG.SettingsDestinyLightHint": "Modifier le texte de la réserve du destin pour le côté lumineux",
+ "SWFFG.SettingsDestinyDark": "Côté obscur du destin",
+ "SWFFG.SettingsDestinyDarkHint": "Modifier le texte de la réserve du destin pour le côté obscur",
+ "SWFFG.SettingsEnableForceDie": "Activer le dé de force dans le Lanceur",
+ "SWFFG.SettingsEnableForceDieHint": "Activer/Désactiver le dé de force blanc dans le lanceur (NOTE : Si le thème du dé est \"Star Wars Force Die\" il sera automatiquement activé)",
+ "SWFFG.SettingsPausedImage": "Icône de Pause",
+ "SWFFG.SettingsPausedImageHint": "Changez l'icône de pause en sélectionnant une image de remplacement."
+}
\ No newline at end of file
diff --git a/modules/actors/actor-ffg.js b/modules/actors/actor-ffg.js
index 56445731..cb5ad327 100644
--- a/modules/actors/actor-ffg.js
+++ b/modules/actors/actor-ffg.js
@@ -20,7 +20,8 @@ export class ActorFFG extends Actor {
// if the actor has skills, add custom skills and sort by abbreviation
if (data.skills) {
- const actorSkills = {};
+ let actorSkills = data.skills;
+
Object.keys(data.skills)
.filter((skill) => {
return data.skills[skill].custom;
@@ -31,25 +32,15 @@ export class ActorFFG extends Actor {
abrev: data.skills[skill].label,
label: data.skills[skill].label,
custom: data.skills[skill].custom,
+ ...data.skills[skill],
};
});
- const skills = JSON.parse(JSON.stringify(CONFIG.FFG.skills));
- mergeObject(skills, actorSkills);
-
- const sorted = Object.keys(skills).sort(function (a, b) {
- const x = game.i18n.localize(skills[a].abrev);
- const y = game.i18n.localize(skills[b].abrev);
-
- return x < y ? -1 : x > y ? 1 : 0;
- });
-
- let ordered = {};
- sorted.forEach((skill) => {
- ordered[skill] = skills[skill];
- });
+ let skills = JSON.parse(JSON.stringify(CONFIG.FFG.skills));
- CONFIG.FFG.skills = ordered;
+ // if (game.settings.get("starwarsffg", "skilltheme") !== "starwars") {
+ data.skills = mergeObject(skills, actorSkills);
+ // }
let unique = [...new Set(Object.values(data.skills).map((item) => item.type))];
if (unique.indexOf("General") > 0) {
@@ -83,15 +74,16 @@ export class ActorFFG extends Actor {
//localize skill names
for (let skill of Object.keys(data.skills)) {
- const cleanedSkillName = skill.replace(/[\W_]+/g, "");
-
- const strId = `SWFFG.SkillsName${cleanedSkillName}`;
- const localizedField = game.i18n.localize(strId);
+ let skillLabel = CONFIG.FFG.skills?.[skill]?.label;
- if (!data.skills[skill].custom) {
- data.skills[skill].label = localizedField;
+ if (!skillLabel) {
+ // this is a one-off skill added directly to the character
+ skillLabel = data.skills[skill].label;
}
- data.skills = this._sortSkills(data.skills);
+
+ const localizedField = game.i18n.localize(skillLabel);
+
+ data.skills[skill].label = localizedField;
}
}
@@ -120,7 +112,7 @@ export class ActorFFG extends Actor {
}
//Calculate the number of alive minions
- data.quantity.value = Math.min(data.quantity.max, data.quantity.max - Math.floor(data.stats.wounds.value / data.unit_wounds.value));
+ data.quantity.value = Math.min(data.quantity.max, data.quantity.max - Math.floor((data.stats.wounds.value - 1) / data.unit_wounds.value));
// Loop through Skills, and where groupskill = true, set the rank to 1*(quantity-1).
for (let [key, skill] of Object.entries(data.skills)) {
@@ -302,7 +294,7 @@ export class ActorFFG extends Actor {
}
// enable talent sorting if global to true and sheet is set to inherit or sheet is set to true.
- if ((game.settings.get("starwarsffg", "talentSorting") && actorData.flags?.config?.talentSorting === "0") || actorData.flags?.config?.talentSorting === "1") {
+ if ((game.settings.get("starwarsffg", "talentSorting") && (!actorData.flags?.config?.talentSorting || actorData.flags?.config?.talentSorting === "0")) || actorData.flags?.config?.talentSorting === "1") {
data.talentList = globalTalentList.slice().sort(this._sortTalents);
} else {
data.talentList = globalTalentList;
@@ -371,82 +363,6 @@ export class ActorFFG extends Actor {
return s.charAt(0).toUpperCase() + s.slice(1);
}
- // Sort skills by label
- _sortSkills(skills) {
- // Break down skills object into an array for sorting (I hate Javascript)
- let skillarray = Object.entries(skills).filter((skill) => {
- return skill[1]["type"] != "Knowledge";
- });
- let knowledgearray = Object.entries(skills).filter((skill) => {
- return skill[1]["type"] === "Knowledge";
- });
- if (game.settings.get("starwarsffg", "skillSorting")) {
- skillarray.sort(function (a, b) {
- // a should come before b in the sorted order
- if (a[1]["label"] < b[1]["label"]) {
- return -1 * 1;
- // a should come after b in the sorted order
- } else if (a[1]["label"] > b[1]["label"]) {
- return 1 * 1;
- // a and b are the same
- } else {
- return 0 * 1;
- }
- });
- knowledgearray.sort(function (a, b) {
- // a should come before b in the sorted order
- if (game.i18n.localize(CONFIG.FFG.skills_knowledgestripped[a[0]]) < game.i18n.localize(CONFIG.FFG.skills_knowledgestripped[b[0]])) {
- return -1 * 1;
- // a should come after b in the sorted order
- } else if (game.i18n.localize(CONFIG.FFG.skills_knowledgestripped[a[0]]) > game.i18n.localize(CONFIG.FFG.skills_knowledgestripped[b[0]])) {
- return 1 * 1;
- // a and b are the same
- } else {
- return 0 * 1;
- }
- });
- } else {
- skillarray.sort(function (a, b) {
- // a should come before b in the sorted order
- if (a[0] < b[0]) {
- return -1 * 1;
- // a should come after b in the sorted order
- } else if (a[0] > b[0]) {
- return 1 * 1;
- // a and b are the same
- } else {
- return 0 * 1;
- }
- });
- knowledgearray.sort(function (a, b) {
- // a should come before b in the sorted order
- if (a[0] < b[0]) {
- return -1 * 1;
- // a should come after b in the sorted order
- } else if (a[0] > b[0]) {
- return 1 * 1;
- // a and b are the same
- } else {
- return 0 * 1;
- }
- });
- }
- let skillobject = {};
- // Reconstruct skills object from sorted array.
- skillarray.forEach((skill) => {
- const skillname = skill[0];
- const value = skill[1];
- skillobject[skillname] = value;
- });
- knowledgearray.forEach((skill) => {
- const skillname = skill[0];
- const value = skill[1];
- skillobject[skillname] = value;
- });
- skills = skillobject;
- return skills;
- }
-
// group talents
_sortTalents(a, b) {
/*
@@ -536,7 +452,7 @@ export class ActorFFG extends Actor {
value = [data[name][k].fore, data[name][k].port, data[name][k].starboard, data[name][k].aft];
} else if (key === "Soak") {
try {
- if ((actor?._sheetClass?.name === "AdversarySheetFFG" && actorData.data.flags?.config?.enableAutoSoakCalculation) || game.settings.get("starwarsffg", "enableSoakCalc")) {
+ if ((typeof actorData.data.flags?.config?.enableAutoSoakCalculation === undefined && game.settings.get("starwarsffg", "enableSoakCalc")) || actorData.data.flags?.config?.enableAutoSoakCalculation) {
value = 0;
}
} catch (err) {
@@ -690,13 +606,15 @@ export class ActorFFG extends Actor {
setValueAndSources("Skill Add Light", "light");
setValueAndSources("Skill Add Success", "success");
setValueAndSources("Skill Add Threat", "threat");
+ setValueAndSources("Skill Add Triumph", "triumph");
+ setValueAndSources("Skill Add Despair", "despair");
const forceboost = ModifierHelpers.getCalculatedValueFromItems(actorData.items, key, "Force Boost", true);
data.skills[key].force = 0;
- if (forceboost.total > 0) {
+ if (forceboost.checked) {
const forcedice = data.stats.forcePool.max - data.stats.forcePool.value;
if (forcedice > 0) {
- data.skills[key].force = forcedice.total;
+ data.skills[key].force = forcedice;
data.skills[key].forcesource = forceboost.sources;
}
}
diff --git a/modules/actors/actor-sheet-ffg.js b/modules/actors/actor-sheet-ffg.js
index f102e415..4511e322 100644
--- a/modules/actors/actor-sheet-ffg.js
+++ b/modules/actors/actor-sheet-ffg.js
@@ -8,6 +8,8 @@ import ActorOptions from "./actor-ffg-options.js";
import ImportHelpers from "../importer/import-helpers.js";
import ModifierHelpers from "../helpers/modifiers.js";
import ActorHelpers from "../helpers/actor-helpers.js";
+import ItemHelpers from "../helpers/item-helpers.js";
+import EmbeddedItemHelpers from "../helpers/embeddeditem-helpers.js";
export class ActorSheetFFG extends ActorSheet {
constructor(...args) {
@@ -44,16 +46,31 @@ export class ActorSheetFFG extends ActorSheet {
/* -------------------------------------------- */
/** @override */
- getData() {
+ getData(options) {
const data = super.getData();
data.classType = this.constructor.name;
+
+ if (options?.action === "update" && this.object.compendium) {
+ data.item = mergeObject(data.actor, options.data);
+ }
+
data.dtypes = ["String", "Number", "Boolean"];
for (let attr of Object.values(data.data.attributes)) {
attr.isCheckbox = attr.dtype === "Boolean";
}
data.FFG = CONFIG.FFG;
+
+ let autoSoakCalculation = true;
+
+ if (typeof this.actor.data?.flags?.config?.enableAutoSoakCalculation === "undefined") {
+ autoSoakCalculation = game.settings.get("starwarsffg", "enableSoakCalc");
+ } else {
+ autoSoakCalculation = this.actor.data.flags.config.enableAutoSoakCalculation;
+ }
+
data.settings = {
- enableSoakCalculation: game.settings.get("starwarsffg", "enableSoakCalc"),
+ enableSoakCalculation: autoSoakCalculation,
+ enableCriticalInjuries: this.actor.data?.flags?.config?.enableCriticalInjuries,
};
// Establish sheet width and height using either saved persistent values or default values defined in swffg-config.js
@@ -82,6 +99,10 @@ export class ActorSheetFFG extends ActorSheet {
data.data.skilllist = this._createSkillColumns(data);
}
+ if (this.actor.data?.flags?.config?.enableObligation === false && this.actor.data?.flags?.config?.enableDuty === false && this.actor.data?.flags?.config?.enableMorality === false && this.actor.data?.flags?.config?.enableConflict === false) {
+ data.hideObligationDutyMoralityConflictTab = true;
+ }
+
return data;
}
@@ -91,7 +112,6 @@ export class ActorSheetFFG extends ActorSheet {
activateListeners(html) {
super.activateListeners(html);
- // TODO: This is not needed in Foundry 0.6.0
// Activate tabs
let tabs = html.find(".tabs");
let initial = this._sheetTab;
@@ -280,8 +300,15 @@ export class ActorSheetFFG extends ActorSheet {
this.sheetoptions.register("enableAutoSoakCalculation", {
name: game.i18n.localize("SWFFG.EnableSoakCalc"),
hint: game.i18n.localize("SWFFG.EnableSoakCalcHint"),
+ type: "Boolean",
default: true,
});
+ this.sheetoptions.register("enableCriticalInjuries", {
+ name: game.i18n.localize("SWFFG.EnableCriticalInjuries"),
+ hint: game.i18n.localize("SWFFG.EnableCriticalInjuriesHint"),
+ type: "Boolean",
+ default: false,
+ });
this.sheetoptions.register("talentSorting", {
name: game.i18n.localize("SWFFG.EnableSortTalentsByActivation"),
hint: game.i18n.localize("SWFFG.EnableSortTalentsByActivationHint"),
@@ -317,6 +344,18 @@ export class ActorSheetFFG extends ActorSheet {
if (item?.type == "species" || item?.type == "career" || item?.type == "specialization") item.sheet.render(true);
else this._itemDisplayDetails(item, ev);
}
+
+ html.find("li.item-pill").on("click", async (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ const li = event.currentTarget;
+
+ let itemId = li.dataset.itemId;
+ let modifierType = li.dataset.modifierType;
+ let modifierId = li.dataset.modifierId;
+
+ await EmbeddedItemHelpers.displayOwnedItemItemModifiersAsJournal(itemId, modifierType, modifierId, this.actor._id, this.actor.compendium);
+ });
}
});
@@ -1040,22 +1079,32 @@ export class ActorSheetFFG extends ActorSheet {
data.data.skilltypes.forEach((type) => {
// filter and sort skills for current skill category
+ let sortFunction = (a, b) => {
+ if (a.toLowerCase() > b.toLowerCase()) return 1;
+ if (a.toLowerCase() < b.toLowerCase()) return -1;
+ return 0;
+ };
+ if (game.settings.get("starwarsffg", "skillSorting")) {
+ sortFunction = (a, b) => {
+ if (data.data.skills[a].label > data.data.skills[b].label) return 1;
+ if (data.data.skills[a].label < data.data.skills[b].label) return -1;
+ return 0;
+ };
+ }
+
const skills = Object.keys(data.data.skills)
.filter((s) => data.data.skills[s].type === type.type)
- .sort((a, b) => {
- let comparison = 0;
- if (a.toLowerCase() > b.toLowerCase()) {
- comparison = 1;
- } else if (a.toLowerCase() < b.toLowerCase()) {
- comparison = -1;
- }
- return comparison;
- });
+ .sort(sortFunction);
// if the skill list is larger that the column row count then take into account the added header row.
- if (skills.length > colRowCount) {
- colRowCount = Math.ceil((totalRows + 1) / 2.0);
- rowsLeft = colRowCount;
+ if (skills.length >= colRowCount) {
+ if (skills.length - colRowCount > 2) {
+ colRowCount = Math.ceil((totalRows + 1) / 2.0);
+ rowsLeft = colRowCount;
+ } else {
+ colRowCount = skills.length + 1;
+ rowsLeft = colRowCount;
+ }
}
cols[currentColumn].push({ id: "header", ...type });
diff --git a/modules/apps/pause-ffg.js b/modules/apps/pause-ffg.js
new file mode 100644
index 00000000..01e71532
--- /dev/null
+++ b/modules/apps/pause-ffg.js
@@ -0,0 +1,21 @@
+export default class PauseFFG extends Pause {
+ static get defaultOptions() {
+ const options = super.defaultOptions;
+ options.id = "pause";
+ options.template = "systems/starwarsffg/templates/parts/ffg-paused.html";
+ options.popOut = false;
+ return options;
+ }
+
+ getData() {
+ let icon = game.settings.get("starwarsffg", "ui-pausedImage");
+ if (icon?.length <= 0) {
+ icon = "icons/svg/clockwork.svg";
+ }
+
+ return {
+ paused: game.paused,
+ icon,
+ };
+ }
+}
diff --git a/modules/config/ffg-armor.js b/modules/config/ffg-armor.js
new file mode 100644
index 00000000..a2582b01
--- /dev/null
+++ b/modules/config/ffg-armor.js
@@ -0,0 +1,26 @@
+export const armor_stats = {
+ "defence": {
+ value: "defence",
+ label: "SWFFG.Defense",
+ },
+ "soak": {
+ value: "soak",
+ label: "SWFFG.ItemsSoak",
+ },
+ "encumbrance": {
+ value: "emcubrance",
+ label: "SWFFG.ItemsEncum",
+ },
+ "hardpoints": {
+ value: "hardpoints",
+ label: "SWFFG.ItemsHP",
+ },
+ "rarity": {
+ value: "rarity",
+ label: "SWFFG.ItemsRarity",
+ },
+ "price": {
+ value: "price",
+ label: "SWFFG.ItemsPrice",
+ },
+};
diff --git a/modules/config/ffg-dice.js b/modules/config/ffg-dice.js
index e4253293..969781cd 100644
--- a/modules/config/ffg-dice.js
+++ b/modules/config/ffg-dice.js
@@ -8,3 +8,103 @@ export const pool_results = {
light: "SWFFG.RollResultLight",
dark: "SWFFG.RollResultDark",
};
+
+export function configureDice() {
+ // Set up dice with dynamic dice theme
+ const dicetheme = game.settings.get("starwarsffg", "dicetheme");
+ CONFIG.FFG.theme = dicetheme;
+
+ CONFIG.FFG.PROFICIENCY_ICON = `systems/starwarsffg/images/dice/${dicetheme}/yellow.png`;
+ CONFIG.FFG.ABILITY_ICON = `systems/starwarsffg/images/dice/${dicetheme}/green.png`;
+ CONFIG.FFG.CHALLENGE_ICON = `systems/starwarsffg/images/dice/${dicetheme}/red.png`;
+ CONFIG.FFG.DIFFICULTY_ICON = `systems/starwarsffg/images/dice/${dicetheme}/purple.png`;
+ CONFIG.FFG.BOOST_ICON = `systems/starwarsffg/images/dice/${dicetheme}/blue.png`;
+ CONFIG.FFG.SETBACK_ICON = `systems/starwarsffg/images/dice/${dicetheme}/black.png`;
+ CONFIG.FFG.REMOVESETBACK_ICON = `systems/starwarsffg/images/dice/${dicetheme}/black-minus.png`;
+ CONFIG.FFG.FORCE_ICON = `systems/starwarsffg/images/dice/${dicetheme}/whiteHex.png`;
+
+ CONFIG.FFG.ABILITY_RESULTS = {
+ 1: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 2: { label: `
`, success: 1, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 3: { label: `
`, success: 1, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 4: { label: `
`, success: 2, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 5: { label: `
`, success: 0, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 6: { label: `
`, success: 0, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 7: { label: `
`, success: 1, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 8: { label: `
`, success: 0, failure: 0, advantage: 2, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ };
+
+ CONFIG.FFG.BOOST_RESULTS = {
+ 1: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 2: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 3: { label: `
`, success: 1, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 4: { label: `
`, success: 1, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 5: { label: `
`, success: 0, failure: 0, advantage: 2, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 6: { label: `
`, success: 0, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ };
+
+ CONFIG.FFG.CHALLENGE_RESULTS = {
+ 1: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 2: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 3: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 4: { label: `
`, success: 0, failure: 2, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 5: { label: `
`, success: 0, failure: 2, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 6: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 7: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 8: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 9: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 10: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 2, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 11: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 2, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 12: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 0, triumph: 0, despair: 1, light: 0, dark: 0 },
+ };
+
+ CONFIG.FFG.DIFFICULTY_RESULTS = {
+ 1: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 2: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 3: { label: `
`, success: 0, failure: 2, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 4: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 5: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 6: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 7: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 2, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 8: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
+ };
+
+ CONFIG.FFG.FORCE_RESULTS = {
+ 1: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 1 },
+ 2: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 1 },
+ 3: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 1 },
+ 4: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 1 },
+ 5: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 1 },
+ 6: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 1 },
+ 7: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 2 },
+ 8: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 1, dark: 0 },
+ 9: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 1, dark: 0 },
+ 10: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 2, dark: 0 },
+ 11: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 2, dark: 0 },
+ 12: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 2, dark: 0 },
+ };
+
+ CONFIG.FFG.PROFICIENCY_RESULTS = {
+ 1: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 2: { label: `
`, success: 1, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 3: { label: `
`, success: 1, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 4: { label: `
`, success: 2, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 5: { label: `
`, success: 2, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 6: { label: `
`, success: 0, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 7: { label: `
`, success: 1, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 8: { label: `
`, success: 1, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 9: { label: `
`, success: 1, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 10: { label: `
`, success: 0, failure: 0, advantage: 2, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 11: { label: `
`, success: 0, failure: 0, advantage: 2, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 12: { label: `
`, success: 1, failure: 0, advantage: 0, threat: 0, triumph: 1, despair: 0, light: 0, dark: 0 },
+ };
+
+ CONFIG.FFG.SETBACK_RESULTS = {
+ 1: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 2: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 3: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 4: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 5: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
+ 6: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
+ };
+}
diff --git a/modules/config/ffg-modifiers.js b/modules/config/ffg-modifiers.js
index 8eb58610..d596800f 100644
--- a/modules/config/ffg-modifiers.js
+++ b/modules/config/ffg-modifiers.js
@@ -19,6 +19,10 @@ export const general_modifiers = {
"value": "Skill Add Dark",
"label": "SWFFG.ModTypeSkillAddDark",
},
+ "Skill Add Despair": {
+ "value": "Skill Add Despair",
+ "label": "SWFFG.ModTypeSkillAddDespair",
+ },
"Skill Add Failure": {
"value": "Skill Add Failure",
"label": "SWFFG.ModTypeSkillAddFailure",
@@ -35,6 +39,10 @@ export const general_modifiers = {
"value": "Skill Add Threat",
"label": "SWFFG.ModTypeSkillAddThreat",
},
+ "Skill Add Triumph": {
+ "value": "Skill Add Triumph",
+ "label": "SWFFG.ModTypeSkillAddTriumph",
+ },
"Skill Boost": {
"value": "Skill Boost",
"label": "SWFFG.ModTypeSkillBoost",
@@ -58,67 +66,138 @@ export const general_modifiers = {
};
export const weapon_modifiers = {
- "Career Skill": {
- "value": "Career Skill",
- "label": "SWFFG.ModTypeCareerSkill",
+ "Result Modifiers": {
+ "value": "Result Modifiers",
+ "label": "SWFFG.ModTypeResultModifiers",
},
- "Characteristic": {
- "value": "Characteristic",
- "label": "SWFFG.ModTypeCharacteristic",
+ "Dice Modifiers": {
+ "value": "Dice Modifiers",
+ "label": "SWFFG.ModTypeDiceModifiers",
},
- "Skill Add Advantage": {
- "value": "Skill Add Advantage",
- "label": "SWFFG.ModTypeSkillAddAdvantage",
+ "Roll Modifiers": {
+ "value": "Roll Modifiers",
+ "label": "SWFFG.ModTypeRollModifiers",
},
- "Skill Add Dark": {
- "value": "Skill Add Dark",
- "label": "SWFFG.ModTypeSkillAddDark",
+ "Weapon Stat": {
+ "value": "Weapon Stat",
+ "label": "SWFFG.ModTypeStatWeapon",
},
- "Skill Add Failure": {
- "value": "Skill Add Failure",
- "label": "SWFFG.ModTypeSkillAddFailure",
+};
+
+export const vehicle_modifiers = {
+ "Stat": {
+ "value": "Stat",
+ "label": "SWFFG.ModTypeStat",
},
- "Skill Add Light": {
- "value": "Skill Add Light",
- "label": "SWFFG.ModTypeSkillAddLight",
+};
+
+export const modifier_types = {
+ "All": {
+ "value": "all",
+ "label": "SWFFG.ModTypeAll",
},
- "Skill Add Success": {
- "value": "Skill Add Success",
- "label": "SWFFG.ModTypeSkillAddSuccess",
+ "Armor": {
+ "value": "armor",
+ "label": "SWFFG.ItemsArmor",
},
- "Skill Add Threat": {
- "value": "Skill Add Threat",
- "label": "SWFFG.ModTypeSkillAddThreat",
+ "Vehicle": {
+ "value": "vehicle",
+ "label": "SWFFG.ItemsVehicles",
},
- "Skill Boost": {
- "value": "Skill Boost",
- "label": "SWFFG.ModTypeSkillBoost",
- },
- "Skill Rank": {
- "value": "Skill Rank",
- "label": "SWFFG.ModTypeSkillRank",
+ "Weapon": {
+ "value": "weapon",
+ "label": "SWFFG.ItemsWeapons",
},
- "Skill Remove Setback": {
- "value": "Skill Remove Setback",
- "label": "SWFFG.ModTypeSkillRemoveSetback",
+};
+
+export const itemmodifier_modifiertypes = {
+ "Result Modifiers": {
+ "value": "Result Modifiers",
+ "label": "SWFFG.ModTypeResultModifiers",
},
- "Skill Setback": {
- "value": "Skill Setback",
- "label": "SWFFG.ModTypeSkillSetback",
+ "Dice Modifiers": {
+ "value": "Dice Modifiers",
+ "label": "SWFFG.ModTypeDiceModifiers",
},
- "Stat": {
- "value": "Stat",
- "label": "SWFFG.ModTypeStat",
+ "Roll Modifiers": {
+ "value": "Roll Modifiers",
+ "label": "SWFFG.ModTypeRollModifiers",
},
"Weapon Stat": {
"value": "Weapon Stat",
"label": "SWFFG.ModTypeStatWeapon",
+ }, //-> Damage, Crit, Encum, HP, Rarity, Price, Range,
+ "Armor Stat": {
+ "value": "Armor Stat",
+ "label": "SWFFG.ModTypeStatArmor",
+ }, //-> Def, Soak, Encum, HP, Rarity, Price
+};
+
+export const itemmodifier_rollmodifiers = {
+ "Add Boost": {
+ "value": "Add Boost",
+ "label": "SWFFG.ModTypeAddBoost",
+ },
+ "Add Setback": {
+ "value": "Add Setback",
+ "label": "SWFFG.ModTypeAddSetback",
},
};
-export const vehicle_modifiers = {
- "Stat": {
- "value": "Stat",
- "label": "SWFFG.ModTypeStat",
+export const itemmodifier_dicemodifiers = {
+ "Add Difficulty": {
+ "value": "Add Difficulty",
+ "label": "SWFFG.ModTypeAddDifficulty",
+ },
+ "Upgrade Difficulty": {
+ "value": "Upgrade Difficulty",
+ "label": "SWFFG.ModTypeUpgradeDifficulty",
+ },
+ "Downgrade Difficulty": {
+ "value": "Downgrade Difficulty",
+ "label": "SWFFG.ModTypeDowngradeDifficulty",
+ },
+ "Upgrade Ability": {
+ "value": "Upgrade Ability",
+ "label": "SWFFG.ModTypeUpgradeAbility",
+ },
+ "Downgrade Ability": {
+ "value": "Downgrade Ability",
+ "label": "SWFFG.ModTypeDowngradeAbility",
+ },
+};
+
+export const itemmodifier_resultmodifiers = {
+ "Add Advantage": {
+ "value": "Add Advantage",
+ "label": "SWFFG.ModTypeAddAdvantage",
+ },
+ "Add Success": {
+ "value": "Add Success",
+ "label": "SWFFG.ModTypeAddSuccess",
+ },
+ "Add Threat": {
+ "value": "Add Threat",
+ "label": "SWFFG.ModTypeAddThreat",
+ },
+ "Add Failure": {
+ "value": "Add Failure",
+ "label": "SWFFG.ModTypeAddFailure",
+ },
+ "Add Light": {
+ "value": "Add Light",
+ "label": "SWFFG.ModTypeAddLight",
+ },
+ "Add Dark": {
+ "value": "Add Dark",
+ "label": "SWFFG.ModTypeAddDark",
+ },
+ "Add Triumph": {
+ "value": "Add Triumph",
+ "label": "SWFFG.ModTypeAddTriumph",
+ },
+ "Add Despair": {
+ "value": "Add Despair",
+ "label": "SWFFG.ModTypeAddDespair",
},
};
diff --git a/modules/config/ffg-weapons.js b/modules/config/ffg-weapons.js
index a4126e54..105cd13a 100644
--- a/modules/config/ffg-weapons.js
+++ b/modules/config/ffg-weapons.js
@@ -15,4 +15,16 @@ export const weapon_stats = {
value: "hardpoints",
label: "SWFFG.ItemsHP",
},
+ "rarity": {
+ value: "rarity",
+ label: "SWFFG.ItemsRarity",
+ },
+ "price": {
+ value: "price",
+ label: "SWFFG.ItemsPrice",
+ },
+ "range": {
+ value: "range",
+ label: "SWFFG.ItemWeaponRange",
+ },
};
diff --git a/modules/dice/pool.js b/modules/dice/pool.js
index a48cadc5..6f7ed137 100644
--- a/modules/dice/pool.js
+++ b/modules/dice/pool.js
@@ -24,6 +24,8 @@ export class DicePoolFFG {
this.failure = obj.failure || 0;
this.light = obj.light || 0;
this.dark = obj.dark || 0;
+ this.triumph = obj.triumph || 0;
+ this.despair = obj.despair || 0;
this.source = {};
@@ -124,7 +126,7 @@ export class DicePoolFFG {
if (this.challenge > 0) {
this.challenge--;
this.difficulty++;
- }
+ }
} else {
if (this.difficulty > 0) {
this.difficulty--;
@@ -191,7 +193,7 @@ export class DicePoolFFG {
let advanceContainer = this.renderPreview(container);
let additionalSymbols = [];
- ["advantage", "success", "threat", "failure", "light", "dark"].forEach((symbol) => {
+ ["advantage", "success", "threat", "failure", "light", "dark", "triumph", "despair"].forEach((symbol) => {
let diceSymbol = "";
switch (symbol) {
case "advantage": {
@@ -218,6 +220,14 @@ export class DicePoolFFG {
diceSymbol = "[DA]";
break;
}
+ case "triumph": {
+ diceSymbol = "[TR]";
+ break;
+ }
+ case "despair": {
+ diceSymbol = "[DE]";
+ break;
+ }
}
if (this[symbol] !== 0) {
@@ -290,6 +300,8 @@ export class DicePoolFFG {
failure: container.querySelector('[name="failure"]')?.value ? container.querySelector('[name="failure"]').value : 0,
light: container.querySelector('[name="light"]')?.value ? container.querySelector('[name="light"]').value : 0,
dark: container.querySelector('[name="dark"]')?.value ? container.querySelector('[name="dark"]').value : 0,
+ triumph: container.querySelector('[name="triumph"]')?.value ? container.querySelector('[name="triumph"]').value : 0,
+ despair: container.querySelector('[name="despair"]')?.value ? container.querySelector('[name="despair"]').value : 0,
});
}
}
diff --git a/modules/dice/roll-builder.js b/modules/dice/roll-builder.js
index 65ac9f59..32ab76f7 100644
--- a/modules/dice/roll-builder.js
+++ b/modules/dice/roll-builder.js
@@ -38,7 +38,8 @@ export default class RollBuilderFFG extends FormApplication {
game.playlists.entries.forEach((playlist) => {
playlist.sounds.forEach((sound) => {
let selected = false;
- if (this.roll?.sound && this.roll.sound === sound.path) {
+ const s = this.roll?.sound ?? this.roll?.item?.flags?.ffgsound;
+ if (s === sound.path) {
selected = true;
}
sounds.push({ name: sound.name, path: sound.path, selected });
@@ -51,7 +52,8 @@ export default class RollBuilderFFG extends FormApplication {
if (playlist) {
playlist.sounds.forEach((sound) => {
let selected = false;
- if (this.roll?.sound && this.roll.sound === sound.path) {
+ const s = this.roll?.sound ?? this.roll?.item?.flags?.ffgsound;
+ if (s === sound.path) {
selected = true;
}
sounds.push({ name: sound.name, path: sound.path, selected });
@@ -62,7 +64,7 @@ export default class RollBuilderFFG extends FormApplication {
}
}
- let users = [{ name: 'Send To All', id: 'all'}];
+ let users = [{ name: "Send To All", id: "all" }];
if (game.user.isGM) {
game.users.entries.forEach((user) => {
if (user.visible && user.id !== game.user.id) {
@@ -71,12 +73,20 @@ export default class RollBuilderFFG extends FormApplication {
});
}
+ const enableForceDie = game.settings.get("starwarsffg", "enableForceDie");
+ const labels = {
+ light: game.settings.get("starwarsffg", "destiny-pool-light"),
+ dark: game.settings.get("starwarsffg", "destiny-pool-dark"),
+ };
+
return {
sounds,
isGM: game.user.isGM,
canUserAddAudio,
flavor: this.roll.flavor,
users,
+ enableForceDie,
+ labels,
};
}
@@ -93,6 +103,27 @@ export default class RollBuilderFFG extends FormApplication {
const sound = html.find(".sound-selection")?.[0]?.value;
if (sound) {
this.roll.sound = sound;
+ if (this?.roll?.item) {
+ let entity;
+ let entityData;
+ if (!this?.roll?.item?.flags?.uuid) {
+ entity = CONFIG["Actor"].entityClass.collection.get(this.roll.data.actor._id);
+ entityData = {
+ _id: this.roll.item._id,
+ };
+ } else {
+ const parts = this.roll.item.flags.uuid.split(".");
+ const [entityName, entityId, embeddedName, embeddedId] = parts;
+ entity = CONFIG[entityName].entityClass.collection.get(entityId);
+ if (parts.length === 4) {
+ entityData = {
+ _id: embeddedId,
+ };
+ }
+ }
+ setProperty(entityData, "flags.ffgsound", sound);
+ entity.updateOwnedItem(entityData);
+ }
}
}
@@ -122,11 +153,11 @@ export default class RollBuilderFFG extends FormApplication {
roll: this.roll,
dicePool: this.dicePool,
description: this.description,
- }
- }
- }
+ },
+ },
+ };
- if(sentToPlayer !== 'all') {
+ if (sentToPlayer !== "all") {
chatOptions.whisper = [sentToPlayer];
}
diff --git a/modules/dice/roll.js b/modules/dice/roll.js
index f4dfe6d1..03bc1048 100644
--- a/modules/dice/roll.js
+++ b/modules/dice/roll.js
@@ -66,6 +66,26 @@ export class RollFFG extends Roll {
negative: +args[2].dark < 0,
});
}
+ if (args[2]?.triumph) {
+ this.ffg.triumph = +args[2].triumph;
+ this.ffg.success = +args[2].triumph;
+ this.addedResults.push({
+ type: "Triumph",
+ symbol: PopoutEditor.renderDiceImages("[TR]"),
+ value: Math.abs(+args[2].triumph),
+ negative: +args[2].triumph < 0,
+ });
+ }
+ if (args[2]?.despair) {
+ this.ffg.despair = +args[2].despair;
+ this.ffg.failure = +args[2].despair;
+ this.addedResults.push({
+ type: "Despair",
+ symbol: PopoutEditor.renderDiceImages("[DE]"),
+ value: Math.abs(+args[2].despair),
+ negative: +args[2].despair < 0,
+ });
+ }
if (args[3]) {
this.flavorText = args[3];
@@ -212,6 +232,10 @@ export class RollFFG extends Roll {
// Define chat data
if (this?.data) {
this.data.additionalFlavorText = this.flavorText;
+ } else {
+ this.data = {
+ additionalFlavorText: this.flavorText,
+ };
}
const chatData = {
@@ -277,6 +301,8 @@ export class RollFFG extends Roll {
// Prepare message options
const messageOptions = { rollMode: rMode };
+ Hooks.call("ffgDiceMessage", this);
+
// Either create the message or just return the chat data
return create ? CONFIG.ChatMessage.entityClass.create(messageData, messageOptions) : messageData;
}
diff --git a/modules/ffg-destiny-tracker.js b/modules/ffg-destiny-tracker.js
index 99e399c5..76455c0f 100644
--- a/modules/ffg-destiny-tracker.js
+++ b/modules/ffg-destiny-tracker.js
@@ -9,11 +9,14 @@ import { GroupManager } from "./groupmanager-ffg.js";
*
*/
export default class DestinyTracker extends FormApplication {
- constructor() {
+ constructor(options) {
super();
this.destinyQueue = [];
this.isRunningQueue = false;
+ if (options?.menu) {
+ this.menu = options.menu;
+ }
}
/** @override */
@@ -30,6 +33,7 @@ export default class DestinyTracker extends FormApplication {
getData() {
// Get current value
let destinyPool = { light: game.settings.get("starwarsffg", "dPoolLight"), dark: game.settings.get("starwarsffg", "dPoolDark") };
+ let destinyPoolLabel = { light: game.settings.get("starwarsffg", "destiny-pool-light"), dark: game.settings.get("starwarsffg", "destiny-pool-dark") };
const x = $(window).width();
const y = $(window).height();
@@ -39,11 +43,16 @@ export default class DestinyTracker extends FormApplication {
this.position.width = 150;
this.position.height = 105;
+ // filter menu based on role.
+
+ const menu = this.menu.filter((m) => game.user.hasRole(m.minimumRole) || !m.minimumRole);
+
// Return data
return {
destinyPool,
+ destinyPoolLabel,
isGM: game.user.isGM,
- menu: this.object.menu,
+ menu,
};
}
@@ -57,44 +66,36 @@ export default class DestinyTracker extends FormApplication {
/** @override */
activateListeners(html) {
- const topHeader = html.find(".swffg-destiny-bar")[0];
- new Draggable(this, html, topHeader, this.options.resizable);
- const bottomHeader = html.find(".swffg-destiny-bar")[1];
- new Draggable(this, html, bottomHeader, this.options.resizable);
+ const d = html.find("swffg-destiny-container")[0];
+ new Draggable(this, html, d, this.options.resizable);
$("#destiny-tracker").css({ bottom: "0px", right: "305px" });
// future functionality to allow multiple menu items to be passed in
- // html.find(".dropbtn").click((event) => {
- // const id = `#${$(event.target).attr("id")}Content`;
- // console.log("clicked");
-
- // $(html.find(id)).toggleClass("show");
- // });
- // window.onclick = function(event) {
- // if (!event.target.matches('.dropbtn')) {
- // var dropdowns = document.getElementsByClassName("dropdown-content");
- // var i;
- // for (i = 0; i < dropdowns.length; i++) {
- // var openDropdown = dropdowns[i];
- // if (openDropdown.classList.contains('show')) {
- // openDropdown.classList.remove('show');
- // }
- // }
- // }
- // }
- // html.find(".dropdown-content a").click((event) => {
- // event.preventDefault();
- // event.stopPropagation();
-
- // const index = event.currentTarget.dataset.value;
- // this.object.menu[index].callback();
- // $(event.currentTarget).parent().toggleClass("show");
- // })
- html.find(".group-manager").click((event) => {
+
+ $.expr.filters.offscreen = function (el) {
+ var rect = el.getBoundingClientRect();
+ return rect.x + rect.width < 0 || rect.y + rect.height < 0 || rect.y + rect.height > window.innerHeight || rect.x + rect.width > window.innerWidth || rect.x > window.innerWidth || rect.y > window.innerHeight;
+ };
+
+ html.find(".dropbtn").click((event) => {
+ const id = `#${$(event.currentTarget).attr("id")}Content`;
+ $(html.find(id)).toggleClass("show");
+
+ if ($(".dropdown-content").is(":offscreen")) {
+ $(html.find(id)).addClass("vertical");
+ } else {
+ $(html.find(id)).removeClass("vertical");
+ }
+ });
+
+ html.find(".dropdown-content a").click((event) => {
event.preventDefault();
event.stopPropagation();
- new GroupManager().render(true);
+
+ const index = event.currentTarget.dataset.value;
+ this.menu[index].callback();
+ $(event.currentTarget).parent().toggleClass("show");
});
html.find(".destiny-points").click(async (event) => {
@@ -106,10 +107,10 @@ export default class DestinyTracker extends FormApplication {
var actionType = null;
if (pointType == "dPoolLight") {
flipType = "dPoolDark";
- typeName = game.i18n.localize("SWFFG.Lightside");
+ typeName = game.i18n.localize(game.settings.get("starwarsffg", "destiny-pool-light"));
} else {
flipType = "dPoolLight";
- typeName = game.i18n.localize("SWFFG.Darkside");
+ typeName = game.i18n.localize(game.settings.get("starwarsffg", "destiny-pool-dark"));
}
var messageText;
@@ -136,8 +137,8 @@ export default class DestinyTracker extends FormApplication {
messageText = `
Flipped a ${typeName} point
-
${game.i18n.localize("SWFFG.Darkside")} Remaining: ${pool.dark}
-
${game.i18n.localize("SWFFG.Lightside")} Remaining: ${pool.light}
+
${game.i18n.localize(game.settings.get("starwarsffg", "destiny-pool-dark"))} Remaining: ${pool.dark}
+
${game.i18n.localize(game.settings.get("starwarsffg", "destiny-pool-light"))} Remaining: ${pool.light}
`;
}
} else if (add) {
@@ -164,29 +165,6 @@ export default class DestinyTracker extends FormApplication {
});
});
- if (game.user.isGM) {
- new ContextMenu(html, ".swffg-destiny-bar", [
- {
- name: game.i18n.localize("SWFFG.RequestDestinyRoll"),
- icon: '',
- callback: (li) => {
- const messageText = ``;
-
- new Map([...game.settings.settings].filter(([k, v]) => v.key.includes("destinyrollers"))).forEach((i) => {
- game.settings.set(i.module, i.key, undefined);
- });
-
- CONFIG.FFG.DestinyGM = game.user.id;
-
- ChatMessage.create({
- user: game.user._id,
- content: messageText,
- });
- },
- },
- ]);
- }
-
// handle previously created roll destiny chat messages
$(".ffg-destiny-roll").on("click", this.OnClickRollDestiny.bind(this));
diff --git a/modules/groupmanager-ffg.js b/modules/groupmanager-ffg.js
index edd48a2d..d6fc892e 100644
--- a/modules/groupmanager-ffg.js
+++ b/modules/groupmanager-ffg.js
@@ -28,6 +28,12 @@ export class GroupManagerLayer extends CanvasLayer {
}
export class GroupManager extends FormApplication {
+ constructor(options) {
+ super();
+ this.obligations = [];
+ this.duties = [];
+ }
+
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["starwarsffg", "form", "group-manager"],
@@ -36,8 +42,9 @@ export class GroupManager extends FormApplication {
submitOnClose: true,
popOut: true,
editable: game.user.isGM,
+ resizable: true,
width: 330,
- height: 650,
+ height: 900,
template: "systems/starwarsffg/templates/group-manager.html",
id: "group-manager",
title: "Group Manager",
@@ -58,18 +65,36 @@ export class GroupManager extends FormApplication {
const pcListMode = game.settings.get("starwarsffg", "pcListMode");
const characters = [];
+ let obligationRangeStart = 0;
+ let dutyRangeStart = 0;
if (pcListMode === "active") {
players.forEach((player) => {
if (player.character) {
- characters.push(player.character);
+ try {
+ obligationRangeStart = this._addCharacterObligationDuty(player.character, obligationRangeStart, player.character.data.data.obligationlist, "obligations");
+ dutyRangeStart = this._addCharacterObligationDuty(player.character, dutyRangeStart, player.character.data.data.dutylist, "duties");
+ //obligationRangeStart = this._addCharacterObligations(player.character, obligationRangeStart);
+ //dutyRangeStart = this._addCharacterDuties(player.character, dutyRangeStart);
+ characters.push(player.character);
+ } catch (err) {
+ CONFIG.logger.warn(`Unable to add player (${player.character.name}) to obligation/duty table`, err);
+ }
}
});
} else if (pcListMode === "owned") {
players.forEach((player) => {
const char = game.actors.filter((actor) => actor.hasPerm(player, "OWNER"));
char.forEach((c) => {
- characters.push(c);
+ try {
+ obligationRangeStart = this._addCharacterObligationDuty(c, obligationRangeStart, c.data.data.obligationlist, "obligations");
+ dutyRangeStart = this._addCharacterObligationDuty(c, dutyRangeStart, c.data.data.dutylist, "duties");
+ characters.push(c);
+ // obligationRangeStart = this._addCharacterObligations(c, obligationRangeStart);
+ // dutyRangeStart = this._addCharacterDuties(c, dutyRangeStart);
+ } catch (err) {
+ CONFIG.logger.warn(`Unable to add player (${c.data.name}) to obligation/duty table`, err);
+ }
});
});
}
@@ -78,8 +103,18 @@ export class GroupManager extends FormApplication {
const initiative = CONFIG.Combat.initiative.formula;
const isGM = game.user.isGM;
const theme = CONFIG.FFG.theme;
+ players.hasObligation = this.obligations?.length;
+ let obligations = this.obligations;
+ players.hasDuty = this.duties?.length;
+ let duties = this.duties;
if (!isGM) this.position.height = 470;
- return { dPool, players, initiative, isGM, pcListMode, characters, theme };
+
+ const labels = {
+ light: game.settings.get("starwarsffg", "destiny-pool-light"),
+ dark: game.settings.get("starwarsffg", "destiny-pool-dark"),
+ };
+
+ return { dPool, players, initiative, isGM, pcListMode, characters, obligations, duties, theme, labels };
}
/* -------------------------------------------- */
@@ -125,23 +160,18 @@ export class GroupManager extends FormApplication {
// Add individual character to combat tracker.
html.find(".add-to-combat").click((ev) => {
const character = ev.currentTarget.dataset.character;
- const c = game.actors.get(character);
- const token = c.getActiveTokens();
- this._addCharacterToCombat(c, token, game.combat);
+ this._addCharacterToCombat(character, game.combat);
});
// Add all characters to combat tracker.
html.find(".group-to-combat").click((ev) => {
const characters = [];
const groupmanager = document.getElementById("group-manager");
const charlist = groupmanager.querySelectorAll('tr[class="player-character"]');
+ const tokens = canvas.tokens.controlled;
charlist.forEach((element) => {
characters.push(element.dataset["character"]);
});
- characters.forEach((c) => {
- const character = game.actors.get(c);
- const token = character.getActiveTokens();
- this._addCharacterToCombat(character, token, game.combat);
- });
+ this._addGroupToCombat(characters, tokens, game.combat);
});
// Add XP to individual character.
@@ -161,9 +191,12 @@ export class GroupManager extends FormApplication {
this._bulkXP(characters);
});
- // Temporary warning for non-functional buttons.
- html.find(".temp-button").click((ev) => {
- ui.notifications.warn("This function is not yet implemented.");
+ html.find(".obligation-button").click((ev) => {
+ this._rollObligation();
+ });
+
+ html.find(".duty-button").click((ev) => {
+ this._rollDuty();
});
// Open character sheet on row click.
@@ -192,18 +225,98 @@ export class GroupManager extends FormApplication {
return formData;
}
- async _addCharacterToCombat(character, token, cbt) {
- if (token.length > 0) {
- // If no combat encounter is active, create one.
- if (!cbt) {
- let scene = game.scenes.viewed;
- if (!scene) return;
- let cbt = await game.combats.object.create({ scene: scene._id, active: true });
- await cbt.activate();
- }
- await token[0].toggleCombat(game.combat);
+ _addCharacterObligationDuty(character, rangeStart, list, type) {
+ try {
+ Object.values(list).forEach((item) => {
+ let rangeEnd = rangeStart + parseInt(item.magnitude);
+ this[type].push({
+ playerId: character.id,
+ name: character.name,
+ type: item.type,
+ magnitude: item.magnitude,
+ rangeStart: rangeStart + 1,
+ rangeEnd: rangeEnd,
+ });
+ rangeStart = rangeEnd;
+ });
+ } catch (err) {
+ CONFIG.logger.warn(`Unable to add player ${character.name} `);
+ }
+ return rangeStart;
+ }
+
+ async _rollObligation() {
+ this._rollTable(this.obligations, game.i18n.localize("SWFFG.DescriptionObligation"));
+ }
+
+ async _rollDuty() {
+ this._rollTable(this.duties, game.i18n.localize("SWFFG.DescriptionDuty"));
+ }
+
+ async _rollTable(table, type) {
+ let r = new Roll("1d100").roll();
+ let rollOptions = game.settings.get("starwarsffg", "privateTriggers") ? { rollMode: "gmroll" } : {};
+ r.toMessage(
+ {
+ flavor: `${game.i18n.localize("SWFFG.Rolling")} ${type}...`,
+ },
+ rollOptions
+ );
+ let filteredTable = table.filter((entry) => entry.rangeStart <= r.total && r.total <= entry.rangeEnd);
+ let tableResult = filteredTable?.length ? `${filteredTable[0].type} ${type} ${game.i18n.localize("SWFFG.Triggered")} ${game.i18n.localize("SWFFG.For")} @Actor[${filteredTable[0].playerId}]{${filteredTable[0].name}}` : `${game.i18n.localize("SWFFG.OptionValueNo")} ${type} ${game.i18n.localize("SWFFG.Triggered")}`;
+ let messageOptions = {
+ user: game.user._id,
+ content: tableResult,
+ };
+ if (game.settings.get("starwarsffg", "privateTriggers")) {
+ messageOptions.whisper = ChatMessage.getWhisperRecipients("GM");
+ }
+ ChatMessage.create(messageOptions);
+ }
+
+ async _addGroupToCombat(characters, targets, cbt) {
+ await this._setupCombat(cbt);
+ let tokens = targets.filter((t) => !t.inCombat);
+ await Promise.all(
+ characters.map(async (c) => {
+ let token = await this._getCharacterToken(game.actors.get(c));
+ if (token) {
+ if (!token._controlled && !token.inCombat) {
+ tokens.push(token);
+ }
+ } else {
+ ui.notifications.warn(`${c.name} has no active Token in the current scene.`);
+ }
+ })
+ );
+ const createData = tokens.map((t) => {
+ return { tokenId: t.id };
+ });
+ await game.combat.createCombatant(createData);
+ }
+
+ async _addCharacterToCombat(character, cbt) {
+ await this._setupCombat(cbt);
+ let token = await this._getCharacterToken(game.actors.get(character));
+ if (token && !token.inCombat) {
+ await game.combat.createCombatant({ tokenId: token.id });
} else {
- ui.notifications.warn(`${character.name} has no active Token in the current scene.`);
+ ui.notifications.warn(`${c.name} has no active Token in the current scene.`);
+ }
+ }
+
+ async _getCharacterToken(character) {
+ let activeTokens = character.getActiveTokens();
+ return activeTokens.length ? activeTokens[0] : null;
+ }
+
+ async _setupCombat(cbt) {
+ // If no combat encounter is active, create one.
+ if (!cbt) {
+ let scene = game.scenes.viewed;
+ if (!scene) return;
+ let cbt = await game.combats.object.create({ scene: scene._id, active: true });
+ await cbt.activate();
}
}
diff --git a/modules/helpers/actor-helpers.js b/modules/helpers/actor-helpers.js
index 8a8514fb..0a394fe6 100644
--- a/modules/helpers/actor-helpers.js
+++ b/modules/helpers/actor-helpers.js
@@ -88,7 +88,10 @@ export default class ActorHelpers {
// Handle credits
if (formData.data.stats?.credits?.value) {
- const rawCredits = formData.data.stats?.credits.value?.toString().replace(/[^0-9]+|(?<=\.)[^.]*$/g, "");
+ const rawCredits = formData.data.stats?.credits.value
+ ?.toString()
+ .match(/^(?!.*\.).*|.*\./)[0]
+ .replace(/[^0-9]+/g, "");
formData.data.stats.credits.value = parseInt(rawCredits, 10);
}
} else {
diff --git a/modules/helpers/dice-helpers.js b/modules/helpers/dice-helpers.js
index fb0a378f..b8edfae9 100644
--- a/modules/helpers/dice-helpers.js
+++ b/modules/helpers/dice-helpers.js
@@ -1,5 +1,7 @@
import PopoutEditor from "../popout-editor.js";
import RollBuilderFFG from "../dice/roll-builder.js";
+import ModifierHelpers from "../helpers/modifiers.js";
+import ImportHelpers from "../importer/import-helpers.js";
export default class DiceHelpers {
static async rollSkill(obj, event, type, flavorText, sound) {
@@ -17,6 +19,12 @@ export default class DiceHelpers {
CONFIG.logger.warn(`Unable to load skill theme ${theme}, defaulting to starwars skill theme`, err);
}
+ let skillData = skills?.[skillName];
+
+ if (!skillData) {
+ skillData = data.data[skillName];
+ }
+
let skill = {
rank: 0,
characteristic: "",
@@ -29,7 +37,9 @@ export default class DiceHelpers {
failure: 0,
threat: 0,
success: 0,
- label: skills?.[skillName]?.label ? game.i18n.localize(skills[skillName].label) : game.i18n.localize(CONFIG.FFG.skills[skillName].label),
+ triumph: 0,
+ despair: 0,
+ label: skillData?.label ? game.i18n.localize(skillData.label) : game.i18n.localize(skillName),
};
let characteristic = {
value: 0,
@@ -42,31 +52,25 @@ export default class DiceHelpers {
characteristic = data.data.characteristics[skill.characteristic];
}
+ const actor = await game.actors.get(data.actor._id);
+
// Determine if this roll is triggered by an item.
let item = {};
if ($(row.parentElement).hasClass("item")) {
let itemID = row.parentElement.dataset["itemId"];
+ const item1 = actor.getOwnedItem(itemID);
item = Object.entries(data.items).filter((item) => item[1]._id === itemID);
item = item[0][1];
+ item.flags.uuid = item1.uuid;
}
+ const status = this.getWeaponStatus(item);
- let itemStatusSetback = 0;
-
- if (item.type === "weapon" && item?.data?.status && item.data.status !== "None") {
- const status = CONFIG.FFG.itemstatus[item.data.status].attributes.find((i) => i.mod === "Setback");
-
- if (status.value < 99) {
- itemStatusSetback = status.value;
- } else {
- ui.notifications.error(`${item.name} ${game.i18n.localize("SWFFG.ItemTooDamagedToUse")} (${game.i18n.localize(CONFIG.FFG.itemstatus[item.data.status].label)}).`);
- return;
- }
- }
+ // TODO: Get weapon specific modifiers from itemmodifiers and itemattachments
- const dicePool = new DicePoolFFG({
+ let dicePool = new DicePoolFFG({
ability: Math.max(characteristic.value, skill.rank),
boost: skill.boost,
- setback: skill.setback + itemStatusSetback,
+ setback: skill.setback + status.setback,
force: skill.force,
advantage: skill.advantage,
dark: skill.dark,
@@ -74,7 +78,9 @@ export default class DiceHelpers {
failure: skill.failure,
threat: skill.threat,
success: skill.success,
- difficulty: 2, // default to average difficulty
+ triumph: skill.triumph,
+ despair: skill.despair,
+ difficulty: 2 + status.difficulty, // default to average difficulty
});
dicePool.upgrade(Math.min(characteristic.value, skill.rank));
@@ -85,6 +91,7 @@ export default class DiceHelpers {
dicePool.upgradeDifficulty();
}
+ dicePool = new DicePoolFFG(await this.getModifiers(dicePool, item));
this.displayRollDialog(data, dicePool, `${game.i18n.localize("SWFFG.Rolling")} ${skill.label}`, skill.label, item, flavorText, sound);
}
@@ -100,7 +107,7 @@ export default class DiceHelpers {
const characteristic = data.data.characteristics[skill.characteristic];
const dicePool = new DicePoolFFG({
- ability: Math.max(characteristic.value, skill.rank),
+ ability: Math.max(characteristic?.value ? characteristic.value : 0, skill?.rank ? skill.rank : 0),
boost: skill.boost,
setback: skill.setback,
remsetback: skill.remsetback,
@@ -111,6 +118,8 @@ export default class DiceHelpers {
failure: skill.failure,
threat: skill.threat,
success: skill.success,
+ triumph: skill?.triumph ? skill.triumph : 0,
+ despair: skill?.despair ? skill.despair : 0,
source: {
skill: skill?.ranksource?.length ? skill.ranksource : [],
boost: skill?.boostsource?.length ? skill.boostsource : [],
@@ -136,14 +145,17 @@ export default class DiceHelpers {
const actorSheet = actor.sheet.getData();
const item = actor.getOwnedItem(itemId).data;
+ item.flags.uuid = item.uuid;
+
+ const status = this.getWeaponStatus(item);
const skill = actor.data.data.skills[item.data.skill.value];
const characteristic = actor.data.data.characteristics[skill.characteristic];
- const dicePool = new DicePoolFFG({
+ let dicePool = new DicePoolFFG({
ability: Math.max(characteristic.value, skill.rank),
boost: skill.boost,
- setback: skill.setback,
+ setback: skill.setback + status.setback,
force: skill.force,
advantage: skill.advantage,
dark: skill.dark,
@@ -151,11 +163,15 @@ export default class DiceHelpers {
failure: skill.failure,
threat: skill.threat,
success: skill.success,
- difficulty: 2, // default to average difficulty
+ triumph: skill?.triumph ? skill.triumph : 0,
+ despair: skill?.despair ? skill.despair : 0,
+ difficulty: 2 + status.difficulty, // default to average difficulty
});
dicePool.upgrade(Math.min(characteristic.value, skill.rank));
+ dicePool = new DicePoolFFG(await this.getModifiers(dicePool, item));
+
this.displayRollDialog(actorSheet, dicePool, `${game.i18n.localize("SWFFG.Rolling")} ${skill.label}`, skill.label, item, flavorText, sound);
}
@@ -173,10 +189,56 @@ export default class DiceHelpers {
failure: skill.failure,
threat: skill.threat,
success: skill.success,
+ triumph: skill?.triumph ? skill.triumph : 0,
+ despair: skill?.despair ? skill.despair : 0,
});
dicePool.upgrade(Math.min(characteristic.value, skill.rank));
this.displayRollDialog(sheet, dicePool, `${game.i18n.localize("SWFFG.Rolling")} ${skill.label}`, skill.label, null, flavorText, sound);
}
+
+ static getWeaponStatus(item) {
+ let setback = 0;
+ let difficulty = 0;
+
+ if (item.type === "weapon" && item?.data?.status && item.data.status !== "None") {
+ const status = CONFIG.FFG.itemstatus[item.data.status].attributes.find((i) => i.mod === "Setback");
+
+ if (status.value < 99) {
+ if (status.value === 1) {
+ setback = status.value;
+ } else {
+ difficulty = 1;
+ }
+ } else {
+ ui.notifications.error(`${item.name} ${game.i18n.localize("SWFFG.ItemTooDamagedToUse")} (${game.i18n.localize(CONFIG.FFG.itemstatus[item.data.status].label)}).`);
+ return;
+ }
+ }
+
+ return { setback, difficulty };
+ }
+
+ static async getModifiers(dicePool, item) {
+ if (item.type === "weapon") {
+ dicePool = await ModifierHelpers.getDicePoolModifiers(dicePool, item, []);
+
+ if (item?.data?.itemattachment) {
+ await ImportHelpers.asyncForEach(item.data.itemattachment, async (attachment) => {
+ //get base mods and additional mods totals
+ const activeModifiers = attachment.data.itemmodifier.filter((i) => i.data?.active);
+
+ dicePool = await ModifierHelpers.getDicePoolModifiers(dicePool, attachment, activeModifiers);
+ });
+ }
+ if (item?.data?.itemmodifier) {
+ await ImportHelpers.asyncForEach(item.data.itemmodifier, async (modifier) => {
+ dicePool = await ModifierHelpers.getDicePoolModifiers(dicePool, modifier, []);
+ });
+ }
+ }
+
+ return dicePool;
+ }
}
diff --git a/modules/helpers/embeddeditem-helpers.js b/modules/helpers/embeddeditem-helpers.js
new file mode 100644
index 00000000..c0c97317
--- /dev/null
+++ b/modules/helpers/embeddeditem-helpers.js
@@ -0,0 +1,232 @@
+import PopoutEditor from "../popout-editor.js";
+import JournalEntryFFG from "../items/journalentry-ffg.js";
+
+export default class EmbeddedItemHelpers {
+ static async updateRealObject(item, data) {
+ let flags = item.data.flags;
+ let realItem = await game.items.get(flags.ffgTempId);
+ let parents = [];
+ let owner;
+ let entity;
+
+ if (realItem) {
+ parents.unshift(flags);
+ } else {
+ if (Object.values(data).length > 0) {
+ parents.unshift(flags);
+ }
+ let x = flags?.ffgParent;
+ if (flags?.ffgParent && !flags?.ffgParent?.isCompendium) {
+ parents.unshift(x);
+ }
+
+ let ffgTempId = "";
+ while (x) {
+ if (x?.ffgParent && Object.values(x.ffgParent).length > 0) {
+ parents.unshift(x.ffgParent);
+ x = x.ffgParent;
+ } else {
+ flags = x;
+ ffgTempId = x.ffgTempId;
+ x = undefined;
+ }
+ }
+
+ if (flags.ffgUuid) {
+ const parts = flags.ffgUuid.split(".");
+ const [entityName, entityId, embeddedName, embeddedId] = parts;
+ entity = entityName;
+ if (entityName === "Compendium") {
+ realItem = await fromUuid(flags.ffgUuid);
+ } else if (entityName === "Actor") {
+ owner = CONFIG[entityName].entityClass.collection.get(entityId);
+ realItem = await owner.getOwnedItem(embeddedId);
+ } else {
+ realItem = await game.items.get(ffgTempId);
+ }
+ } else {
+ realItem = await game.items.get(ffgTempId);
+ }
+ }
+
+ if (realItem) {
+ if (!item.data._id) {
+ data._id = randomID();
+ }
+
+ let dataPointer = realItem.data;
+
+ if ((Object.values(data).length === 0 || parents.length > 1) && !entity) {
+ parents.forEach((value, index) => {
+ dataPointer = dataPointer.data[parents[index].ffgTempItemType][parents[index].ffgTempItemIndex];
+ });
+ } else if (entity === "Actor" && parents.length > 1) {
+ dataPointer = dataPointer.data[parents[0].ffgTempItemType][parents[0].ffgTempItemIndex];
+ }
+
+ const mergedData = mergeObject(item.data.data, data.data);
+ data.data = mergedData;
+ const itemData = mergeObject(item.data, data);
+
+ if (item.data.flags.ffgTempItemIndex > -1) {
+ dataPointer.data[item.data.flags.ffgTempItemType][item.data.flags.ffgTempItemIndex] = { ...itemData, flags: {} };
+ } else {
+ item.data.flags.ffgTempItemIndex = dataPointer.data[item.data.flags.ffgTempItemType].length;
+ dataPointer.data[item.data.flags.ffgTempItemType].push({ ...itemData, flags: {} });
+ }
+
+ let formData = {};
+ setProperty(formData, `data.${parents[0].ffgTempItemType}`, realItem.data.data[parents[0].ffgTempItemType]);
+
+ item.data = itemData;
+
+ // because this could be a temporary item, item-ffg.js may not fire, we need to set the renderedDesc.
+ if (item.data.data.renderedDesc) {
+ item.data.data.renderedDesc = PopoutEditor.renderDiceImages(item.data.data.description, {});
+ }
+
+ if (realItem?.compendium) {
+ formData._id = realItem._id;
+ await realItem.update(formData);
+ await realItem.sheet.render(true, { action: "update", data: formData });
+ } else {
+ await realItem.update(formData);
+ }
+ }
+ return;
+ }
+
+ /**
+ * Displays Owned Item, Item Modifier as a journal entry
+ *
+ * @param {string} itemId - Owned Item Id
+ * @param {string} modifierType - Item Modifier Type (itemattachment/itemmodifier)
+ * @param {string} modifierId - Item Modifier Id
+ * @param {string} actorId = Actor Id
+ */
+ static async displayOwnedItemItemModifiersAsJournal(itemId, modifierType, modifierId, actorId, compendium) {
+ try {
+ let actor;
+
+ if (compendium) {
+ actor = await compendium.getEntity(actorId);
+ } else {
+ actor = await game.actors.get(actorId);
+ }
+ const ownedItem = await actor.getOwnedItem(itemId);
+
+ if (!ownedItem) ui.notifications.warn(`The item had been removed or can not be found!`);
+
+ let modifierIndex;
+ let item;
+ if (ownedItem?.data?.data?.[modifierType]) {
+ modifierIndex = ownedItem.data.data[modifierType].findIndex((i) => i._id === modifierId);
+ item = ownedItem.data.data[modifierType][modifierIndex];
+ }
+
+ if (!item) {
+ // this is a modifier on an attachment
+ ownedItem.data.data.itemattachment.forEach((a) => {
+ modifierIndex = a.data[modifierType].findIndex((m) => m._id === modifierId);
+ if (modifierIndex > -1) {
+ item = a.data[modifierType][modifierIndex];
+ }
+ });
+ }
+
+ const readonlyItem = {
+ name: item.name,
+ content: item.data.description,
+ permission: {
+ default: ENTITY_PERMISSIONS.OBSERVER,
+ },
+ };
+
+ const readonlyItemJournalEntry = new JournalEntryFFG(readonlyItem, { temporary: true });
+ readonlyItemJournalEntry.sheet.render(true);
+ } catch (err) {
+ ui.notifications.warn(`The item or quality has been removed or can not be found!`);
+ CONFIG.logger.warn(`Error loading Read-Only Journal Item`, err);
+ }
+ }
+
+ static async loadItemModifierSheet(itemId, modifierType, modifierId, actorId) {
+ let parent;
+ let ownedItem;
+
+ if (actorId) {
+ parent = await game.actors.get(actorId);
+ ownedItem = await parent.getOwnedItem(itemId);
+ } else {
+ ownedItem = await game.items.get(itemId);
+ }
+
+ let modifierIndex;
+ if (!isNaN(modifierId)) {
+ modifierIndex = modifierId;
+ } else {
+ modifierIndex = ownedItem.data.data[modifierType].findIndex((i) => i._id === modifierId);
+ }
+
+ let item;
+ if (ownedItem?.data?.data?.[modifierType]) {
+ item = ownedItem.data.data[modifierType][modifierIndex];
+ }
+
+ if (!item) {
+ // this is a modifier on an attachment
+ ownedItem.data.data.itemattachment.forEach((a) => {
+ if (!isNaN(modifierId)) {
+ modifierIndex = modifierId;
+ } else {
+ modifierIndex = a.data[modifierType].findIndex((m) => m._id === modifierId);
+ }
+ if (modifierIndex > -1) {
+ item = a.data[modifierType][modifierIndex];
+ }
+ });
+ }
+
+ const temp = {
+ ...item,
+ flags: {
+ ffgTempId: itemId,
+ ffgTempItemType: modifierType,
+ ffgTempItemIndex: modifierIndex,
+ ffgIsTemp: true,
+ ffgUuid: ownedItem.uuid,
+ },
+ };
+
+ let tempItem = await Item.create(temp, { temporary: true });
+ tempItem.data._id = temp._id;
+ tempItem.data.flags.readonly = true;
+ if (!temp._id) {
+ tempItem.data._id = randomID();
+ }
+ tempItem.sheet.render(true);
+ }
+
+ static async createNewEmbeddedItem(type, data, flags) {
+ let temp = {
+ img: "icons/svg/mystery-man.svg",
+ name: "",
+ type,
+ flags: {
+ ffgTempItemType: type,
+ ffgTempItemIndex: -1,
+ ...flags,
+ },
+ data,
+ };
+
+ let tempItem = await Item.create(temp, { temporary: true });
+
+ tempItem.data._id = temp._id;
+ if (!temp._id) {
+ tempItem.data._id = randomID();
+ }
+
+ return tempItem;
+ }
+}
diff --git a/modules/helpers/item-helpers.js b/modules/helpers/item-helpers.js
index 18e78796..b9b14ca7 100644
--- a/modules/helpers/item-helpers.js
+++ b/modules/helpers/item-helpers.js
@@ -1,4 +1,6 @@
import ImportHelpers from "../importer/import-helpers.js";
+import JournalEntryFFG from "../items/journalentry-ffg.js";
+import ModifierHelpers from "./modifiers.js";
export default class ItemHelpers {
static async itemUpdate(event, formData) {
@@ -10,10 +12,7 @@ export default class ItemHelpers {
CONFIG.logger.debug(`Updating ${this.object.type}`);
if (this.object.data.type === "weapon") {
- const isMelee = formData?.data?.skill?.value ? formData.data.skill.value.includes("Melee") : formData[`data.skill.value`].includes("Melee");
- const isBrawl = formData?.data?.skill?.value ? formData.data.skill.value.includes("Brawl") : formData[`data.skill.value`].includes("Brawl");
-
- if ((isMelee || isBrawl) && formData?.data?.useBrawn) {
+ if (ModifierHelpers.applyBrawnToDamage(formData.data)) {
setProperty(formData, `data.damage.value`, 0);
}
}
@@ -41,8 +40,7 @@ export default class ItemHelpers {
// Update the Item
this.item.data.flags.loaded = false;
- const x = await this.object.update(formData);
- this.item.data = mergeObject(this.item.data, x[0]);
+ await this.object.update(formData);
if (this.object.data.type === "talent") {
if (this.object.data.flags?.clickfromparent?.length) {
@@ -85,79 +83,4 @@ export default class ItemHelpers {
}
}
}
-
- static oldItemUpdate(event, formData) {
- if (this.object.isOwned && this.object.actor?.compendium?.metadata) {
- return;
- }
- CONFIG.logger.debug(`Updating ${this.object.type}`);
-
- if (this.object.data.type === "weapon" && (formData["data.skill.value"] === "Melee" || formData["data.skill.value"] === "Brawl")) {
- formData["data.damage.value"] = 0;
- }
-
- // Handle the free-form attributes list
- const formAttrs = expandObject(formData)?.data?.attributes || {};
- const attributes = Object.values(formAttrs).reduce((obj, v) => {
- let k = v["key"].trim();
- delete v["key"];
- obj[k] = v;
- return obj;
- }, {});
-
- // Remove attributes which are no longer used
- if (this.object.data?.data?.attributes) {
- for (let k of Object.keys(this.object.data.data.attributes)) {
- if (!attributes.hasOwnProperty(k)) attributes[`-=${k}`] = null;
- }
- }
-
- // Re-combine formData
- formData = Object.entries(formData)
- .filter((e) => !e[0].startsWith("data.attributes"))
- .reduce(
- (obj, e) => {
- obj[e[0]] = e[1];
- return obj;
- },
- { _id: this.object._id, "data.attributes": attributes }
- );
-
- // Update the Item
- this.item.data.flags.loaded = false;
- return this.object.update(formData);
-
- if (this.object.data.type === "talent") {
- let data = this.object.data.data;
- if (data.trees.length > 0) {
- CONFIG.logger.debug("Using Talent Tree property for update");
- data.trees.forEach((spec) => {
- const specializations = game.data.items.filter((item) => {
- return item.id === spec;
- });
-
- specializations.forEach((item) => {
- CONFIG.logger.debug(`Updating Specialization`);
- for (let talentData in item.data.talents) {
- this._updateSpecializationTalentReference(item.data.talents[talentData], itemData);
- }
- });
- });
- } else {
- CONFIG.logger.debug("Legacy item, updating all specializations");
- game.data.items.forEach((item) => {
- if (item.type === "specialization") {
- for (let talentData in item.data.talents) {
- if (item.data.talents[talentData].itemId === this.object.data._id) {
- if (!data.trees.includes(item._id)) {
- data.trees.push(item._id);
- }
- this._updateSpecializationTalentReference(item.data.talents[talentData], itemData);
- }
- }
- }
- });
- }
- }
- }
}
diff --git a/modules/helpers/modifiers.js b/modules/helpers/modifiers.js
index 7492f5a9..5dfdc646 100644
--- a/modules/helpers/modifiers.js
+++ b/modules/helpers/modifiers.js
@@ -183,6 +183,30 @@ export default class ModifierHelpers {
}
}
+ static getCalculatedValueFromCurrentAndArray(item, items, key, modtype, includeSource) {
+ let total = 0;
+ let checked = false;
+ let sources = [];
+
+ const filteredAttributes = Object.values(item.data.attributes).filter((a) => a.modtype === modtype && a.mod === key);
+
+ filteredAttributes.forEach((attr) => {
+ sources.push({ modtype, key, name: item.name, value: attr.value, type: item.type });
+ total += parseInt(attr.value, 10);
+ });
+
+ const itemsTotal = ModifierHelpers.getCalculatedValueFromItems(items, key, modtype, includeSource);
+ if (includeSource) {
+ total += itemsTotal.total;
+ sources = sources.concat(itemsTotal.sources);
+
+ return { total, sources };
+ } else {
+ total += itemsTotal;
+ return total;
+ }
+ }
+
static getBaseValue(items, key, modtype) {
let total = 0;
@@ -220,6 +244,8 @@ export default class ModifierHelpers {
let newKey = document.createElement("div");
if (["criticaldamage", "shipattachment", "shipweapon"].includes(this.object.type)) {
newKey.innerHTML = ``;
+ } else if (["itemmodifier", "itemattachment"].includes(this.object.type)) {
+ newKey.innerHTML = ``;
} else {
newKey.innerHTML = ``;
}
@@ -296,4 +322,35 @@ export default class ModifierHelpers {
title,
}).render(true);
}
+
+ static async getDicePoolModifiers(pool, item, items) {
+ let dicePool = new DicePoolFFG(pool);
+
+ dicePool.boost += ModifierHelpers.getCalculatedValueFromCurrentAndArray(item, items, "Add Boost", "Roll Modifiers");
+ dicePool.setback += ModifierHelpers.getCalculatedValueFromCurrentAndArray(item, items, "Add Setback", "Roll Modifiers");
+ dicePool.advantage += ModifierHelpers.getCalculatedValueFromCurrentAndArray(item, items, "Add Advantage", "Result Modifiers");
+ dicePool.dark += ModifierHelpers.getCalculatedValueFromCurrentAndArray(item, items, "Add Dark", "Result Modifiers");
+ dicePool.failure += ModifierHelpers.getCalculatedValueFromCurrentAndArray(item, items, "Add Failure", "Result Modifiers");
+ dicePool.light += ModifierHelpers.getCalculatedValueFromCurrentAndArray(item, items, "Add Light", "Result Modifiers");
+ dicePool.success += ModifierHelpers.getCalculatedValueFromCurrentAndArray(item, items, "Add Success", "Result Modifiers");
+ dicePool.threat += ModifierHelpers.getCalculatedValueFromCurrentAndArray(item, items, "Add Threat", "Result Modifiers");
+ dicePool.triumph += ModifierHelpers.getCalculatedValueFromCurrentAndArray(item, items, "Add Triumph", "Result Modifiers");
+ dicePool.despair += ModifierHelpers.getCalculatedValueFromCurrentAndArray(item, items, "Add Despair", "Result Modifiers");
+
+ dicePool.difficulty += ModifierHelpers.getCalculatedValueFromCurrentAndArray(item, items, "Add Difficulty", "Dice Modifiers");
+ dicePool.upgradeDifficulty(ModifierHelpers.getCalculatedValueFromCurrentAndArray(item, items, "Upgrade Difficulty", "Dice Modifiers"));
+ dicePool.upgradeDifficulty(-1 * ModifierHelpers.getCalculatedValueFromCurrentAndArray(item, items, "Downgrade Difficulty", "Dice Modifiers"));
+ dicePool.upgrade(ModifierHelpers.getCalculatedValueFromCurrentAndArray(item, items, "Upgrade Ability", "Dice Modifiers"));
+ dicePool.upgrade(-1 * ModifierHelpers.getCalculatedValueFromCurrentAndArray(item, items, "Downgrade Ability", "Dice Modifiers"));
+
+ return dicePool;
+ }
+
+ static applyBrawnToDamage(data) {
+ if ((data.skill.value.includes("Melee") || data.skill.value.includes("Brawl") || data.skill.value.includes("Lightsaber")) && data.skill.useBrawn) {
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/modules/importer/data-importer.js b/modules/importer/data-importer.js
index c24e7270..8da1e4e9 100644
--- a/modules/importer/data-importer.js
+++ b/modules/importer/data-importer.js
@@ -1,4 +1,5 @@
import ImportHelpers from "./import-helpers.js";
+import OggDude from "./oggdude/oggdude.js";
/**
* A specialized form used to pop out the editor.
@@ -105,6 +106,7 @@ export default class DataImporter extends FormApplication {
this._enableImportSelection(zip.files, "ItemDescriptors");
this._enableImportSelection(zip.files, "SigAbilityNodes");
this._enableImportSelection(zip.files, "Skills");
+ this._enableImportSelection(zip.files, "ItemAttachments");
} catch (err) {
ui.notifications.warn("There was an error trying to load the import file, check the console log for more information.");
console.error(err);
@@ -166,7 +168,7 @@ export default class DataImporter extends FormApplication {
// load skills for reference
let data = await zip.file(skillsFileName).async("text");
const xmlDoc = ImportHelpers.stringToXml(data);
- await this._loadSkillsList(xmlDoc, createSkillJournalEntries);
+ await OggDude.Import.Skills(xmlDoc, createSkillJournalEntries);
}
const itemDescriptors = importFiles.find((item) => item.file.includes("ItemDescriptors.xml"));
@@ -174,7 +176,7 @@ export default class DataImporter extends FormApplication {
if (itemDescriptors) {
let data = await zip.file(itemDescriptors.file).async("text");
const xmlDoc = ImportHelpers.stringToXml(data);
- await this._loadItemDescriptors(xmlDoc);
+ await OggDude.Import.ItemDescriptors(xmlDoc);
}
await this.asyncForEach(importFiles, async (file) => {
@@ -182,21 +184,22 @@ export default class DataImporter extends FormApplication {
const data = await zip.file(file.file).async("text");
const xmlDoc = ImportHelpers.stringToXml(data);
- promises.push(this._handleGear(xmlDoc, zip));
- promises.push(this._handleWeapons(xmlDoc, zip));
- promises.push(this._handleArmor(xmlDoc, zip));
- promises.push(this._handleTalents(xmlDoc, zip));
- promises.push(this._handleForcePowers(xmlDoc, zip));
- promises.push(this._handleSignatureAbilties(xmlDoc, zip));
+ promises.push(OggDude.Import.Gear(xmlDoc, zip));
+ promises.push(OggDude.Import.Weapons(xmlDoc, zip));
+ promises.push(OggDude.Import.Armor(xmlDoc, zip));
+ promises.push(OggDude.Import.Talents(xmlDoc, zip));
+ promises.push(OggDude.Import.ForcePowers(xmlDoc, zip));
+ promises.push(OggDude.Import.SignatureAbilities(xmlDoc, zip));
+ promises.push(OggDude.Import.ItemAttachments(xmlDoc));
} else {
if (file.file.includes("/Specializations/")) {
isSpecialization = true;
}
if (file.file.includes("/Careers/")) {
- promises.push(this._handleCareers(zip));
+ promises.push(OggDude.Import.Career(zip));
}
if (file.file.includes("/Species/")) {
- promises.push(this._handleSpecies(zip));
+ promises.push(OggDude.Import.Species(zip));
}
if (file.file.includes("/Vehicles/")) {
isVehicle = true;
@@ -206,10 +209,10 @@ export default class DataImporter extends FormApplication {
await Promise.all(promises);
if (isSpecialization) {
- await this._handleSpecializations(zip);
+ await OggDude.Import.Specializations(zip);
}
if (isVehicle) {
- await this._handleVehicles(zip);
+ await OggDude.Import.Vehicles(zip);
}
if ($(".debug input:checked").length > 0) {
@@ -221,1687 +224,6 @@ export default class DataImporter extends FormApplication {
}
}
- async _loadSkillsList(xmlDoc, create) {
- const skills = xmlDoc.getElementsByTagName("Skill");
- if (skills.length > 0) {
- CONFIG.temporary["skills"] = {};
-
- let totalCount = skills.length;
- let currentCount = 0;
- let pack;
- if (create) {
- pack = await this._getCompendiumPack("JournalEntry", `oggdude.SkillDescriptions`);
- $(".import-progress.skills").toggleClass("import-hidden");
- }
-
- for (let i = 0; i < skills.length; i += 1) {
- const skill = skills[i];
- const importkey = skill.getElementsByTagName("Key")[0]?.textContent;
- let name = skill.getElementsByTagName("Name")[0]?.textContent;
-
- name = name.replace(" - ", ": ");
- if (["CORE", "EDU", "LORE", "OUT", "UND", "WARF", "XEN"].includes(importkey)) {
- name = `Knowledge: ${name}`;
- }
-
- CONFIG.temporary.skills[importkey] = name;
-
- if (create) {
- try {
- const d = JXON.xmlToJs(skill);
-
- let item = {
- name: d.Name,
- flags: {
- ffgimportid: d.Key,
- },
- content: d?.Description?.length && d.Description.length > 0 ? d.Description : "Dataset did not have a description",
- };
-
- item.content += ImportHelpers.getSources(d?.Sources ?? d?.Source);
-
- let compendiumItem;
- await pack.getIndex();
- let entry = pack.index.find((e) => e.name === item.name);
-
- if (!entry) {
- CONFIG.logger.debug(`Importing Skill Description - JournalEntry`);
- compendiumItem = new JournalEntry(item, { temporary: true });
- this._importLogger(`New Skill Description ${d.Name} : ${JSON.stringify(compendiumItem)}`);
- let id = await pack.importEntity(compendiumItem);
- } else {
- CONFIG.logger.debug(`Updating Skill Description - JournalEntry`);
- let updateData = item;
- updateData["_id"] = entry._id;
- this._importLogger(`Updating Skill Description ${d.Name} : ${JSON.stringify(updateData)}`);
- pack.updateEntity(updateData);
- }
- //}
- currentCount += 1;
-
- $(".skills .import-progress-bar")
- .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
- .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
- this._importLogger(`End importing Skill Description ${d.Name}`);
- } catch (err) {
- CONFIG.logger.error(`Error importing record : `, err);
- }
- }
- }
- }
- }
-
- async _handleSignatureAbilties(xmlDoc, zip) {
- try {
- const sigAbilityNode = xmlDoc.getElementsByTagName("SigAbilityNode");
-
- if (sigAbilityNode.length > 0) {
- const signatureAbilityUpgrades = JXON.xmlToJs(xmlDoc);
-
- const signatureAbilityFiles = Object.values(zip.files).filter((file) => {
- return !file.dir && file.name.split(".").pop() === "xml" && file.name.includes("/SigAbilities/");
- });
-
- if (signatureAbilityUpgrades.SigAbilityNodes?.SigAbilityNode?.length > 0 && signatureAbilityFiles.length > 0) {
- $(".import-progress.signatureabilities").toggleClass("import-hidden");
- let pack = await this._getCompendiumPack("Item", `oggdude.SignatureAbilities`);
- let totalCount = signatureAbilityFiles.length;
- let currentCount = 0;
- this._importLogger(`Beginning import of ${signatureAbilityFiles.length} signature abilities`);
-
- await this.asyncForEach(signatureAbilityFiles, async (file) => {
- try {
- const data = await zip.file(file.name).async("text");
- const xmlDoc1 = ImportHelpers.stringToXml(data);
- const sa = JXON.xmlToJs(xmlDoc1);
-
- let signatureAbility = {
- name: sa.SigAbility.Name,
- type: "signatureability",
- flags: {
- ffgimportid: sa.SigAbility.Key,
- },
- data: {
- description: sa.SigAbility.Description,
- attributes: {},
- upgrades: {},
- },
- };
-
- signatureAbility.data.description += ImportHelpers.getSources(sa?.SigAbility?.Sources ?? sa?.SigAbility?.Source);
-
- for (let i = 1; i < sa.SigAbility.AbilityRows.AbilityRow.length; i += 1) {
- try {
- const row = sa.SigAbility.AbilityRows.AbilityRow[i];
- row.Abilities.Key.forEach((keyName, index) => {
- let rowAbility = {};
-
- let rowAbilityData = signatureAbilityUpgrades.SigAbilityNodes.SigAbilityNode.find((a) => {
- return a.Key === keyName;
- });
-
- rowAbility.name = rowAbilityData.Name;
- rowAbility.description = rowAbilityData.Description;
- rowAbility.visible = true;
- rowAbility.attributes = {};
-
- if (row.Directions.Direction[index].Up) {
- rowAbility["links-top-1"] = true;
- }
-
- switch (row.AbilitySpan.Span[index]) {
- case "1":
- rowAbility.size = "single";
- break;
- case "2":
- rowAbility.size = "double";
- if (index < 3 && row.Directions.Direction[index + 1].Up) {
- rowAbility["links-top-2"] = true;
- }
- break;
- case "3":
- rowAbility.size = "triple";
- if (index < 2 && row.Directions.Direction[index + 1].Up) {
- rowAbility["links-top-2"] = true;
- }
- if (index < 2 && row.Directions.Direction[index + 2].Up) {
- rowAbility["links-top-3"] = true;
- }
- break;
- case "4":
- rowAbility.size = "full";
- if (index < 1 && row.Directions.Direction[index + 1].Up) {
- rowAbility["links-top-2"] = true;
- }
- if (index < 1 && row.Directions.Direction[index + 2].Up) {
- rowAbility["links-top-3"] = true;
- }
- if (index < 1 && row.Directions.Direction[index + 3].Up) {
- rowAbility["links-top-4"] = true;
- }
- break;
- default:
- rowAbility.size = "single";
- rowAbility.visible = false;
- }
-
- if (row.Directions.Direction[index].Right) {
- rowAbility["links-right"] = true;
- }
-
- const talentKey = `upgrade${(i - 1) * 4 + index}`;
- signatureAbility.data.upgrades[talentKey] = rowAbility;
- });
- } catch (err) {
- CONFIG.logger.error(`Error importing record : `, err);
- }
- }
-
- let imgPath = await ImportHelpers.getImageFilename(zip, "SigAbilities", "", signatureAbility.flags.ffgimportid);
- if (imgPath) {
- signatureAbility.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
- }
-
- let compendiumItem;
- await pack.getIndex();
- let entry = pack.index.find((e) => e.name === signatureAbility.name);
-
- if (!entry) {
- CONFIG.logger.debug(`Importing Signature Ability - Item`);
- compendiumItem = new Item(signatureAbility, { temporary: true });
- this._importLogger(`New Signature Ability ${signatureAbility.name} : ${JSON.stringify(compendiumItem)}`);
- pack.importEntity(compendiumItem);
- } else {
- CONFIG.logger.debug(`Updating Signature Ability - Item`);
- //let updateData = ImportHelpers.buildUpdateData(power);
- let updateData = signatureAbility;
- updateData["_id"] = entry._id;
- this._importLogger(`Updating Signature Ability ${signatureAbility.name} : ${JSON.stringify(updateData)}`);
- pack.updateEntity(updateData);
- }
- currentCount += 1;
-
- $(".signatureabilities .import-progress-bar")
- .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
- .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
- this._importLogger(`End importing Signature Ability ${sa.SigAbility.Name}`);
- } catch (err) {
- CONFIG.logger.error(`Error importing record : `, err);
- }
- });
- this._importLogger(`Completed Signature Ability Import`);
- } else {
- CONFIG.logger.error(`Error importing signature abilities, found ${signatureAbilityFiles.length} signature ability files and ${signatureAbilityUpgrades.SigAbilityNodes?.SigAbilityNode?.length} upgrades in data set`);
- this._importLogger(`Error importing signature abilities, found ${signatureAbilityFiles.length} signature ability files and ${signatureAbilityUpgrades.SigAbilityNodes?.SigAbilityNode?.length} upgrades in data set`);
- }
- }
- } catch (err) {
- CONFIG.logger.error(`Failed to import signature abilities`, err);
- this._importLogger(`Failed to import signature abilities`, err);
- }
- }
-
- async _loadItemDescriptors(xmlDoc) {
- this._importLogger(`Starting Item Qualities Import`);
- const descriptors = xmlDoc.getElementsByTagName("ItemDescriptor");
- if (descriptors.length > 0) {
- let totalCount = descriptors.length;
- let currentCount = 0;
- $(".import-progress.itemdescriptors").toggleClass("import-hidden");
- let pack = await this._getCompendiumPack("JournalEntry", `oggdude.ItemQualities`);
- CONFIG.temporary["descriptors"] = {};
-
- await this.asyncForEach(descriptors, async (descriptor) => {
- try {
- const d = JXON.xmlToJs(descriptor);
-
- //if (d.Type) {
- let itemDescriptor = {
- name: d.Name,
- flags: {
- ffgimportid: d.Key,
- },
- content: d?.Description?.length && d.Description.length > 0 ? d.Description : "Dataset did not have a description",
- };
-
- itemDescriptor.content += ImportHelpers.getSources(d?.Sources ?? d?.Source);
-
- let compendiumItem;
- await pack.getIndex();
- let entry = pack.index.find((e) => e.name === itemDescriptor.name);
-
- if (!entry) {
- CONFIG.logger.debug(`Importing Item Quality - JournalEntry`);
- compendiumItem = new JournalEntry(itemDescriptor, { temporary: true });
- this._importLogger(`New item quality ${d.Name} : ${JSON.stringify(compendiumItem)}`);
- let id = await pack.importEntity(compendiumItem);
- CONFIG.temporary["descriptors"][d.Key] = id.id;
- } else {
- CONFIG.logger.debug(`Updating Item Quality - JournalEntry`);
- //let updateData = ImportHelpers.buildUpdateData(itemDescriptor);
- let updateData = itemDescriptor;
- updateData["_id"] = entry._id;
- CONFIG.temporary["descriptors"][d.Key] = entry._id;
- this._importLogger(`Updating item quality ${d.Name} : ${JSON.stringify(updateData)}`);
- pack.updateEntity(updateData);
- }
- //}
- currentCount += 1;
-
- $(".itemdescriptors .import-progress-bar")
- .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
- .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
- this._importLogger(`End importing item quality ${d.Name}`);
- } catch (err) {
- CONFIG.logger.error(`Error importing record : `, err);
- }
- });
- }
- this._importLogger(`Completed Item Qualities Import`);
- }
-
- async _handleTalents(xmlDoc, zip) {
- this._importLogger(`Starting Talent Import`);
- const talents = xmlDoc.getElementsByTagName("Talent");
- if (talents.length > 0) {
- let totalCount = talents.length;
- let currentCount = 0;
- this._importLogger(`Beginning import of ${talents.length} talents`);
- $(".import-progress.talents").toggleClass("import-hidden");
- let pack = await this._getCompendiumPack("Item", `oggdude.Talents`);
-
- for (let i = 0; i < talents.length; i += 1) {
- try {
- const talent = talents[i];
- const importkey = talent.getElementsByTagName("Key")[0]?.textContent;
- const name = talent.getElementsByTagName("Name")[0]?.textContent;
- const description = talent.getElementsByTagName("Description")[0]?.textContent;
- const ranked = talent.getElementsByTagName("Ranked")[0]?.textContent === "true" ? true : false;
- const activationValue = talent.getElementsByTagName("ActivationValue")[0]?.textContent;
-
- this._importLogger(`Start importing talent ${name}`);
-
- let activation = "Passive";
-
- switch (activationValue) {
- case "taManeuver":
- activation = "Active (Maneuver)";
- break;
- case "taAction":
- activation = "Active (Action)";
- break;
- case "taIncidental":
- activation = "Active (Incidental)";
- break;
- case "taIncidentalOOT":
- activation = "Active (Incidental, Out of Turn)";
- break;
- default:
- activation = "Passive";
- }
-
- const forcetalent = talent.getElementsByTagName("ForceTalent")[0]?.textContent === "true" ? true : false;
- const conflicttalent = talent.getElementsByTagName("Conflict")[0]?.textContent?.length > 0 ? true : false;
-
- const item = {
- name,
- type: "talent",
- flags: {
- ffgimportid: importkey,
- },
- data: {
- attributes: {},
- description,
- ranks: {
- ranked,
- },
- activation: {
- value: activation,
- },
- isForceTalent: forcetalent,
- isConflictTalent: conflicttalent,
- },
- };
-
- const d = JXON.xmlToJs(talents[i]);
- item.data.description += ImportHelpers.getSources(d?.Sources ?? d?.Source);
-
- const attributes = talent.getElementsByTagName("Attributes")[0];
- if (attributes) {
- item.data.attributes = Object.assign(item.data.attributes, ImportHelpers.getAttributeObject(attributes));
- }
-
- const careerskills = talent.getElementsByTagName("ChooseCareerSkills")[0];
- if (careerskills) {
- const cs = JXON.xmlToJs(careerskills);
-
- const funcAddCareerSkill = (s) => {
- if (Object.keys(CONFIG.temporary.skills).includes(s)) {
- const skill = CONFIG.temporary.skills[s];
- const careerKey = Object.keys(item.data.attributes).length + 1;
- item.data.attributes[`attr${careerKey}`] = {
- mod: skill,
- modtype: "Career Skill",
- value: true,
- };
- }
- };
-
- if (cs?.NewSkills?.Key) {
- if (Array.isArray(cs.NewSkills.Key)) {
- cs.NewSkills.Key.forEach((s) => {
- funcAddCareerSkill(s);
- });
- } else {
- funcAddCareerSkill(cs.NewSkills.Key);
- }
- }
- }
-
- const diemodifiers = talent.getElementsByTagName("DieModifiers")[0];
- if (diemodifiers) {
- const diemod = JXON.xmlToJs(diemodifiers);
- if (diemod.DieModifier) {
- if (!Array.isArray(diemod.DieModifier)) {
- diemod.DieModifier = [diemod.DieModifier];
- }
- diemod.DieModifier.forEach((mod) => {
- const attr = ImportHelpers.getBaseModAttributeObject({
- Key: mod.SkillKey,
- ...mod,
- });
- if (attr) {
- item.data.attributes[attr.type] = attr.value;
- }
- });
- }
- }
-
- // does an image exist?
- let imgPath = await ImportHelpers.getImageFilename(zip, "Talent", "", item.flags.ffgimportid);
- if (imgPath) {
- item.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
- }
-
- let compendiumItem;
- await pack.getIndex();
- let entry = pack.index.find((e) => e.name === item.name);
-
- if (!entry) {
- CONFIG.logger.debug(`Importing Talent - Item`);
- compendiumItem = new Item(item, { temporary: true });
- this._importLogger(`New talent ${name} : ${JSON.stringify(compendiumItem)}`);
- pack.importEntity(compendiumItem);
- } else {
- CONFIG.logger.debug(`Update Talent - Item`);
- //let updateData = ImportHelpers.buildUpdateData(item);
- let updateData = item;
- updateData["_id"] = entry._id;
- this._importLogger(`Updating talent ${name} : ${JSON.stringify(updateData)}`);
- pack.updateEntity(updateData);
- }
- currentCount += 1;
-
- $(".talents .import-progress-bar")
- .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
- .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
- this._importLogger(`End importing talent ${name}`);
- } catch (err) {
- CONFIG.logger.error(`Error importing record : `, err);
- this._importLogger(`Error importing talent: ${JSON.stringify(err)}`);
- }
- }
- }
- this._importLogger(`Completed Talent Import`);
- }
-
- async _handleForcePowers(xmlDoc, zip) {
- this._importLogger(`Starting Force Power Import`);
- const forceabilities = xmlDoc.getElementsByTagName("ForceAbility");
- if (forceabilities.length > 0) {
- $(".import-progress.force").toggleClass("import-hidden");
- let pack = await this._getCompendiumPack("Item", `oggdude.ForcePowers`);
-
- const fa = JXON.xmlToJs(xmlDoc);
- // now we need to loop through the files in the Force Powers folder
-
- const forcePowersFiles = Object.values(zip.files).filter((file) => {
- return !file.dir && file.name.split(".").pop() === "xml" && file.name.includes("/Force Powers/");
- });
-
- let totalCount = forcePowersFiles.length;
- let currentCount = 0;
- this._importLogger(`Beginning import of ${forcePowersFiles.length} force powers`);
-
- await this.asyncForEach(forcePowersFiles, async (file) => {
- try {
- const data = await zip.file(file.name).async("text");
- const xmlDoc1 = ImportHelpers.stringToXml(data);
- const fp = JXON.xmlToJs(xmlDoc1);
-
- // setup the base information
-
- let power = {
- name: fp.ForcePower.Name,
- type: "forcepower",
- flags: {
- ffgimportid: fp.ForcePower.Key,
- },
- data: {
- attributes: {},
- upgrades: {},
- },
- };
-
- this._importLogger(`Start importing force power ${fp.ForcePower.Name}`);
-
- // get the basic power informatio
- const importKey = fp.ForcePower.AbilityRows.AbilityRow[0].Abilities.Key[0];
-
- let forceAbility = fa.ForceAbilities.ForceAbility.find((ability) => {
- return ability.Key === importKey;
- });
-
- power.data.description = forceAbility.Description;
- power.data.description += ImportHelpers.getSources(fp?.ForcePower?.Sources ?? fp?.ForcePower?.Source);
-
- if (forceAbility?.DieModifiers?.DieModifier) {
- if (!Array.isArray(forceAbility.DieModifiers.DieModifier)) {
- forceAbility.DieModifiers.DieModifier = [forceAbility.DieModifiers.DieModifier];
- }
- forceAbility.DieModifiers.DieModifier.forEach((mod) => {
- const attr = ImportHelpers.getBaseModAttributeObject({
- Key: mod.SkillKey,
- ...mod,
- });
- if (attr) {
- power.data.attributes[attr.type] = attr.value;
- }
- });
- }
-
- // next we will parse the rows
-
- for (let i = 1; i < fp.ForcePower.AbilityRows.AbilityRow.length; i += 1) {
- try {
- const row = fp.ForcePower.AbilityRows.AbilityRow[i];
- row.Abilities.Key.forEach((keyName, index) => {
- let rowAbility = {};
-
- let rowAbilityData = fa.ForceAbilities.ForceAbility.find((a) => {
- return a.Key === keyName;
- });
-
- rowAbility.name = rowAbilityData.Name;
- rowAbility.description = rowAbilityData.Description;
- rowAbility.cost = row.Costs.Cost[index];
- rowAbility.visible = true;
- rowAbility.attributes = {};
-
- if (row.Directions.Direction[index].Up) {
- rowAbility["links-top-1"] = true;
- }
-
- switch (row.AbilitySpan.Span[index]) {
- case "1":
- rowAbility.size = "single";
- break;
- case "2":
- rowAbility.size = "double";
- if (index < 3 && row.Directions.Direction[index + 1].Up) {
- rowAbility["links-top-2"] = true;
- }
- break;
- case "3":
- rowAbility.size = "triple";
- if (index < 2 && row.Directions.Direction[index + 1].Up) {
- rowAbility["links-top-2"] = true;
- }
- if (index < 2 && row.Directions.Direction[index + 2].Up) {
- rowAbility["links-top-3"] = true;
- }
- break;
- case "4":
- rowAbility.size = "full";
- if (index < 1 && row.Directions.Direction[index + 1].Up) {
- rowAbility["links-top-2"] = true;
- }
- if (index < 1 && row.Directions.Direction[index + 2].Up) {
- rowAbility["links-top-3"] = true;
- }
- if (index < 1 && row.Directions.Direction[index + 3].Up) {
- rowAbility["links-top-4"] = true;
- }
- break;
- default:
- rowAbility.size = "single";
- rowAbility.visible = false;
- }
-
- if (row.Directions.Direction[index].Right) {
- rowAbility["links-right"] = true;
- }
-
- const funcAddUpgradeDieModifier = (mod) => {
- if (Object.keys(CONFIG.temporary.skills).includes(mod.SkillKey)) {
- // only handling boosts initially
- if (mod.BoostCount || mod.SetbackCount || mod.AddSetbackCount || mod.ForceCount) {
- const skill = CONFIG.temporary.skills[mod.SkillKey];
- const modKey = Object.keys(rowAbility.attributes).length + 1;
- let modtype = "Skill Boost";
- let count = 0;
- if (mod.AddSetbackCount) {
- modtype = "Skill Setback";
- count = mod.AddSetbackCount;
- }
- if (mod.SetbackCount) {
- modtype = "Skill Remove Setback";
- count = mod.SetbackCount;
- }
- if (mod.ForceCount) {
- modtype = "Force Boost";
- count = true;
- }
- if (mod.BoostCount) {
- count = mod.BoostCount;
- }
-
- rowAbility.attributes[`attr${keyName}${modKey}`] = {
- mod: skill,
- modtype,
- value: count,
- };
- }
- }
- };
-
- if (rowAbilityData?.DieModifiers?.DieModifier) {
- if (Array.isArray(rowAbilityData.DieModifiers.DieModifier)) {
- rowAbilityData.DieModifiers.DieModifier.forEach((mod) => {
- funcAddUpgradeDieModifier(mod);
- });
- } else {
- if (Object.keys(CONFIG.temporary.skills).includes(rowAbilityData.DieModifiers.DieModifier.SkillKey)) {
- funcAddUpgradeDieModifier(rowAbilityData.DieModifiers.DieModifier);
- }
- }
- }
-
- const talentKey = `upgrade${(i - 1) * 4 + index}`;
- power.data.upgrades[talentKey] = rowAbility;
- });
- } catch (err) {
- CONFIG.logger.error(`Error importing record : `, err);
- }
- }
-
- if (fp.ForcePower.AbilityRows.AbilityRow.length < 5) {
- for (let i = fp.ForcePower.AbilityRows.AbilityRow.length; i < 5; i += 1) {
- for (let index = 0; index < 4; index += 1) {
- const talentKey = `upgrade${(i - 1) * 4 + index}`;
-
- let rowAbility = { visible: false };
-
- power.data.upgrades[talentKey] = rowAbility;
- }
- }
- }
-
- // does an image exist?
- let imgPath = await ImportHelpers.getImageFilename(zip, "ForcePowers", "", power.flags.ffgimportid);
- if (imgPath) {
- power.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
- }
-
- let compendiumItem;
- await pack.getIndex();
- let entry = pack.index.find((e) => e.name === power.name);
-
- if (!entry) {
- CONFIG.logger.debug(`Importing Force Power - Item`);
- compendiumItem = new Item(power, { temporary: true });
- this._importLogger(`New force power ${fp.ForcePower.Name} : ${JSON.stringify(compendiumItem)}`);
- pack.importEntity(compendiumItem);
- } else {
- CONFIG.logger.debug(`Updating Force Power - Item`);
- //let updateData = ImportHelpers.buildUpdateData(power);
- let updateData = power;
- updateData["_id"] = entry._id;
- this._importLogger(`Updating force power ${fp.ForcePower.Name} : ${JSON.stringify(updateData)}`);
- pack.updateEntity(updateData);
- }
- currentCount += 1;
-
- $(".force .import-progress-bar")
- .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
- .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
- this._importLogger(`End importing force power ${fp.ForcePower.Name}`);
- } catch (err) {
- CONFIG.logger.error(`Error importing record : `, err);
- }
- });
- }
- this._importLogger(`Completed Force Power Import`);
- }
-
- async _handleGear(xmlDoc, zip) {
- this._importLogger(`Starting Gear Import`);
- const gear = xmlDoc.getElementsByTagName("Gear");
-
- if (gear.length > 0) {
- let totalCount = gear.length;
- let currentCount = 0;
- this._importLogger(`Beginning import of ${gear.length} gear`);
-
- $(".import-progress.gear").toggleClass("import-hidden");
- let pack = await this._getCompendiumPack("Item", `oggdude.Gear`);
-
- for (let i = 0; i < gear.length; i += 1) {
- try {
- const item = gear[i];
-
- const importkey = item.getElementsByTagName("Key")[0]?.textContent;
- const name = item.getElementsByTagName("Name")[0]?.textContent;
- const description = item.getElementsByTagName("Description")[0]?.textContent;
- const price = item.getElementsByTagName("Price")[0]?.textContent;
- const rarity = item.getElementsByTagName("Rarity")[0]?.textContent;
- const encumbrance = item.getElementsByTagName("Encumbrance")[0]?.textContent;
- const type = item.getElementsByTagName("Type")[0]?.textContent;
- const restricted = item.getElementsByTagName("Restricted")[0]?.textContent === "true" ? true : false;
-
- this._importLogger(`Start importing gear ${name}`);
-
- const newItem = {
- name,
- type: "gear",
- flags: {
- ffgimportid: importkey,
- },
- data: {
- attributes: {},
- description,
- encumbrance: {
- value: encumbrance,
- },
- price: {
- value: price,
- },
- rarity: {
- value: rarity,
- isrestricted: restricted,
- },
- },
- };
-
- const d = JXON.xmlToJs(item);
- newItem.data.description += ImportHelpers.getSources(d?.Sources ?? d?.Source);
-
- const baseMods = item.getElementsByTagName("BaseMods")[0];
- if (baseMods) {
- const mods = await ImportHelpers.getBaseModObject(baseMods);
- if (mods) {
- newItem.data.attributes = mods;
- }
- }
-
- // does an image exist?
- let imgPath = await ImportHelpers.getImageFilename(zip, "Equipment", "Gear", newItem.flags.ffgimportid);
- if (imgPath) {
- newItem.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
- }
-
- let compendiumItem;
- await pack.getIndex();
- let entry = pack.index.find((e) => e.name === newItem.name);
-
- if (!entry) {
- CONFIG.logger.debug(`Importing Gear - Item`);
- compendiumItem = new Item(newItem, { temporary: true });
- this._importLogger(`New gear ${name} : ${JSON.stringify(compendiumItem)}`);
- pack.importEntity(compendiumItem);
- } else {
- CONFIG.logger.debug(`Updating Gear - Item`);
- //let updateData = ImportHelpers.buildUpdateData(newItem);
- let updateData = newItem;
- updateData["_id"] = entry._id;
- this._importLogger(`Updating gear ${name} : ${JSON.stringify(updateData)}`);
- pack.updateEntity(updateData);
- }
- currentCount += 1;
-
- $(".gear .import-progress-bar")
- .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
- .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
- this._importLogger(`End importing gear ${name}`);
- } catch (err) {
- CONFIG.logger.error(`Error importing record : `, err);
- this._importLogger(`Error importing gear: ${JSON.stringify(err)}`);
- }
- }
- }
-
- this._importLogger(`Completed Gear Import`);
- }
-
- async _handleWeapons(xmlDoc, zip) {
- this._importLogger(`Starting Weapon Import`);
- const weapons = xmlDoc.getElementsByTagName("Weapon");
-
- if (weapons.length > 0) {
- let totalCount = weapons.length;
- let currentCount = 0;
- this._importLogger(`Beginning import of ${weapons.length} weapons`);
-
- $(".import-progress.weapons").toggleClass("import-hidden");
- let pack = await this._getCompendiumPack("Item", `oggdude.Weapons`);
-
- for (let i = 0; i < weapons.length; i += 1) {
- try {
- const weapon = weapons[i];
-
- const importkey = weapon.getElementsByTagName("Key")[0]?.textContent;
- const name = weapon.getElementsByTagName("Name")[0]?.textContent;
- const description = weapon.getElementsByTagName("Description")[0]?.textContent;
- const price = weapon.getElementsByTagName("Price")[0]?.textContent;
- const rarity = weapon.getElementsByTagName("Rarity")[0]?.textContent;
- const encumbrance = weapon.getElementsByTagName("Encumbrance")[0]?.textContent;
- const damage = weapon.getElementsByTagName("Damage")[0]?.textContent;
- const damageAdd = weapon.getElementsByTagName("DamageAdd")[0]?.textContent;
- const crit = weapon.getElementsByTagName("Crit")[0]?.textContent;
-
- const skillkey = weapon.getElementsByTagName("SkillKey")[0]?.textContent;
- const range = weapon.getElementsByTagName("RangeValue")[0]?.textContent.replace("wr", "");
- const hardpoints = weapon.getElementsByTagName("HP")[0]?.textContent;
-
- const weaponType = weapon.getElementsByTagName("Type")[0]?.textContent;
- const restricted = weapon.getElementsByTagName("Restricted")[0]?.textContent === "true" ? true : false;
-
- this._importLogger(`Start importing weapon ${name}`);
-
- let skill = "";
-
- switch (skillkey) {
- case "RANGLT":
- skill = "Ranged: Light";
- break;
- case "RANGHVY":
- skill = "Ranged: Heavy";
- break;
- case "GUNN":
- skill = "Gunnery";
- break;
- case "BRAWL":
- skill = "Brawl";
- break;
- case "MELEE":
- skill = "Melee";
- break;
- case "LTSABER":
- skill = "Lightsaber";
- break;
- default:
- }
-
- const fp = JXON.xmlToJs(weapon);
-
- let newItem = {
- name,
- type: weaponType === "Vehicle" ? "shipweapon" : "weapon",
- flags: {
- ffgimportid: importkey,
- },
- data: {
- attributes: {},
- description,
- encumbrance: {
- value: encumbrance,
- },
- price: {
- value: price,
- },
- rarity: {
- value: rarity,
- isrestricted: restricted,
- },
- damage: {
- value: !damage ? damageAdd : damage,
- },
- crit: {
- value: crit,
- },
- special: {
- value: "",
- },
- skill: {
- value: skill,
- },
- range: {
- value: range,
- },
- hardpoints: {
- value: hardpoints,
- },
- },
- };
-
- const d = JXON.xmlToJs(weapon);
- newItem.data.description += ImportHelpers.getSources(d?.Sources ?? d?.Source);
-
- const qualities = [];
-
- if (fp?.Qualities?.Quality && !Array.isArray(fp?.Qualities?.Quality)) {
- fp.Qualities.Quality = [fp.Qualities.Quality];
- }
-
- if (fp?.Qualities?.Quality && fp.Qualities.Quality.length > 0) {
- await this.asyncForEach(fp.Qualities.Quality, async (quality) => {
- let descriptor = await ImportHelpers.findCompendiumEntityByImportId("JournalEntry", quality.Key);
-
- if (descriptor?.compendium?.metadata) {
- qualities.push(` ${quality.Key} ${quality.Count ? quality.Count : ""}`);
- } else {
- qualities.push(`${quality.Key} ${quality.Count ? quality.Count : ""}`);
- }
-
- if (quality.Key === "DEFENSIVE") {
- const nk = Object.keys(newItem.data.attributes).length + 1;
- const count = quality.Count ? parseInt(quality.Count) : 0;
-
- newItem.data.attributes[`attr${nk}`] = {
- isCheckbox: false,
- mod: "Defence-Melee",
- modtype: "Stat",
- value: count,
- };
- }
- });
- }
-
- newItem.data.special.value = qualities.join(", ");
-
- if ((skill.includes("Melee") || skill.includes("Brawl") || skill.includes("Lightsaber")) && damage === "0") {
- newItem.data.skill.useBrawn = true;
- }
-
- const baseMods = weapon.getElementsByTagName("BaseMods")[0];
- if (baseMods) {
- const mods = await ImportHelpers.getBaseModObject(baseMods);
- if (mods) {
- newItem.data.attributes = mods;
- }
- }
-
- if (damageAdd && parseInt(damageAdd, 10) > 0 && newItem.type === "weapon") {
- const nk = Object.keys(newItem.data.attributes).length + 1;
-
- newItem.data.attributes[`attr${nk}`] = {
- isCheckbox: false,
- mod: "damage",
- modtype: "Weapon Stat",
- value: damageAdd,
- };
- }
-
- // does an image exist?
- let imgPath = await ImportHelpers.getImageFilename(zip, "Equipment", "Weapon", newItem.flags.ffgimportid);
- if (imgPath) {
- newItem.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
- }
-
- let compendiumItem;
- await pack.getIndex();
- let entry = pack.index.find((e) => e.name === newItem.name);
-
- if (!entry) {
- CONFIG.logger.debug(`Importing Weapon - Item`);
- compendiumItem = new Item(newItem, { temporary: true });
- this._importLogger(`New weapon ${name} : ${JSON.stringify(compendiumItem)}`);
- pack.importEntity(compendiumItem);
- } else {
- CONFIG.logger.debug(`Updating Weapon - Item`);
- //let updateData = ImportHelpers.buildUpdateData(newItem);
- let updateData = newItem;
- updateData["_id"] = entry._id;
- this._importLogger(`Updating weapon ${name} : ${JSON.stringify(updateData)}`);
- pack.updateEntity(updateData);
- }
- currentCount += 1;
-
- $(".weapons .import-progress-bar")
- .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
- .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
- this._importLogger(`End importing weapon ${name}`);
- } catch (err) {
- CONFIG.logger.error(`Error importing record (${weapons[i].getElementsByTagName("Name")[0]?.textContent}) : `, err);
- this._importLogger(`Error importing weapon: ${JSON.stringify(err)}`);
- }
- }
- }
- this._importLogger(`Completed Weapon Import`);
- }
-
- async _handleArmor(xmlDoc, zip) {
- this._importLogger(`Starting Armor Import`);
-
- const fa = JXON.xmlToJs(xmlDoc);
-
- const armors = xmlDoc.getElementsByTagName("Armor");
-
- if (armors.length > 0) {
- let totalCount = armors.length;
- let currentCount = 0;
- this._importLogger(`Beginning import of ${armors.length} armor`);
-
- $(".import-progress.armor").toggleClass("import-hidden");
- let pack = await this._getCompendiumPack("Item", `oggdude.Armor`);
-
- for (let i = 0; i < armors.length; i += 1) {
- try {
- const armor = armors[i];
-
- const importkey = armor.getElementsByTagName("Key")[0]?.textContent;
- const name = armor.getElementsByTagName("Name")[0]?.textContent;
- const description = armor.getElementsByTagName("Description")[0]?.textContent;
- const price = armor.getElementsByTagName("Price")[0]?.textContent;
- const rarity = armor.getElementsByTagName("Rarity")[0]?.textContent;
- const restricted = armor.getElementsByTagName("Restricted")[0]?.textContent === "true" ? true : false;
- const encumbrance = armor.getElementsByTagName("Encumbrance")[0]?.textContent;
-
- const defense = armor.getElementsByTagName("Defense")[0]?.textContent;
- const soak = armor.getElementsByTagName("Soak")[0]?.textContent;
- const hardpoints = armor.getElementsByTagName("HP")[0]?.textContent;
-
- this._importLogger(`Start importing armor ${name}`);
-
- let newItem = {
- name,
- type: "armour",
- flags: {
- ffgimportid: importkey,
- },
- data: {
- attributes: {},
- description,
- encumbrance: {
- value: encumbrance,
- },
- price: {
- value: price,
- },
- rarity: {
- value: rarity,
- isrestricted: restricted,
- },
- defence: {
- value: defense,
- },
- soak: {
- value: soak,
- },
- hardpoints: {
- value: hardpoints,
- },
- },
- };
-
- const d = JXON.xmlToJs(armor);
- newItem.data.description += ImportHelpers.getSources(d?.Sources ?? d?.Source);
-
- const baseMods = armor.getElementsByTagName("BaseMods")[0];
- if (baseMods) {
- const mods = await ImportHelpers.getBaseModObject(baseMods);
- if (mods) {
- newItem.data.attributes = mods;
- }
- }
-
- // does an image exist?
- let imgPath = await ImportHelpers.getImageFilename(zip, "Equipment", "Armor", newItem.flags.ffgimportid);
- if (imgPath) {
- newItem.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
- }
-
- let compendiumItem;
- await pack.getIndex();
- let entry = pack.index.find((e) => e.name === newItem.name);
-
- if (!entry) {
- CONFIG.logger.debug(`Importing Armor - Item`);
- compendiumItem = new Item(newItem, { temporary: true });
- this._importLogger(`New armor ${name} : ${JSON.stringify(compendiumItem)}`);
- pack.importEntity(compendiumItem);
- } else {
- CONFIG.logger.debug(`Updating Armor - Item`);
- //let updateData = ImportHelpers.buildUpdateData(newItem);
- let updateData = newItem;
- updateData["_id"] = entry._id;
- this._importLogger(`Updating armor ${name} : ${JSON.stringify(updateData)}`);
- pack.updateEntity(updateData);
- }
- currentCount += 1;
-
- $(".armor .import-progress-bar")
- .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
- .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
- this._importLogger(`End importing armor ${name}`);
- } catch (err) {
- CONFIG.logger.error(`Error importing record : `, err);
- this._importLogger(`Error importing armor: ${JSON.stringify(err)}`);
- }
- }
- }
- this._importLogger(`Completed Armor Import`);
- }
-
- async _handleSpecializations(zip) {
- this._importLogger(`Starting Specialization Import`);
-
- const specializationFiles = Object.values(zip.files).filter((file) => {
- return !file.dir && file.name.split(".").pop() === "xml" && file.name.includes("/Specializations/");
- });
-
- let totalCount = specializationFiles.length;
- let currentCount = 0;
-
- if (specializationFiles.length > 0) {
- $(".import-progress.specializations").toggleClass("import-hidden");
- let pack = await this._getCompendiumPack("Item", `oggdude.Specializations`);
-
- await this.asyncForEach(specializationFiles, async (file) => {
- try {
- const data = await zip.file(file.name).async("text");
- const xmlDoc = ImportHelpers.stringToXml(data);
- const specData = JXON.xmlToJs(xmlDoc);
-
- let specialization = {
- name: specData.Specialization.Name,
- type: "specialization",
- flags: {
- ffgimportid: specData.Specialization.Key,
- },
- data: {
- attributes: {},
- description: specData.Specialization.Description,
- talents: {},
- careerskills: {},
- isReadOnly: true,
- },
- };
-
- specialization.data.description += ImportHelpers.getSources(specData?.Specialization?.Sources ?? specData?.Specialization?.Source);
- this._importLogger(`Start importing Specialization ${specialization.name}`);
-
- // assign career skills
- try {
- specData.Specialization.CareerSkills.Key.forEach((skillKey) => {
- let skill = CONFIG.temporary.skills[skillKey];
-
- if (skill) {
- // add career skill
- const careerKey = Object.keys(specialization.data.attributes).length + 1;
- specialization.data.attributes[`attr${careerKey}`] = {
- mod: skill,
- modtype: "Career Skill",
- value: true,
- };
-
- // most specialization give players choice were to put points, create modifier but put value of 0
- const skillKey = Object.keys(specialization.data.attributes).length + 1;
- specialization.data.attributes[`attr${skillKey}`] = {
- mod: skill,
- modtype: "Skill Rank",
- value: "0",
- };
- }
- });
- } catch (err) {
- // skipping career skills
- }
-
- if (specData.Specialization?.Attributes?.ForceRating) {
- specialization.data.attributes[`attrForceRating`] = {
- mod: "ForcePool",
- modtype: "Stat",
- value: 1,
- };
- }
-
- for (let i = 0; i < specData.Specialization.TalentRows.TalentRow.length; i += 1) {
- const row = specData.Specialization.TalentRows.TalentRow[i];
-
- await this.asyncForEach(row.Talents.Key, async (keyName, index) => {
- let rowTalent = {};
-
- let talentItem = ImportHelpers.findEntityByImportId("items", keyName);
- if (!talentItem) {
- talentItem = await ImportHelpers.findCompendiumEntityByImportId("Item", keyName);
- }
-
- if (talentItem) {
- rowTalent.name = talentItem.data.name;
- rowTalent.description = talentItem.data.data.description;
- rowTalent.activation = talentItem.data.data.activation.value;
- rowTalent.activationLabel = talentItem.data.data.activation.label;
- rowTalent.isForceTalent = talentItem.data.data.isForceTalent === "true" ? true : false;
- rowTalent.isConflictTalent = talentItem.data.data.isConflictTalent === "true" ? true : false;
- rowTalent.isRanked = talentItem.data.data.ranks.ranked === "true" ? true : false;
- rowTalent.size = "single";
- rowTalent.canLinkTop = true;
- rowTalent.canLinkRight = true;
- rowTalent.itemId = talentItem.data._id;
- rowTalent.attributes = talentItem.data.data.attributes;
-
- if (row.Directions.Direction[index].Up && row.Directions.Direction[index].Up === "true") {
- rowTalent["links-top-1"] = true;
- }
-
- if (row.Directions.Direction[index].Right && row.Directions.Direction[index].Right === "true") {
- rowTalent["links-right"] = true;
- }
-
- if (talentItem.compendium) {
- rowTalent.pack = `${talentItem.compendium.metadata.package}.${talentItem.compendium.metadata.name}`;
- }
-
- const talentKey = `talent${i * 4 + index}`;
- specialization.data.talents[talentKey] = rowTalent;
- }
- });
- }
-
- // does an image exist?
- let imgPath = await ImportHelpers.getImageFilename(zip, "Specialization", "", specialization.flags.ffgimportid);
- if (imgPath) {
- specialization.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
- }
-
- let compendiumItem;
- await pack.getIndex();
- let entry = pack.index.find((e) => e.name === specialization.name);
- if (!entry) {
- CONFIG.logger.debug(`Importing Specialization - Item`);
- compendiumItem = new Item(specialization, { temporary: true });
- this._importLogger(`New Specialization ${specialization.name} : ${JSON.stringify(compendiumItem)}`);
- pack.importEntity(compendiumItem);
- } else {
- CONFIG.logger.debug(`Updating Specialization - Item`);
- //let updateData = ImportHelpers.buildUpdateData(specialization);
- let updateData = specialization;
- updateData["_id"] = entry._id;
- this._importLogger(`Updating Specialization ${specialization.name} : ${JSON.stringify(updateData)}`);
- pack.updateEntity(updateData);
- }
- currentCount += 1;
-
- $(".specializations .import-progress-bar")
- .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
- .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
- this._importLogger(`End importing Specialization ${specialization.name}`);
- } catch (err) {
- CONFIG.logger.error(`Error importing record : `, err);
- }
- });
- }
-
- this._importLogger(`Completed Specialization Import`);
- }
-
- async _handleCareers(zip) {
- this._importLogger(`Starting Career Import`);
-
- const careerFiles = Object.values(zip.files).filter((file) => {
- return !file.dir && file.name.split(".").pop() === "xml" && file.name.includes("/Careers/");
- });
-
- let totalCount = careerFiles.length;
- let currentCount = 0;
-
- if (careerFiles.length > 0) {
- $(".import-progress.careers").toggleClass("import-hidden");
- let pack = await this._getCompendiumPack("Item", `oggdude.Careers`);
-
- await this.asyncForEach(careerFiles, async (file) => {
- try {
- const data = await zip.file(file.name).async("text");
- const xmlDoc = ImportHelpers.stringToXml(data);
- const careerData = JXON.xmlToJs(xmlDoc);
-
- let career = {
- name: careerData.Career.Name,
- type: "career",
- flags: {
- ffgimportid: careerData.Career.Key,
- },
- data: {
- attributes: {},
- description: careerData.Career.Description,
- },
- };
-
- career.data.description += ImportHelpers.getSources(careerData?.Career?.Sources ?? careerData?.Career?.Source);
-
- this._importLogger(`Start importing Career ${career.name}`);
-
- careerData.Career.CareerSkills.Key.forEach((skillKey) => {
- let skill = CONFIG.temporary.skills[skillKey];
- if (skill) {
- const careerKey = Object.keys(career.data.attributes).length + 1;
- career.data.attributes[`attr${careerKey}`] = {
- mod: skill,
- modtype: "Career Skill",
- value: true,
- };
- const skillKey = Object.keys(career.data.attributes).length + 1;
- career.data.attributes[`attr${skillKey}`] = {
- mod: skill,
- modtype: "Skill Rank",
- value: "0",
- };
- }
- });
-
- // does an image exist?
- let imgPath = await ImportHelpers.getImageFilename(zip, "Career", "", career.flags.ffgimportid);
- if (imgPath) {
- career.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
- }
-
- let compendiumItem;
- await pack.getIndex();
- let entry = pack.index.find((e) => e.name === career.name);
- if (!entry) {
- CONFIG.logger.debug(`Importing Career - Item`);
- compendiumItem = new Item(career, { temporary: true });
- this._importLogger(`New Career ${career.name} : ${JSON.stringify(compendiumItem)}`);
- pack.importEntity(compendiumItem);
- } else {
- CONFIG.logger.debug(`Updating Career - Item`);
- //let updateData = ImportHelpers.buildUpdateData(career);
- let updateData = career;
- updateData["_id"] = entry._id;
- this._importLogger(`Updating Career ${career.name} : ${JSON.stringify(updateData)}`);
- pack.updateEntity(updateData);
- }
- currentCount += 1;
-
- $(".careers .import-progress-bar")
- .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
- .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
- this._importLogger(`End importing Career ${career.name}`);
- } catch (err) {
- this._importLogger(`Error importing record : `, err);
- CONFIG.logger.error(`Error importing record : `, err);
- }
- });
- }
- }
-
- async _handleSpecies(zip) {
- this._importLogger(`Starting Species Import`);
-
- const speciesFiles = Object.values(zip.files).filter((file) => {
- return !file.dir && file.name.split(".").pop() === "xml" && file.name.includes("/Species/");
- });
-
- let totalCount = speciesFiles.length;
- let currentCount = 0;
-
- if (speciesFiles.length > 0) {
- $(".import-progress.species").toggleClass("import-hidden");
- let pack = await this._getCompendiumPack("Item", `oggdude.Species`);
- await this.asyncForEach(speciesFiles, async (file) => {
- try {
- const data = await zip.file(file.name).async("text");
- const xmlDoc = ImportHelpers.stringToXml(data);
- const speciesData = JXON.xmlToJs(xmlDoc);
-
- let species = {
- name: speciesData.Species.Name,
- type: "species",
- flags: {
- ffgimportid: speciesData.Species.Key,
- },
- data: {
- attributes: {},
- description: speciesData.Species.Description,
- },
- };
-
- species.data.description += ImportHelpers.getSources(speciesData?.Species?.Sources ?? speciesData?.Species?.Source);
-
- const funcAddAttribute = (modtype, mod, value, hidden) => {
- const charKey = Object.keys(species.data.attributes).length + 1;
- species.data.attributes[mod] = {
- mod,
- modtype,
- value: parseInt(value, 10),
- exclude: hidden,
- };
- };
-
- funcAddAttribute("Characteristic", "Brawn", speciesData.Species.StartingChars.Brawn, true);
- funcAddAttribute("Characteristic", "Agility", speciesData.Species.StartingChars.Agility, true);
- funcAddAttribute("Characteristic", "Intellect", speciesData.Species.StartingChars.Intellect, true);
- funcAddAttribute("Characteristic", "Cunning", speciesData.Species.StartingChars.Cunning, true);
- funcAddAttribute("Characteristic", "Willpower", speciesData.Species.StartingChars.Willpower, true);
- funcAddAttribute("Characteristic", "Presence", speciesData.Species.StartingChars.Presence, true);
-
- funcAddAttribute("Stat", "Wounds", speciesData.Species.StartingAttrs.WoundThreshold, true);
- funcAddAttribute("Stat", "Strain", speciesData.Species.StartingAttrs.StrainThreshold, true);
-
- if (speciesData?.Species?.SkillModifiers?.SkillModifier) {
- if (Array.isArray(speciesData.Species.SkillModifiers.SkillModifier)) {
- speciesData.Species.SkillModifiers.SkillModifier.forEach((s) => {
- let skill = CONFIG.temporary.skills[s.Key];
- if (skill) {
- funcAddAttribute("Skill Rank", skill, s.RankStart, false);
- }
- });
- } else {
- let skill = CONFIG.temporary.skills[speciesData.Species.SkillModifiers.SkillModifier.Key];
- if (skill) {
- funcAddAttribute("Skill Rank", skill, speciesData.Species.SkillModifiers.SkillModifier.RankStart, false);
- }
- }
- }
-
- let abilities = [];
-
- if (speciesData?.Species?.OptionChoices?.OptionChoice) {
- let options = speciesData.Species.OptionChoices.OptionChoice;
-
- if (!Array.isArray(speciesData.Species.OptionChoices.OptionChoice)) {
- options = [speciesData.Species.OptionChoices.OptionChoice];
- }
-
- options.forEach((o) => {
- let option = o.Options.Option;
- if (!Array.isArray(o.Options.Option)) {
- option = [o.Options.Option];
- }
- abilities.push(`${option[0].Name} : ${option[0].Description}
`);
- });
- }
-
- species.data.description = `Abilities
` + abilities.join("") + "" + species.data.description;
-
- // does an image exist?
- let imgPath = await ImportHelpers.getImageFilename(zip, "Species", "", species.flags.ffgimportid);
- if (imgPath) {
- species.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
- }
-
- let compendiumItem;
- await pack.getIndex();
- let entry = pack.index.find((e) => e.name === species.name);
- if (!entry) {
- CONFIG.logger.debug(`Importing Species - Item`);
- compendiumItem = new Item(species, { temporary: true });
- this._importLogger(`New Species ${species.name} : ${JSON.stringify(compendiumItem)}`);
- pack.importEntity(compendiumItem);
- } else {
- CONFIG.logger.debug(`Updating Species - Item`);
- //let updateData = ImportHelpers.buildUpdateData(species);
- let updateData = species;
-
- updateData["_id"] = entry._id;
- this._importLogger(`Updating Species ${species.name} : ${JSON.stringify(updateData)}`);
- pack.updateEntity(updateData);
- }
- currentCount += 1;
-
- $(".species .import-progress-bar")
- .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
- .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
- this._importLogger(`End importing Species ${species.name}`);
- } catch (err) {
- this._importLogger(`Error importing record : `, err);
- CONFIG.logger.error(`Error importing record : `, err);
- }
- });
- }
- }
-
- async _handleVehicles(zip) {
- this._importLogger(`Starting Vehicles Import`);
-
- const vehicleFiles = Object.values(zip.files).filter((file) => {
- return !file.dir && file.name.split(".").pop() === "xml" && file.name.includes("/Vehicles/");
- });
-
- let totalCount = vehicleFiles.length;
- let currentCount = 0;
-
- if (vehicleFiles.length > 0) {
- $(".import-progress.vehicles").toggleClass("import-hidden");
- let packSpace = await this._getCompendiumPack("Actor", `oggdude.Vehicles.Space`);
- let packPlanetary = await this._getCompendiumPack("Actor", `oggdude.Vehicles.Planetary`);
-
- const isSpace = (vehicle) => {
- let result = false;
-
- const spacecategories = ["Starship", "Non-Fighter Starship", "Capital Ship", "Bulk Transport", "Station", "Medium Transport"];
-
- const spacetypes = ["Space-dwelling Creature", "Hyperdrive Sled", "Hyperdrive Docking Ring"];
-
- if (vehicle?.Vehicle?.Categories?.Category) {
- if (Array.isArray(vehicle.Vehicle.Categories.Category)) {
- vehicle.Vehicle.Categories.Category.forEach((cat) => {
- if (spacecategories.includes(cat)) {
- result = true;
- }
- });
- } else {
- if (spacecategories.includes(vehicle.Vehicle.Categories.Category)) {
- result = true;
- }
- }
- } else {
- if (vehicle.Vehicle.Type) {
- if (spacetypes.includes(vehicle.Vehicle.Type)) {
- result = true;
- }
- }
- }
-
- return result;
- };
-
- await this.asyncForEach(vehicleFiles, async (file) => {
- try {
- const data = await zip.file(file.name).async("text");
- const xmlDoc = ImportHelpers.stringToXml(data);
- const vehicleData = JXON.xmlToJs(xmlDoc);
-
- let sensorRange;
-
- switch (vehicleData.Vehicle.SensorRangeValue) {
- case "srClose":
- sensorRange = "Close";
- break;
- case "srShort":
- sensorRange = "Short";
- break;
- case "srMedium":
- sensorRange = "Medium";
- break;
- case "srLong":
- sensorRange = "Long";
- break;
- case "srExtreme":
- sensorRange = "Extreme";
- break;
- default:
- sensorRange = "None";
- }
-
- let vehicle = {
- name: vehicleData.Vehicle.Name,
- type: "vehicle",
- flags: {
- ffgimportid: vehicleData.Vehicle.Key,
- },
- data: {
- biography: vehicleData.Vehicle.Description,
- stats: {
- silhouette: {
- value: parseInt(vehicleData.Vehicle.Silhouette, 10),
- },
- speed: {
- max: parseInt(vehicleData.Vehicle.Speed, 10),
- },
- handling: {
- value: parseInt(vehicleData.Vehicle.Handling, 10),
- },
- hullTrauma: {
- max: parseInt(vehicleData.Vehicle.HullTrauma, 10),
- },
- systemStrain: {
- max: parseInt(vehicleData.Vehicle.SystemStrain, 10),
- },
- shields: {
- fore: parseInt(vehicleData.Vehicle.DefFore, 10),
- port: parseInt(vehicleData.Vehicle.DefPort, 10),
- starboard: parseInt(vehicleData.Vehicle.DefStarboard, 10),
- aft: parseInt(vehicleData.Vehicle.DefAft, 10),
- },
- armour: {
- value: parseInt(vehicleData.Vehicle.Armor, 10),
- },
- sensorRange: {
- value: sensorRange,
- },
- crew: {},
- passengerCapacity: {
- value: parseInt(vehicleData.Vehicle.Passengers, 10),
- },
- encumbrance: {
- max: parseInt(vehicleData.Vehicle.Encumbrance, 10),
- },
- cost: {
- value: parseInt(vehicleData.Vehicle.Price, 10),
- },
- rarity: {
- value: parseInt(vehicleData.Vehicle.Rarity, 10),
- },
- customizationHardPoints: {
- value: parseInt(vehicleData.Vehicle.HP, 10),
- },
- hyperdrive: {
- value: parseInt(vehicleData.Vehicle.HyperdrivePrimary, 10),
- },
- consumables: {
- value: 1,
- duration: "months",
- },
- },
- },
- items: [],
- };
-
- vehicle.data.biography += ImportHelpers.getSources(vehicleData?.Vehicle?.Sources ?? vehicleData?.Vehicle?.Source);
-
- const funcAddWeapon = async (weapon) => {
- try {
- let weaponData = JSON.parse(JSON.stringify(await ImportHelpers.findCompendiumEntityByImportId("Item", weapon.Key)));
-
- let weapons = 1;
- if (weapon.Count) {
- weapons = weapon.Count;
- }
-
- for (let i = 0; i < weapons; i += 1) {
- if (!weaponData.data.firingarc) {
- weaponData.data.firingarc = {};
- }
- weaponData.data.firingarc.fore = weapon.FiringArcs.Fore === "true" ? true : false;
- weaponData.data.firingarc.aft = weapon.FiringArcs.Aft === "true" ? true : false;
- weaponData.data.firingarc.port = weapon.FiringArcs.Port === "true" ? true : false;
- weaponData.data.firingarc.starboard = weapon.FiringArcs.Starboard === "true" ? true : false;
- weaponData.data.firingarc.dorsal = weapon.FiringArcs.Dorsal === "true" ? true : false;
- weaponData.data.firingarc.ventral = weapon.FiringArcs.Ventral === "true" ? true : false;
-
- if (weapon?.Qualities?.Quality) {
- const qualities = await ImportHelpers.getQualities(weapon.Qualities.Quality);
-
- weaponData.data.special.value = qualities.qualities.join(", ");
- weaponData.data.attributes = qualities.attributes;
- }
-
- vehicle.items.push(weaponData);
- }
- } catch (err) {
- CONFIG.logger.warn(`Unable to locate weapon ${weapon.Key} while import ${vehicle.name}`);
- this._importLogger(`Unable to locate weapon ${weapon.Key} for ${vehicle.name}`);
- }
- };
-
- if (vehicleData.Vehicle.VehicleWeapons?.VehicleWeapon) {
- const temp = new Actor(vehicle, { temporary: true });
-
- if (!Array.isArray(vehicleData.Vehicle.VehicleWeapons.VehicleWeapon)) {
- vehicleData.Vehicle.VehicleWeapons.VehicleWeapon = [vehicleData.Vehicle.VehicleWeapons.VehicleWeapon];
- }
- await this.asyncForEach(vehicleData.Vehicle.VehicleWeapons.VehicleWeapon, async (weapon) => {
- await funcAddWeapon(weapon);
- });
- }
-
- let pack;
- if (isSpace(vehicleData)) {
- pack = packSpace;
- } else {
- pack = packPlanetary;
- }
-
- let compendiumItem;
- let actor;
- await pack.getIndex();
- let entry = pack.index.find((e) => e.name === vehicle.name);
- if (!entry) {
- CONFIG.logger.debug(`Importing Vehicles - Actor`);
- compendiumItem = new Actor(vehicle, { temporary: true });
- this._importLogger(`New Vehicles ${vehicle.name} : ${JSON.stringify(compendiumItem)}`);
- actor = await pack.importEntity(compendiumItem);
- } else {
- CONFIG.logger.debug(`Updating Vehicles - Actor`);
- //let updateData = ImportHelpers.buildUpdateData(vehicle);
- let updateData = vehicle;
- updateData["_id"] = entry._id;
- this._importLogger(`Updating Vehicles ${vehicle.name} : ${JSON.stringify(updateData)}`);
- await pack.updateEntity(updateData);
- actor = await game.actors.get(entry._id);
- }
-
- currentCount += 1;
-
- $(".vehicles .import-progress-bar")
- .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
- .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
- this._importLogger(`End importing Vehicles ${vehicle.name}`);
- } catch (err) {
- this._importLogger(`Error importing record : `, err);
- CONFIG.logger.error(`Error importing record : `, err);
- }
- });
- }
- }
-
- async _getCompendiumPack(type, name) {
- this._importLogger(`Checking for existing compendium pack ${name}`);
- let pack = game.packs.find((p) => {
- return p.metadata.label === name;
- });
- if (!pack) {
- this._importLogger(`Compendium pack ${name} not found, creating new`);
- pack = await Compendium.create({ entity: type, label: name });
- } else {
- this._importLogger(`Existing compendium pack ${name} found`);
- }
-
- return pack;
- }
-
_enableImportSelection(files, name, isDirectory, returnFilename) {
this._importLogger(`Checking zip file for ${name}`);
let fileName;
diff --git a/modules/importer/import-helpers.js b/modules/importer/import-helpers.js
index fc831811..2976a8ef 100644
--- a/modules/importer/import-helpers.js
+++ b/modules/importer/import-helpers.js
@@ -166,7 +166,7 @@ export default class ImportHelpers {
* @param {string} id - Entity Id
* @returns {object} - Entity Object Data
*/
- static async findCompendiumEntityByImportId(type, id, packId) {
+ static async findCompendiumEntityByImportId(type, id, packId, itemType) {
const cachePack = async (packid) => {
if (!CONFIG.temporary[packid]) {
const pack = await game.packs.get(packid);
@@ -176,7 +176,7 @@ export default class ImportHelpers {
const content = await pack.getContent();
for (var i = 0; i < content.length; i++) {
- CONFIG.temporary[packid][content[i].data.flags.ffgimportid] = content[i];
+ CONFIG.temporary[packid][content[i].data.flags.ffgimportid] = duplicate(content[i]);
}
}
} else {
@@ -184,17 +184,13 @@ export default class ImportHelpers {
}
if (CONFIG.temporary?.[packid]?.[id]) {
- return packid;
+ if (!itemType || CONFIG.temporary?.[packid]?.[id]?.type === itemType) {
+ return packid;
+ }
}
return undefined;
};
- // first try finding item by import id in normal items
- const item = this.findEntityByImportId("items", id);
- if (item) {
- return item;
- }
-
let packname;
if (!packId) {
let packs = Array.from(await game.packs.keys());
@@ -285,10 +281,27 @@ export default class ImportHelpers {
modtype = "Skill Add Failure";
value = parseInt(mod.FailureCount, 10);
}
+ if (mod.TriumphCount) {
+ modtype = "Skill Add Triumph";
+ value = parseInt(mod.TriumphCount, 10);
+ }
+ if (mod.DespairCount) {
+ modtype = "Skill Add Despair";
+ value = parseInt(mod.DespairCount, 10);
+ }
} else {
modtype = "Skill Rank";
}
- type = CONFIG.temporary.skills[mod.Key];
+
+ let skill = CONFIG.temporary.skills[mod.Key];
+
+ if (skill.includes(":") && !skill.includes(": ")) {
+ skill = skill.replace(":", ": ");
+ }
+
+ if (Object.keys(CONFIG.FFG.skills).includes(skill)) {
+ type = skill;
+ }
}
if (mod.Key === "ENCTADD") {
@@ -426,6 +439,8 @@ export default class ImportHelpers {
const characterName = characterData.Character.Description.CharName;
+ const exists = game.data.actors.find((actor) => actor.flags.ffgimportid === characterData.Character.Key);
+
let character = {
name: characterName ? characterName : "No Name",
type: "character",
@@ -650,10 +665,29 @@ export default class ImportHelpers {
},
experience: {
total: parseInt(characterData.Character.Experience.ExperienceRanks.StartingRanks ?? 0, 10) + parseInt(characterData.Character.Experience.ExperienceRanks.SpeciesRanks ?? 0, 10) + parseInt(characterData.Character.Experience.ExperienceRanks.PurchasedRanks ?? 0, 10),
- available: parseInt(characterData.Character.Experience.ExperienceRanks.StartingRanks ?? 0, 10) + parseInt(characterData.Character.Experience.ExperienceRanks.SpeciesRanks ?? 0, 10) + parseInt(characterData.Character.Experience.ExperienceRanks.PurchasedRanks ?? 0, 10) - parseInt(characterData.Character.Experience.ExperienceRanks.UsedExperience ?? 0, 10),
+ available: parseInt(characterData.Character.Experience.ExperienceRanks.StartingRanks ?? 0, 10) + parseInt(characterData.Character.Experience.ExperienceRanks.SpeciesRanks ?? 0, 10) + parseInt(characterData.Character.Experience.ExperienceRanks.PurchasedRanks ?? 0, 10) - parseInt(characterData.Character.Experience.UsedExperience ?? 0, 10),
+ },
+ obligationlist: {},
+ dutylist: {},
+ morality: {
+ label: "Morality",
+ strength: characterData.Character.Morality?.MoralityPairs?.MoralityPair?.StrengthKey,
+ type: "Number",
+ value: parseInt(characterData.Character.Morality.MoralityValue ?? 0, 10),
+ weakness: characterData.Character.Morality?.MoralityPairs?.MoralityPair?.WeaknessKey,
+ },
+ biography: characterData.Character.Story,
+ general: {
+ age: characterData.Character.Description.Age,
+ build: characterData.Character.Description.Build,
+ eyes: characterData.Character.Description.Eyes,
+ hair: characterData.Character.Description.Hair,
+ features: characterData.Character.Description.OtherFeatures,
+ height: characterData.Character.Description.Height,
+ gender: characterData.Character.Description.Gender,
},
},
- items: [],
+ items: exists?.items ? exists.items : [],
};
characterData.Character.Characteristics.CharCharacteristic.forEach((char) => {
@@ -727,10 +761,11 @@ export default class ImportHelpers {
updateDialog(10);
try {
- const x = await this.findCompendiumEntityByImportId("Item", characterData.Character.Species.SpeciesKey);
+ const speciesDBItem = await this.findCompendiumEntityByImportId("Item", characterData.Character.Species.SpeciesKey, undefined, "species");
+
+ if (speciesDBItem) {
+ let species = JSON.parse(JSON.stringify(speciesDBItem));
- const species = JSON.parse(JSON.stringify(x));
- if (species) {
for (let i = 0; i < speciesSkills.length; i += 1) {
// first determine if the modifier exists, oggdudes doesn't differentiate between chosen skills (ie human) vs static skill (ie Nautolan)
@@ -742,16 +777,83 @@ export default class ImportHelpers {
}
}
- character.items.push(species);
+ // does the character data already include the species
+ let speciesItem = character.items.find((s) => s.flags.ffgimportid === species.flags.ffgimportid);
+
+ if (speciesItem) {
+ species = mergeObject(species, speciesItem);
+ } else {
+ character.items.push(species);
+ }
}
} catch (err) {
- CONFIG.logger.error(`Unable to add species ${characterData.Character.Species.SpeciesKey} to character.`);
+ CONFIG.logger.error(`Unable to add species ${characterData.Character.Species.SpeciesKey} to character.`, err);
+ }
+
+ let obligationlist = [];
+ if (characterData.Character.Obligations.CharObligation) {
+ let obligation = 0;
+ if (Array.isArray(characterData.Character.Obligations.CharObligation)) {
+ characterData.Character.Obligations.CharObligation.forEach((CharObligation) => {
+ const nk = randomID();
+ const charobligation = {
+ key: nk,
+ type: CharObligation.Name,
+ magnitude: CharObligation.Size,
+ };
+ character.data.obligationlist[charobligation.key] = charobligation;
+ if (parseInt(CharObligation.Size, 10)) {
+ obligation += parseInt(CharObligation.Size, 10);
+ }
+ });
+ } else {
+ const nk = randomID();
+ const charobligation = {
+ key: nk,
+ type: characterData.Character.Obligations.CharObligation.Name,
+ magnitude: characterData.Character.Obligations.CharObligation.Size,
+ };
+ character.data.obligationlist[charobligation.key] = charobligation;
+ if (parseInt(characterData.Character.Obligations.CharObligation.Size, 10)) {
+ obligation += parseInt(characterData.Character.Obligations.CharObligation.Size, 10);
+ }
+ }
+ }
+
+ let dutylist = [];
+ if (characterData.Character.Duties.CharDuty) {
+ let duty = 0;
+ if (Array.isArray(characterData.Character.Duties.CharDuty)) {
+ characterData.Character.Duties.CharDuty.forEach((CharDuty) => {
+ const nk = randomID();
+ const charduty = {
+ key: nk,
+ type: CharDuty.Name,
+ magnitude: CharDuty.Size,
+ };
+ character.data.dutylist[charduty.key] = charduty;
+ if (parseInt(CharDuty.Size, 10)) {
+ duty += parseInt(CharDuty.Size, 10);
+ }
+ });
+ } else {
+ const nk = randomID();
+ const charduty = {
+ key: nk,
+ type: characterData.Character.Duties.CharDuty.Name,
+ magnitude: characterData.Character.Duties.CharDuty.Size,
+ };
+ character.data.dutylist[charduty.key] = charduty;
+ if (parseInt(characterData.Character.Duties.CharDuty.Size, 10)) {
+ duty += parseInt(characterData.Character.Duties.CharDuty.Size, 10);
+ }
+ }
}
updateDialog(20);
try {
- const career = JSON.parse(JSON.stringify(await this.findCompendiumEntityByImportId("Item", characterData.Character.Career.CareerKey)));
+ const career = JSON.parse(JSON.stringify(await this.findCompendiumEntityByImportId("Item", characterData.Character.Career.CareerKey, undefined, "career")));
if (career) {
if (characterData.Character.Career.CareerSkills?.Key) {
characterData.Character.Career.CareerSkills.Key.forEach((key) => {
@@ -774,16 +876,23 @@ export default class ImportHelpers {
}
});
}
- character.items.push(career);
+
+ let careerItem = character.items.find((s) => s.flags.ffgimportid === career.flags.ffgimportid);
+
+ if (careerItem) {
+ careerItem = mergeObject(career, careerItem);
+ } else {
+ character.items.push(career);
+ }
}
} catch (err) {
- CONFIG.logger.error(`Unable to add career ${characterData.Character.Career.CareerKey} to character.`);
+ CONFIG.logger.error(`Unable to add career ${characterData.Character.Career.CareerKey} to character.`, err);
}
updateDialog(30);
try {
- const specialization = JSON.parse(JSON.stringify(await this.findCompendiumEntityByImportId("Item", characterData.Character.Career.StartingSpecKey)));
+ let specialization = JSON.parse(JSON.stringify(await this.findCompendiumEntityByImportId("Item", characterData.Character.Career.StartingSpecKey, undefined, "specialization")));
if (specialization) {
if (characterData.Character.Career.CareerSpecSkills?.Key) {
characterData.Character.Career.CareerSpecSkills.Key.forEach((key) => {
@@ -825,7 +934,7 @@ export default class ImportHelpers {
}
output.islearned = true;
} catch (err) {
- CONFIG.logger.error(`Unable to add specialization ${characterSpecTalent.Key} to character.`);
+ CONFIG.logger.error(`Unable to add specialization ${characterSpecTalent.Key} to character.`, err);
}
return output;
}
@@ -839,7 +948,11 @@ export default class ImportHelpers {
updateDialog(30 + miniValue);
};
- if (Array.isArray(characterData.Character.Specializations.CharSpecialization)) {
+ if (!Array.isArray(characterData.Character.Specializations.CharSpecialization)) {
+ characterData.Character.Specializations.CharSpecialization = [characterData.Character.Specializations.CharSpecialization];
+ }
+
+ if (characterData?.Character?.Specializations?.CharSpecialization?.length) {
specTotal = characterData.Character.Specializations.CharSpecialization.length;
updateDialogSpecialization(specCount, specTotal);
await this.asyncForEach(characterData.Character.Specializations.CharSpecialization, async (spec) => {
@@ -880,10 +993,17 @@ export default class ImportHelpers {
specCount += 1;
updateDialogSpecialization(specCount, specTotal);
- character.items.push(specialization);
+
+ let specializationItem = character.items.find((s) => s.flags.ffgimportid === specialization.flags.ffgimportid);
+
+ if (specializationItem) {
+ specializationItem = mergeObject(specialization, specializationItem);
+ } else {
+ character.items.push(specialization);
+ }
} else {
try {
- const newspec = JSON.parse(JSON.stringify(await this.findCompendiumEntityByImportId("Item", spec.Key)));
+ let newspec = JSON.parse(JSON.stringify(await this.findCompendiumEntityByImportId("Item", spec.Key, undefined, "specialization")));
specTotal += spec.Talents.CharTalent.length;
updateDialogSpecialization(specCount, specTotal);
for (let i = 0; i < spec.Talents.CharTalent.length; i += 1) {
@@ -918,7 +1038,14 @@ export default class ImportHelpers {
specCount += 1;
updateDialogSpecialization(specCount, specTotal);
}
- character.items.push(newspec);
+
+ let specializationItem = character.items.find((s) => s.flags.ffgimportid === newspec.flags.ffgimportid);
+
+ if (specializationItem) {
+ specializationItem = mergeObject(newspec, specializationItem);
+ } else {
+ character.items.push(newspec);
+ }
} catch (err) {
CONFIG.logger.error(`Unable to add specialization ${spec.Key} to character.`);
}
@@ -926,168 +1053,140 @@ export default class ImportHelpers {
updateDialogSpecialization(specCount, specTotal);
}
});
- } else {
- specTotal += characterData.Character.Specializations.CharSpecialization.Talents.CharTalent.length;
- updateDialogSpecialization(specCount, specTotal);
- for (let i = 0; i < characterData.Character.Specializations.CharSpecialization.Talents.CharTalent.length; i += 1) {
- const talent = await funcGetTalent(characterData.Character.Specializations.CharSpecialization.Talents.CharTalent[i], specialization.data.talents[`talent${i}`].itemId);
- if (talent) {
- specialization.data.talents[`talent${i}`] = { ...specialization.data.talents[`talent${i}`], ...talent };
-
- if (characterData.Character.Specializations.CharSpecialization.Talents.CharTalent[i]?.BonusChars?.BonusChar) {
- if (Array.isArray(characterData.Character.Specializations.CharSpecialization.Talents.CharTalent[i]?.BonusChars?.BonusChar)) {
- await this.asyncForEach(characterData.Character.Specializations.CharSpecialization.Talents.CharTalent[i].BonusChars.BonusChar, async (char) => {
- let attrId = Object.keys(specialization.data.talents[`talent${i}`].attributes).length + 1;
-
- specialization.data.talents[`talent${i}`].attributes[`attr${attrId}`] = {
- isCheckbox: false,
- mod: this.convertOGCharacteristic(char.CharKey),
- modtype: "Characteristic",
- value: char.Bonus,
- };
- });
- } else {
- let attrId = Object.keys(specialization.data.talents[`talent${i}`].attributes).length + 1;
-
- specialization.data.talents[`talent${i}`].attributes[`attr${attrId}`] = {
- isCheckbox: false,
- mod: this.convertOGCharacteristic(characterData.Character.Specializations.CharSpecialization.Talents.CharTalent[i].BonusChars.BonusChar.CharKey),
- modtype: "Characteristic",
- value: characterData.Character.Specializations.CharSpecialization.Talents.CharTalent[i].BonusChars.BonusChar.Bonus,
- };
- }
- }
- }
- specCount += 1;
- updateDialogSpecialization(specCount, specTotal);
- }
- specCount += 1;
- updateDialogSpecialization(specCount, specTotal);
- character.items.push(specialization);
}
}
} catch (err) {
- CONFIG.logger.error(`Unable to add specializations to character.`);
+ CONFIG.logger.error(`Unable to add specializations to character.`, err);
}
updateDialog(40);
await this.asyncForEach(forcepowers, async (power) => {
try {
- const force = JSON.parse(JSON.stringify(await this.findCompendiumEntityByImportId("Item", power.Key)));
+ let force = JSON.parse(JSON.stringify(await this.findCompendiumEntityByImportId("Item", power.Key, undefined, "forcepower")));
for (let i = 4; i < power.ForceAbilities.CharForceAbility.length; i += 1) {
if (power.ForceAbilities.CharForceAbility[i].Purchased) {
force.data.upgrades[`upgrade${i - 4}`].islearned = true;
}
}
- character.items.push(force);
+ let forceItem = character.items.find((s) => s.flags.ffgimportid === force.flags.ffgimportid);
+
+ if (forceItem) {
+ forceItem = mergeObject(force, forceItem);
+ } else {
+ character.items.push(force);
+ }
} catch (err) {
- CONFIG.logger.error(`Unable to add force power ${forcepowers.Key} to character.`);
+ CONFIG.logger.error(`Unable to add force power ${forcepowers.Key} to character.`, err);
}
});
updateDialog(50);
if (characterData.Character?.Weapons?.CharWeapon) {
- if (Array.isArray(characterData.Character.Weapons.CharWeapon)) {
- await this.asyncForEach(characterData.Character.Weapons.CharWeapon, async (w) => {
- try {
- const weapon = JSON.parse(JSON.stringify(await this.findCompendiumEntityByImportId("Item", w.ItemKey)));
- if (w?.Count) {
- weapon.data.quantity = {
- value: parseInt(w.Count, 10),
- };
+ if (!Array.isArray(characterData.Character.Weapons.CharWeapon)) {
+ characterData.Character.Weapons.CharWeapon = [characterData.Character.Weapons.CharWeapon];
+ }
+ await this.asyncForEach(characterData.Character.Weapons.CharWeapon, async (w) => {
+ try {
+ const weapon = JSON.parse(JSON.stringify(await this.findCompendiumEntityByImportId("Item", w.ItemKey, undefined, "weapon")));
+ delete weapon._id;
+
+ const weaponItems = character.items.filter((s) => s.flags.ffgimportid === weapon.flags.ffgimportid);
+
+ if (weaponItems.length > 0) {
+ for (let i = 0; i < character.items.length; i += 1) {
+ if (character.items[i].type === "weapon" && character.items[i].flags.ffgimportid === weapon.flags.ffgimportid) {
+ character.items[i] = mergeObject(weapon, character.items[i]);
+ }
}
- character.items.push(weapon);
- } catch (err) {
- if (w.ItemKey?.length) {
- CONFIG.logger.error(`Unable to add weapon (${w.ItemKey}) to character.`, err);
+ } else {
+ if (w?.Count) {
+ w.Count = parseInt(w.Count, 10);
+ } else {
+ w.Count = 1;
}
+
+ await this.asyncForEach(new Array(parseInt(w.Count, 10)), () => {
+ character.items.push(weapon);
+ });
}
- });
- } else {
- try {
- const weapon = JSON.parse(JSON.stringify(await this.findCompendiumEntityByImportId("Item", characterData.Character.Weapons.CharWeapon.ItemKey)));
- if (characterData.Character.Weapons.CharWeapon?.Count) {
- weapon.data.quantity = {
- value: parseInt(characterData.Character.Weapons.CharWeapon.Count, 10),
- };
- }
- character.items.push(weapon);
} catch (err) {
- if (characterData.Character.Weapons.CharWeapon?.ItemKey?.length) {
- CONFIG.logger.warn(`Unable to add weapon (${characterData.Character.Weapons.CharWeapon.ItemKey}) to character.`, err);
+ if (w.ItemKey?.length) {
+ CONFIG.logger.error(`Unable to add weapon (${w.ItemKey}) to character.`, err);
}
}
- }
+ });
}
updateDialog(60);
if (characterData.Character?.Armor?.CharArmor) {
- if (Array.isArray(characterData.Character.Armor.CharArmor)) {
- await this.asyncForEach(characterData.Character.Armor.CharArmor, async (w) => {
- try {
- const armor = JSON.parse(JSON.stringify(await this.findCompendiumEntityByImportId("Item", w.ItemKey)));
+ if (!Array.isArray(characterData.Character.Armor.CharArmor)) {
+ characterData.Character.Armor.CharArmor = [characterData.Character.Armor.CharArmor];
+ }
+
+ await this.asyncForEach(characterData.Character.Armor.CharArmor, async (w) => {
+ try {
+ const armor = JSON.parse(JSON.stringify(await this.findCompendiumEntityByImportId("Item", w.ItemKey, undefined, "armour")));
+ delete armor._id;
+ const armorItems = character.items.filter((s) => s.flags.ffgimportid === armor.flags.ffgimportid);
+
+ if (armorItems.length > 0) {
+ for (let i = 0; i < character.items.length; i += 1) {
+ if (character.items[i].type === "armor" && character.items[i].flags.ffgimportid === armor.flags.ffgimportid) {
+ character.items[i] = mergeObject(armor, character.items[i]);
+ }
+ }
+ } else {
if (w?.Count) {
- armor.data.quantity = {
- value: parseInt(w.Count, 10),
- };
+ w.Count = parseInt(w.Count, 10);
+ } else {
+ w.Count = 1;
}
- character.items.push(armor);
- } catch (err) {
- CONFIG.logger.error(`Unable to add armor (${w.ItemKey}) to character.`, err);
- }
- });
- } else {
- try {
- const armor = JSON.parse(JSON.stringify(await this.findCompendiumEntityByImportId("Item", characterData.Character.Armor.CharArmor.ItemKey)));
- if (characterData.Character.Armor.CharArmor?.Count) {
- armor.data.quantity = {
- value: parseInt(characterData.Character.Armor.CharArmor.Count, 10),
- };
+ await this.asyncForEach(new Array(parseInt(w.Count, 10)), () => {
+ character.items.push(armor);
+ });
}
- character.items.push(armor);
} catch (err) {
- CONFIG.logger.error(`Unable to add armor (${characterData.Character.Armor.CharArmor.ItemKey}) to character.`, err);
+ CONFIG.logger.error(`Unable to add armor (${w.ItemKey}) to character.`, err);
}
- }
+ });
}
updateDialog(70);
if (characterData.Character?.Gear?.CharGear) {
- if (Array.isArray(characterData.Character.Gear.CharGear)) {
- await this.asyncForEach(characterData.Character.Gear.CharGear, async (w) => {
- try {
- const gear = JSON.parse(JSON.stringify(await this.findCompendiumEntityByImportId("Item", w.ItemKey)));
-
- if (w?.Count) {
- gear.data.quantity = {
- value: parseInt(w.Count, 10),
- };
- }
- character.items.push(gear);
- } catch (err) {
- CONFIG.logger.error(`Unable to add gear (${w.ItemKey}) to character.`, err);
- }
- });
- } else {
+ if (!Array.isArray(characterData.Character.Gear.CharGear)) {
+ characterData.Character.Gear.CharGear = [characterData.Character.Gear.CharGear];
+ }
+ await this.asyncForEach(characterData.Character.Gear.CharGear, async (w) => {
try {
- const gear = JSON.parse(JSON.stringify(await this.findCompendiumEntityByImportId("Item", characterData.Character.Gear.CharGear.ItemKey)));
+ const gear = JSON.parse(JSON.stringify(await this.findCompendiumEntityByImportId("Item", w.ItemKey, undefined, "gear")));
+ delete gear._id;
- if (characterData.Character.Gear.CharGear?.Count) {
+ let gearItem = character.items.find((s) => s.flags.ffgimportid === gear.flags.ffgimportid);
+
+ let gearCount = 1;
+ if (w?.Count) {
+ gearCount = parseInt(w.Count, 10);
gear.data.quantity = {
- value: parseInt(characterData.Character.Gear.CharGear.Count, 10),
+ value: gearCount,
};
}
- character.items.push(gear);
+
+ if (gearItem) {
+ gearItem = mergeObject(gear, gearItem);
+ gear.data.quantity.value = gearCount;
+ } else {
+ character.items.push(gear);
+ }
} catch (err) {
- CONFIG.logger.error(`Unable to add gear (${characterData.Character.Gear.CharGear.ItemKey}) to character.`, err);
+ CONFIG.logger.error(`Unable to add gear (${w.ItemKey}) to character.`, err);
}
- }
+ });
}
updateDialog(80);
@@ -1109,7 +1208,6 @@ export default class ImportHelpers {
updateDialog(90);
- const exists = game.data.actors.find((actor) => actor.flags.ffgimportid === characterData.Character.Key);
if (exists) {
//let updateData = ImportHelpers.buildUpdateData(character);
let updateData = character;
@@ -1225,8 +1323,450 @@ export default class ImportHelpers {
}
}
- const sourceText = `[P][H3]Sources:[h3]${sourceArray.map((s) => `[H4]Page ${s.$Page} - ${s._}[h4]`).join("")}`;
+ const text = sourceArray.map((s) => {
+ if (s?.$Page) {
+ return `[H4]Page ${s.$Page} - ${s._}[h4]`;
+ } else {
+ return `[H4]${s}[h4]`;
+ }
+ });
+
+ const sourceText = `[P][H3]Sources:[h3]${text.join("")}`;
return sourceText;
}
+
+ static prepareBaseObject(obj, type) {
+ return {
+ name: obj.Name,
+ type,
+ flags: {
+ ffgimportid: obj.Key,
+ },
+ data: {},
+ };
+ }
+
+ static async addImportItemToCompendium(type, data, pack, removeFirst) {
+ let entry = await ImportHelpers.findCompendiumEntityByImportId(type, data.flags.ffgimportid, pack.collection);
+ let objClass;
+ let dataType;
+ switch (type) {
+ case "Item": {
+ objClass = Item;
+ dataType = data.type;
+ break;
+ }
+ case "JournalEntry": {
+ objClass = JournalEntry;
+ if (!data.img) {
+ data.img = `icons/sundries/scrolls/scroll-rolled-white.webp`;
+ }
+ dataType = type;
+ break;
+ }
+ case "Actor": {
+ objClass = Actor;
+ dataType = data.type;
+ break;
+ }
+ }
+
+ if (!entry) {
+ let compendiumItem;
+ CONFIG.logger.debug(`Importing ${type} ${dataType} ${data.name}`);
+ compendiumItem = new objClass(data, { temporary: true });
+ CONFIG.logger.debug(`New ${type} ${dataType} ${data.name} : ${JSON.stringify(compendiumItem)}`);
+ const crt = await pack.importEntity(compendiumItem);
+ CONFIG.temporary[pack.collection][data.flags.ffgimportid] = duplicate(crt);
+ } else {
+ let upd;
+ if (removeFirst) {
+ await pack.deleteEntity(entry._id);
+ let compendiumItem;
+ CONFIG.logger.debug(`Importing ${type} ${dataType} ${data.name}`);
+ compendiumItem = new objClass(data, { temporary: true });
+ CONFIG.logger.debug(`New ${type} ${dataType} ${data.name} : ${JSON.stringify(compendiumItem)}`);
+ upd = await pack.importEntity(compendiumItem);
+ } else {
+ CONFIG.logger.debug(`Updating ${type} ${dataType} ${data.name}`);
+
+ let updateData = data;
+ updateData["_id"] = entry._id;
+
+ if (updateData?.data?.attributes) {
+ // Remove and repopulate all modifiers
+ if (entry.data?.attributes) {
+ for (let k of Object.keys(entry.data.attributes)) {
+ if (!updateData.data.attributes.hasOwnProperty(k)) updateData.data.attributes[`-=${k}`] = null;
+ }
+ }
+ }
+
+ CONFIG.logger.debug(`Updating ${type} ${dataType} ${data.name} : ${JSON.stringify(updateData)}`);
+ await pack.updateEntity(updateData);
+ upd = duplicate(entry);
+ if (upd.data) {
+ upd.data = mergeObject(upd.data, data.data);
+ }
+ }
+ CONFIG.temporary[pack.collection][data.flags.ffgimportid] = upd;
+ }
+ }
+
+ static async getCompendiumPack(type, name) {
+ CONFIG.logger.debug(`Checking for existing compendium pack ${name}`);
+ let pack = game.packs.find((p) => {
+ return p.metadata.label === name;
+ });
+ if (!pack) {
+ CONFIG.logger.debug(`Compendium pack ${name} not found, creating new`);
+ pack = await Compendium.create({ entity: type, label: name });
+ } else {
+ CONFIG.logger.debug(`Existing compendium pack ${name} found`);
+ }
+
+ return pack;
+ }
+
+ static processCharacteristicMod(mod) {
+ const modtype = "Characteristic";
+ const type = ImportHelpers.convertOGCharacteristic(mod.Key);
+
+ return {
+ type,
+ value: {
+ mod: type,
+ modtype,
+ value: mod?.Count ? parseInt(mod.Count, 10) : 0,
+ },
+ };
+ }
+
+ static processSkillMod(mod, isDescriptor) {
+ let type;
+ let modtype;
+ let value = mod?.Count ? parseInt(mod.Count, 10) : 0;
+
+ if (isDescriptor) {
+ // handle description modifiers ** Experimental **
+ Object.keys(mod).forEach((m) => {
+ value = parseInt(mod[m], 10);
+ switch (m) {
+ case "BoostCount": {
+ modtype = "Roll Modifiers";
+ type = "Add Boost";
+ }
+ }
+ });
+ } else {
+ if (mod.SkillIsCareer) {
+ modtype = "Career Skill";
+ } else if (mod.BoostCount || mod.SetbackCount || mod.AddSetbackCount || mod.ForceCount || mod.AdvantageCount || mod.ThreatCount || mod.SuccessCount || mod.FailureCount) {
+ modtype = "Skill Boost";
+
+ if (mod.AddSetbackCount) {
+ modtype = "Skill Setback";
+ value = parseInt(mod.AddSetbackCount, 10);
+ }
+ if (mod.SetbackCount) {
+ modtype = "Skill Remove Setback";
+ value = parseInt(mod.SetbackCount, 10);
+ }
+ if (mod.BoostCount) {
+ value = parseInt(mod.BoostCount, 10);
+ }
+ if (mod.AdvantageCount) {
+ modtype = "Skill Add Advantage";
+ value = parseInt(mod.AdvantageCount, 10);
+ }
+ if (mod.ThreatCount) {
+ modtype = "Skill Add Threat";
+ value = parseInt(mod.ThreatCount, 10);
+ }
+ if (mod.SuccessCount) {
+ modtype = "Skill Add Success";
+ value = parseInt(mod.SuccessCount, 10);
+ }
+ if (mod.FailureCount) {
+ modtype = "Skill Add Failure";
+ value = parseInt(mod.FailureCount, 10);
+ }
+ } else {
+ modtype = "Skill Rank";
+ }
+ if (mod.Key) {
+ let skill = CONFIG.temporary.skills[mod.Key];
+
+ if (skill.includes(":") && !skill.includes(": ")) {
+ skill = skill.replace(":", ": ");
+ }
+
+ if (Object.keys(CONFIG.FFG.skills).includes(skill)) {
+ type = CONFIG.temporary.skills[mod.Key];
+ }
+ } else if (mod?.Skill) {
+ type = mod.Skill;
+ }
+ }
+ if (type) {
+ return { type, value: { mod: type, modtype, value } };
+ }
+ }
+
+ static async processDieMod(mod) {
+ if (!Array.isArray(mod.DieModifier)) {
+ mod.DieModifier = [mod.DieModifier];
+ }
+
+ let output = {
+ attributes: {},
+ };
+
+ await ImportHelpers.asyncForEach(mod.DieModifier, async (dieMod) => {
+ if (!dieMod) {
+ return;
+ } else if (dieMod.SkillKey) {
+ // this is a skill modifier
+ const skillModifier = ImportHelpers.processSkillMod({ Key: dieMod.SkillKey, ...dieMod });
+ output.attributes[skillModifier.type] = skillModifier.value;
+ } else if (dieMod.SkillChar) {
+ // this is a skill modifier based on characteristic (ex all Brawn skills);
+ const skillTheme = await game.settings.get("starwarsffg", "skilltheme");
+ const allSkillsLists = JSON.parse(await game.settings.get("starwarsffg", "arraySkillList"));
+ const skills = allSkillsLists.find((i) => i.id === skillTheme).skills;
+ const characteristicSkills = Object.keys(skills).filter((s) => skills[s].characteristic === ImportHelpers.convertOGCharacteristic(dieMod.SkillChar));
+
+ characteristicSkills.forEach((cs) => {
+ const skillModifier = ImportHelpers.processSkillMod({ Skill: cs, ...dieMod });
+
+ if (output.attributes[skillModifier.type]) {
+ output.attributes[skillModifier.type].value += skillModifier.value.value;
+ } else {
+ output.attributes[skillModifier.type] = skillModifier.value;
+ }
+ });
+ } else if (dieMod.SkillType) {
+ const skillTheme = await game.settings.get("starwarsffg", "skilltheme");
+ const allSkillsLists = JSON.parse(await game.settings.get("starwarsffg", "arraySkillList"));
+ const skills = allSkillsLists.find((i) => i.id === skillTheme).skills;
+ const characteristicSkills = Object.keys(skills).filter((s) => skills[s].type.toLowerCase() === dieMod.SkillType.toLowerCase());
+
+ characteristicSkills.forEach((cs) => {
+ const skillModifier = ImportHelpers.processSkillMod({ Skill: cs, ...dieMod });
+
+ if (output.attributes[skillModifier.type]) {
+ output.attributes[skillModifier.type].value += skillModifier.value.value;
+ } else {
+ output.attributes[skillModifier.type] = skillModifier.value;
+ }
+ });
+ } else {
+ const skillModifier = ImportHelpers.processSkillMod({ Key: dieMod.SkillKey, ...dieMod }, true);
+ output.attributes[skillModifier.type] = skillModifier.value;
+ }
+ });
+
+ return output;
+ }
+
+ static async processModsData(modifiersData) {
+ let output = {
+ attributes: {},
+ description: "",
+ itemattachment: [],
+ itemmodifier: [],
+ };
+
+ if (modifiersData?.Mod || modifiersData?.Quality) {
+ let mods;
+ if (modifiersData?.Mod) {
+ if (!Array.isArray(modifiersData.Mod)) {
+ mods = [modifiersData.Mod];
+ } else {
+ mods = modifiersData.Mod;
+ }
+ } else {
+ if (!Array.isArray(modifiersData.Quality)) {
+ mods = [modifiersData.Quality];
+ } else {
+ mods = modifiersData.Quality;
+ }
+ }
+ await this.asyncForEach(mods, async (modifier) => {
+ if (modifier.Key) {
+ // this is a characteristic or stat or skill or quality modifier.
+ if (["BR", "AG", "INT", "CUN", "WIL", "PR"].includes(modifier.Key)) {
+ // this is a characteristic modifier
+ const attribute = ImportHelpers.processCharacteristicMod(modifier);
+
+ output.attributes[attribute.type] = attribute.value;
+ } else {
+ const compendiumEntry = await ImportHelpers.findCompendiumEntityByImportId("Item", modifier.Key);
+ if (compendiumEntry) {
+ if (compendiumEntry?.type === "itemmodifier") {
+ const descriptor = duplicate(compendiumEntry);
+ descriptor._id = randomID();
+ descriptor.data.rank = modifier?.Count ? parseInt(modifier.Count, 10) : 1;
+ output.itemmodifier.push(descriptor);
+ let rank = "";
+ if (descriptor.data.rank > 1) {
+ rank = `${game.i18n.localize("SWFFG.Count")} ${descriptor.data.rank}`;
+ }
+ output.description += `${descriptor.name} - ${descriptor.data.description} ${rank}
`;
+ }
+ } else if (Object.keys(CONFIG.temporary.skills).includes(modifier.Key)) {
+ // this is a skill upgrade
+ const skillModifier = ImportHelpers.processSkillMod(modifier);
+ if (skillModifier) {
+ output.attributes[skillModifier.type] = skillModifier.value;
+ }
+ } else {
+ CONFIG.logger.warn(`${modifier.Key} not found`);
+ }
+ }
+ } else if (modifier.DieModifiers) {
+ // this is a die modifier
+ const dieModifiers = await ImportHelpers.processDieMod(modifier.DieModifiers);
+ output.attributes = mergeObject(output.attributes, dieModifiers.attributes);
+ } else {
+ // this is just a text modifier
+ const unique = {
+ name: "Unique Mod",
+ type: "itemmodifier",
+ data: {
+ description: modifier.MiscDesc,
+ attributes: {},
+ type: "all",
+ rank: modifier?.Count ? parseInt(modifier.Count, 10) : 1,
+ },
+ };
+ const descriptor = new Item(unique, { temporary: true });
+ descriptor.data._id = randomID();
+ let rank = "";
+ if (unique.data.rank > 1) {
+ rank = `${game.i18n.localize("SWFFG.Count")} ${unique.data.rank}`;
+ }
+
+ output.description += `${unique.data.description} ${rank}
`;
+ output.itemmodifier.push(descriptor.data);
+ }
+ });
+ }
+ return output;
+ }
+
+ static async processMods(obj) {
+ let output = {};
+
+ if (obj?.BaseMods?.Mod) {
+ output.baseMods = await ImportHelpers.processModsData(obj.BaseMods);
+ }
+
+ if (obj?.AddedMods?.Mod) {
+ output.addedMods = await ImportHelpers.processModsData(obj.AddedMods);
+ }
+
+ if (obj?.Qualities?.Quality) {
+ output.qualities = await ImportHelpers.processModsData(obj.Qualities);
+ }
+
+ return output;
+ }
+
+ static processStatMod(mod) {
+ let attributes = {};
+ if (mod) {
+ Object.keys(mod).forEach((m) => {
+ const value = parseInt(mod[m], 10);
+ const modtype = "Stat";
+ let type;
+ switch (m) {
+ case "SoakValue": {
+ type = "Soak";
+ break;
+ }
+ case "ForceRating": {
+ type = "ForcePool";
+ break;
+ }
+ case "StrainThreshold": {
+ type = "Strain";
+ break;
+ }
+ case "DefenseRanged": {
+ type = "Defence-Ranged";
+ break;
+ }
+ case "DefenseMelee": {
+ type = "Defence-Melee";
+ break;
+ }
+ case "WoundThreshold": {
+ type = "Wounds";
+ break;
+ }
+ }
+
+ if (type) {
+ attributes[randomID()] = { mod: type, modtype, value };
+ }
+ });
+ }
+
+ return attributes;
+ }
+
+ static processCareerSkills(skills, includeRank) {
+ let attributes = {};
+ if (skills?.Key) {
+ if (!Array.isArray(skills.Key)) {
+ skills.Key = [skills.Key];
+ }
+
+ skills.Key.forEach((skill) => {
+ let mod = CONFIG.temporary.skills[skill];
+ if (mod) {
+ if (mod.includes(":") && !mod.includes(": ")) {
+ mod = mod.replace(":", ": ");
+ }
+
+ if (Object.keys(CONFIG.FFG.skills).includes(mod)) {
+ if (mod) {
+ const modtype = "Career Skill";
+ attributes[randomID()] = { mod, modtype, value: true };
+
+ if (includeRank) {
+ attributes[randomID()] = { mod, modtype: "Skill Rank", value: 0 };
+ }
+ } else {
+ CONFIG.logger.warn(`Skill ${skill} was not found in the current skills list.`);
+ }
+ }
+ } else {
+ CONFIG.logger.warn(`Skill ${skill} was not found in the current skills list.`);
+ }
+ });
+ }
+
+ return attributes;
+ }
+
+ static async getTemplate(type) {
+ const response = await fetch("systems/starwarsffg/template.json");
+ const template = await response.json();
+
+ const obj = Object.values(template).find((i) => i.types.includes(type));
+
+ let item = obj[type];
+
+ if (item.templates) {
+ item.templates.forEach((i) => {
+ item = mergeObject(item, obj.templates[i]);
+ });
+ delete item.templates;
+ }
+
+ return item;
+ }
}
diff --git a/modules/importer/oggdude/importers/armor.js b/modules/importer/oggdude/importers/armor.js
new file mode 100644
index 00000000..ea9d2335
--- /dev/null
+++ b/modules/importer/oggdude/importers/armor.js
@@ -0,0 +1,79 @@
+import ImportHelpers from "../../import-helpers.js";
+
+export default class Armor {
+ static async Import(xml, zip) {
+ try {
+ const base = JXON.xmlToJs(xml);
+ let items = base?.Armors?.Armor;
+ if (items?.length) {
+ let totalCount = items.length;
+ let currentCount = 0;
+ let pack = await ImportHelpers.getCompendiumPack("Item", `oggdude.Armor`);
+ CONFIG.logger.debug(`Starting Oggdude Armor Import`);
+ $(".import-progress.armor").toggleClass("import-hidden");
+
+ await ImportHelpers.asyncForEach(items, async (item) => {
+ try {
+ let data = ImportHelpers.prepareBaseObject(item, "armour");
+
+ data.data = {
+ attributes: {},
+ description: item.Description,
+ encumbrance: {
+ value: item.Encumbrance ? parseInt(item.Encumbrance, 10) : 0,
+ },
+ price: {
+ value: item.Price ? parseInt(item.Price, 10) : 0,
+ },
+ rarity: {
+ value: item.Rarity ? parseInt(item.Rarity, 10) : 0,
+ isrestricted: item.Restricted === "true" ? true : false,
+ },
+ defence: {
+ value: item.Defense ? parseInt(item.Defense, 10) : 0,
+ },
+ soak: {
+ value: item.Soak ? parseInt(item.Soak, 10) : 0,
+ },
+ hardpoints: {
+ value: item.HP ? parseInt(item.HP, 10) : 0,
+ },
+ itemmodifier: [],
+ itemattachment: [],
+ };
+
+ data.data.description += ImportHelpers.getSources(item?.Sources ?? item?.Source);
+ const mods = await ImportHelpers.processMods(item);
+ if (mods) {
+ if (mods.baseMods) {
+ data.data.attributes = mods.baseMods.attributes;
+ data.data.itemmodifier = data.data.itemmodifier.concat(mods.baseMods.itemmodifier);
+ data.data.itemattachment = mods.baseMods.itemattachment;
+ data.data.description += mods.baseMods.description;
+ }
+ }
+
+ // does an image exist?
+ let imgPath = await ImportHelpers.getImageFilename(zip, "Equipment", "Armor", data.flags.ffgimportid);
+ if (imgPath) {
+ data.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
+ }
+
+ await ImportHelpers.addImportItemToCompendium("Item", data, pack);
+
+ currentCount += 1;
+
+ $(".armor .import-progress-bar")
+ .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
+ .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ });
+ }
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ CONFIG.logger.debug(`Completed Oggdude Armor Import`);
+ }
+}
diff --git a/modules/importer/oggdude/importers/careers.js b/modules/importer/oggdude/importers/careers.js
new file mode 100644
index 00000000..4e189cff
--- /dev/null
+++ b/modules/importer/oggdude/importers/careers.js
@@ -0,0 +1,86 @@
+import ImportHelpers from "../../import-helpers.js";
+
+export default class Career {
+ static async Import(zip) {
+ try {
+ const files = Object.values(zip.files).filter((file) => {
+ return !file.dir && file.name.split(".").pop() === "xml" && file.name.includes("/Careers/");
+ });
+ let totalCount = files.length;
+ let currentCount = 0;
+
+ if (files.length) {
+ let pack = await ImportHelpers.getCompendiumPack("Item", `oggdude.Careers`);
+ CONFIG.logger.debug(`Starting Oggdude Careers Import`);
+ $(".import-progress.careers").toggleClass("import-hidden");
+
+ await ImportHelpers.asyncForEach(files, async (file) => {
+ try {
+ const zipData = await zip.file(file.name).async("text");
+ const xmlData = ImportHelpers.stringToXml(zipData);
+ const careerData = JXON.xmlToJs(xmlData);
+ const item = careerData.Career;
+
+ let data = ImportHelpers.prepareBaseObject(item, "career");
+
+ data.data = {
+ attributes: {},
+ description: item.Description,
+ };
+
+ data.data.description += ImportHelpers.getSources(item.Sources ?? item.Source);
+
+ // process career skills
+ item.CareerSkills.Key.forEach((skillKey) => {
+ let skill = CONFIG.temporary.skills[skillKey];
+ if (skill) {
+ data.data.attributes[`attr${randomID()}`] = {
+ mod: skill,
+ modtype: "Career Skill",
+ value: true,
+ };
+ data.data.attributes[`attr${randomID()}`] = {
+ mod: skill,
+ modtype: "Skill Rank",
+ value: 0,
+ };
+ }
+ });
+
+ // process career attributes
+ if (item?.Attributes) {
+ Object.keys(item.Attributes).forEach((attr) => {
+ switch (attr) {
+ case "ForceRating": {
+ data.data.attributes[`attr${randomID()}`] = {
+ mod: "ForcePool",
+ modtype: "Stat",
+ value: item.Attributes[attr],
+ };
+ break;
+ }
+ }
+ });
+ }
+
+ let imgPath = await ImportHelpers.getImageFilename(zip, "Career", "", data.flags.ffgimportid);
+ if (imgPath) {
+ data.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
+ }
+
+ await ImportHelpers.addImportItemToCompendium("Item", data, pack);
+ currentCount += 1;
+
+ $(".careers .import-progress-bar")
+ .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
+ .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ });
+ }
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ }
+}
diff --git a/modules/importer/oggdude/importers/forcepowers.js b/modules/importer/oggdude/importers/forcepowers.js
new file mode 100644
index 00000000..815336df
--- /dev/null
+++ b/modules/importer/oggdude/importers/forcepowers.js
@@ -0,0 +1,158 @@
+import ImportHelpers from "../../import-helpers.js";
+
+export default class ForcePowers {
+ static async Import(xml, zip) {
+ try {
+ const base = JXON.xmlToJs(xml);
+ const abilities = base?.ForceAbilities?.ForceAbility;
+
+ if (abilities?.length) {
+ const files = Object.values(zip.files).filter((file) => {
+ return !file.dir && file.name.split(".").pop() === "xml" && file.name.includes("/Force Powers/");
+ });
+ let totalCount = files.length;
+ let currentCount = 0;
+
+ if (files.length) {
+ let pack = await ImportHelpers.getCompendiumPack("Item", `oggdude.ForcePowers`);
+ CONFIG.logger.debug(`Starting Oggdude Force Powers Import`);
+ $(".import-progress.force").toggleClass("import-hidden");
+
+ await ImportHelpers.asyncForEach(files, async (file) => {
+ try {
+ const zipData = await zip.file(file.name).async("text");
+ const xmlData = ImportHelpers.stringToXml(zipData);
+ const forceData = JXON.xmlToJs(xmlData);
+ const item = forceData.ForcePower;
+
+ //
+ const basePowerKey = item.AbilityRows.AbilityRow[0].Abilities.Key[0];
+ let basepower = abilities.find((ability) => {
+ return ability.Key === basePowerKey;
+ });
+
+ let data = ImportHelpers.prepareBaseObject(item, "forcepower");
+ data.data = {
+ attributes: {},
+ description: basepower.Description,
+ upgrades: {},
+ };
+
+ data.data.description += ImportHelpers.getSources(item?.Sources ?? item?.Source);
+ if (item?.DieModifiers) {
+ const dieModifiers = await ImportHelpers.processDieMod(item.DieModifiers);
+ data.data.attributes = mergeObject(data.data.attributes, dieModifiers.attributes);
+ }
+
+ // process all ability rows
+ for (let i = 1; i < item.AbilityRows.AbilityRow.length; i += 1) {
+ try {
+ const row = item.AbilityRows.AbilityRow[i];
+ await ImportHelpers.asyncForEach(row.Abilities.Key, async (keyName, index) => {
+ try {
+ let rowAbility = {};
+
+ let rowAbilityData = abilities.find((a) => {
+ return a.Key === keyName;
+ });
+
+ rowAbility.name = rowAbilityData.Name;
+ rowAbility.description = rowAbilityData.Description;
+ rowAbility.cost = row.Costs.Cost[index];
+ rowAbility.visible = true;
+ rowAbility.attributes = {};
+
+ if (row.Directions.Direction[index].Up) {
+ rowAbility["links-top-1"] = true;
+ }
+
+ switch (row.AbilitySpan.Span[index]) {
+ case "1":
+ rowAbility.size = "single";
+ break;
+ case "2":
+ rowAbility.size = "double";
+ if (index < 3 && row.Directions.Direction[index + 1].Up) {
+ rowAbility["links-top-2"] = true;
+ }
+ break;
+ case "3":
+ rowAbility.size = "triple";
+ if (index < 2 && row.Directions.Direction[index + 1].Up) {
+ rowAbility["links-top-2"] = true;
+ }
+ if (index < 2 && row.Directions.Direction[index + 2].Up) {
+ rowAbility["links-top-3"] = true;
+ }
+ break;
+ case "4":
+ rowAbility.size = "full";
+ if (index < 1 && row.Directions.Direction[index + 1].Up) {
+ rowAbility["links-top-2"] = true;
+ }
+ if (index < 1 && row.Directions.Direction[index + 2].Up) {
+ rowAbility["links-top-3"] = true;
+ }
+ if (index < 1 && row.Directions.Direction[index + 3].Up) {
+ rowAbility["links-top-4"] = true;
+ }
+ break;
+ default:
+ rowAbility.size = "single";
+ rowAbility.visible = false;
+ }
+
+ if (row.Directions.Direction[index].Right) {
+ rowAbility["links-right"] = true;
+ }
+
+ if (rowAbilityData?.DieModifiers) {
+ const dieModifiers = await ImportHelpers.processDieMod(rowAbilityData.DieModifiers);
+ rowAbility.attributes = mergeObject(rowAbility.attributes, dieModifiers.attributes);
+ }
+
+ const talentKey = `upgrade${(i - 1) * 4 + index}`;
+ data.data.upgrades[talentKey] = rowAbility;
+ } catch (err) {
+ CONFIG.logger.error(`Error import force ability ${keyName} : `, err);
+ }
+ });
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ }
+
+ // if there are less that 5 rows (include base power) row, hide excess rows
+ if (item.AbilityRows.AbilityRow.length < 5) {
+ for (let i = item.AbilityRows.AbilityRow.length; i < 5; i += 1) {
+ for (let index = 0; index < 4; index += 1) {
+ const talentKey = `upgrade${(i - 1) * 4 + index}`;
+ let rowAbility = { visible: false };
+ data.data.upgrades[talentKey] = rowAbility;
+ }
+ }
+ }
+
+ let imgPath = await ImportHelpers.getImageFilename(zip, "ForcePowers", "", data.flags.ffgimportid);
+ if (imgPath) {
+ data.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
+ }
+
+ await ImportHelpers.addImportItemToCompendium("Item", data, pack);
+
+ currentCount += 1;
+
+ $(".force .import-progress-bar")
+ .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
+ .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ });
+ }
+ }
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ }
+}
diff --git a/modules/importer/oggdude/importers/gear.js b/modules/importer/oggdude/importers/gear.js
new file mode 100644
index 00000000..54fe33d9
--- /dev/null
+++ b/modules/importer/oggdude/importers/gear.js
@@ -0,0 +1,67 @@
+import ImportHelpers from "../../import-helpers.js";
+
+export default class Gear {
+ static async Import(xml, zip) {
+ try {
+ const base = JXON.xmlToJs(xml);
+ let items = base?.Gears?.Gear;
+ if (items?.length) {
+ let totalCount = items.length;
+ let currentCount = 0;
+ let pack = await ImportHelpers.getCompendiumPack("Item", `oggdude.Gear`);
+ CONFIG.logger.debug(`Starting Oggdude Gear Import`);
+ $(".import-progress.gear").toggleClass("import-hidden");
+
+ await ImportHelpers.asyncForEach(items, async (item) => {
+ try {
+ let data = ImportHelpers.prepareBaseObject(item, "gear");
+ data.data = {
+ attributes: {},
+ description: item.Description,
+ encumbrance: {
+ value: item.Encumbrance ? parseInt(item.Encumbrance, 10) : 0,
+ },
+ price: {
+ value: item.Price ? parseInt(item.Price, 10) : 0,
+ },
+ rarity: {
+ value: item.Rarity ? parseInt(item.Rarity, 10) : 0,
+ isrestricted: item.Restricted === "true" ? true : false,
+ },
+ itemmodifier: [],
+ itemattachment: [],
+ };
+
+ data.data.description += ImportHelpers.getSources(item?.Sources ?? item?.Source);
+ const mods = await ImportHelpers.processMods(item);
+ if (mods) {
+ if (mods.baseMods) {
+ data.data.attributes = mods.baseMods.attributes;
+ data.data.itemmodifier = data.data.itemmodifier.concat(mods.baseMods.itemmodifier);
+ data.data.itemattachment = mods.baseMods.itemattachment;
+ data.data.description += mods.baseMods.description;
+ }
+ }
+
+ let imgPath = await ImportHelpers.getImageFilename(zip, "Equipment", "Gear", data.flags.ffgimportid);
+ if (imgPath) {
+ data.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
+ }
+
+ await ImportHelpers.addImportItemToCompendium("Item", data, pack);
+
+ currentCount += 1;
+
+ $(".gear .import-progress-bar")
+ .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
+ .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ });
+ }
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ }
+}
diff --git a/modules/importer/oggdude/importers/item-attachments.js b/modules/importer/oggdude/importers/item-attachments.js
new file mode 100644
index 00000000..e715670b
--- /dev/null
+++ b/modules/importer/oggdude/importers/item-attachments.js
@@ -0,0 +1,56 @@
+import ImportHelpers from "../../import-helpers.js";
+
+export default class ItemAttachments {
+ static async Import(xml) {
+ const base = JXON.xmlToJs(xml);
+ let items = base?.ItemAttachments?.ItemAttachment;
+ if (items) {
+ let totalCount = items.length;
+ let currentCount = 0;
+ let pack = await ImportHelpers.getCompendiumPack("Item", `oggdude.ItemAttachments`);
+ CONFIG.logger.debug(`Starting Oggdude Item Attachments Import`);
+ $(".import-progress.itemattachments").toggleClass("import-hidden");
+
+ await ImportHelpers.asyncForEach(items, async (item) => {
+ try {
+ let data = ImportHelpers.prepareBaseObject(item, "itemattachment");
+ if (Array.isArray(item.Type)) item.Type = item.Type[0];
+ data.img = `/systems/starwarsffg/images/mod-${item?.Type ? item.Type.toLowerCase() : "all"}.png`;
+ data.data = {
+ description: item.Description,
+ attributes: {},
+ price: {
+ value: item.Price ? parseInt(item.Price, 10) : 0,
+ },
+ rarity: {
+ value: item.Rarity ? parseInt(item.Rarity, 10) : 0,
+ },
+ hardpoints: {
+ value: item.HP ? parseInt(item.HP, 10) : 0,
+ },
+ type: item.Type ? item.Type.toLowerCase() : "all",
+ itemmodifier: [],
+ };
+
+ data.data.description += ImportHelpers.getSources(item?.Sources ?? item?.Source);
+ const mods = await ImportHelpers.processMods(item);
+ if (mods) {
+ if (mods?.baseMods?.attributes) data.data.attributes = mods.baseMods.attributes;
+ if (mods?.baseMods?.itemattachment) data.data.itemattachment = mods.baseMods.itemattachment;
+ if (mods?.baseMods?.description) data.data.description += `Base Mods
${mods.baseMods.description}`;
+ if (mods?.addedMods?.itemmodifier) data.data.itemmodifier = mods.addedMods.itemmodifier;
+ }
+
+ await ImportHelpers.addImportItemToCompendium("Item", data, pack);
+ currentCount += 1;
+
+ $(".itemattachments .import-progress-bar")
+ .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
+ .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ });
+ }
+ }
+}
diff --git a/modules/importer/oggdude/importers/item-descriptors.js b/modules/importer/oggdude/importers/item-descriptors.js
new file mode 100644
index 00000000..695d3ec0
--- /dev/null
+++ b/modules/importer/oggdude/importers/item-descriptors.js
@@ -0,0 +1,42 @@
+import ImportHelpers from "../../import-helpers.js";
+
+export default class ItemDescriptors {
+ static async Import(xml) {
+ const base = JXON.xmlToJs(xml);
+ let items = base.ItemDescriptors.ItemDescriptor;
+ let totalCount = items.length;
+ let currentCount = 0;
+ let pack = await ImportHelpers.getCompendiumPack("Item", `oggdude.ItemQualities`);
+ CONFIG.logger.debug(`Starting Oggdude Item Descriptor Import`);
+ $(".import-progress.itemdescriptors").toggleClass("import-hidden");
+
+ await ImportHelpers.asyncForEach(items, async (item) => {
+ try {
+ let data = ImportHelpers.prepareBaseObject(item, "itemmodifier");
+ data.img = `/systems/starwarsffg/images/mod-${item.Type ? item.Type.toLowerCase() : "all"}.png`;
+ data.data = {
+ description: item.Description?.length ? item.Description : item.ModDesc,
+ attributes: {},
+ type: item.Type ? item.Type.toLowerCase() : "all",
+ rank: 1,
+ };
+
+ const mods = await ImportHelpers.processMods(item);
+ if (mods) {
+ if (mods?.baseMods?.attributes) data.data.attributes = mods.baseMods.attributes;
+ }
+
+ await ImportHelpers.addImportItemToCompendium("Item", data, pack);
+
+ currentCount += 1;
+
+ $(".itemdescriptors .import-progress-bar")
+ .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
+ .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ });
+ CONFIG.logger.debug(`Completed Oggdude Item Descriptor Import`);
+ }
+}
diff --git a/modules/importer/oggdude/importers/signature-abilities.js b/modules/importer/oggdude/importers/signature-abilities.js
new file mode 100644
index 00000000..f4f21801
--- /dev/null
+++ b/modules/importer/oggdude/importers/signature-abilities.js
@@ -0,0 +1,125 @@
+import ImportHelpers from "../../import-helpers.js";
+
+export default class SignatureAbilities {
+ static async Import(xml, zip) {
+ const base = JXON.xmlToJs(xml);
+ let items = base?.SigAbilityNodes?.SigAbilityNode;
+
+ if (items?.length) {
+ const files = Object.values(zip.files).filter((file) => {
+ return !file.dir && file.name.split(".").pop() === "xml" && file.name.includes("/SigAbilities/");
+ });
+
+ let totalCount = files.length;
+ let currentCount = 0;
+
+ $(".import-progress.signatureabilities").toggleClass("import-hidden");
+ let pack = await ImportHelpers.getCompendiumPack("Item", `oggdude.SignatureAbilities`);
+ CONFIG.logger.debug(`Starting Oggdude Signature Ability (${files.length}) Import`);
+
+ await ImportHelpers.asyncForEach(files, async (file) => {
+ try {
+ const zipData = await zip.file(file.name).async("text");
+ const xmlData = ImportHelpers.stringToXml(zipData);
+ const signatureAbility = JXON.xmlToJs(xmlData);
+ const item = signatureAbility.SigAbility;
+
+ let data = ImportHelpers.prepareBaseObject(item, "signatureability");
+
+ data.data = {
+ description: item.Description,
+ attributes: {},
+ upgrades: {},
+ };
+
+ data.data.description += ImportHelpers.getSources(item.Sources ?? item.Source);
+ item.AbilityRows.AbilityRow.forEach((row, i) => {
+ try {
+ // skip the first row because it is the large primary ability box
+ if (i > 0) {
+ row.Abilities.Key.forEach((keyName, index) => {
+ let rowAbility = {};
+
+ let rowAbilityData = items.find((a) => {
+ return a.Key === keyName;
+ });
+
+ rowAbility.name = rowAbilityData.Name;
+ rowAbility.description = rowAbilityData.Description;
+ rowAbility.visible = true;
+ rowAbility.attributes = {};
+
+ if (row.Directions.Direction[index].Up) {
+ rowAbility["links-top-1"] = true;
+ }
+
+ switch (row.AbilitySpan.Span[index]) {
+ case "1":
+ rowAbility.size = "single";
+ break;
+ case "2":
+ rowAbility.size = "double";
+ if (index < 3 && row.Directions.Direction[index + 1].Up) {
+ rowAbility["links-top-2"] = true;
+ }
+ break;
+ case "3":
+ rowAbility.size = "triple";
+ if (index < 2 && row.Directions.Direction[index + 1].Up) {
+ rowAbility["links-top-2"] = true;
+ }
+ if (index < 2 && row.Directions.Direction[index + 2].Up) {
+ rowAbility["links-top-3"] = true;
+ }
+ break;
+ case "4":
+ rowAbility.size = "full";
+ if (index < 1 && row.Directions.Direction[index + 1].Up) {
+ rowAbility["links-top-2"] = true;
+ }
+ if (index < 1 && row.Directions.Direction[index + 2].Up) {
+ rowAbility["links-top-3"] = true;
+ }
+ if (index < 1 && row.Directions.Direction[index + 3].Up) {
+ rowAbility["links-top-4"] = true;
+ }
+ break;
+ default:
+ rowAbility.size = "single";
+ rowAbility.visible = false;
+ }
+
+ if (row.Directions.Direction[index].Right) {
+ rowAbility["links-right"] = true;
+ }
+
+ const talentKey = `upgrade${(i - 1) * 4 + index}`;
+ data.data.upgrades[talentKey] = rowAbility;
+ });
+ }
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, data.name);
+ }
+ });
+ let imgPath = await ImportHelpers.getImageFilename(zip, "SigAbilities", "", data.flags.ffgimportid);
+ if (imgPath) {
+ data.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
+ } else {
+ data.img = `icons/svg/aura.svg`;
+ }
+
+ await ImportHelpers.addImportItemToCompendium("Item", data, pack);
+ currentCount += 1;
+
+ $(".signatureabilities .import-progress-bar")
+ .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
+ .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ });
+
+ CONFIG.logger.debug(`Completed Oggdude Signature Ability (${files.length}) Import`);
+ }
+ }
+}
diff --git a/modules/importer/oggdude/importers/skills.js b/modules/importer/oggdude/importers/skills.js
new file mode 100644
index 00000000..bc810c61
--- /dev/null
+++ b/modules/importer/oggdude/importers/skills.js
@@ -0,0 +1,58 @@
+import ImportHelpers from "../../import-helpers.js";
+
+export default class Skills {
+ static async Import(xml, createJournalCompendium) {
+ CONFIG.temporary["skills"] = {};
+
+ const base = JXON.xmlToJs(xml);
+ let items = base.Skills.Skill;
+ let totalCount = items.length;
+ let currentCount = 0;
+ let pack;
+ if (createJournalCompendium) {
+ pack = await ImportHelpers.getCompendiumPack("JournalEntry", `oggdude.SkillDescriptions`);
+ $(".import-progress.skills").toggleClass("import-hidden");
+ CONFIG.logger.debug(`Starting Oggdude Skill Import`);
+ }
+
+ await ImportHelpers.asyncForEach(items, async (item) => {
+ try {
+ let data = {
+ name: `${item.TypeValue === "stKnowledge" ? "Knowledge: " : ""}${item.Name.replace(" - ", ": ")}`,
+ flags: {
+ ffgimportid: item.Key,
+ },
+ content: item?.Description?.length && item.Description.length > 0 ? item.Description : "Dataset did not have a description",
+ };
+ CONFIG.temporary.skills[data.flags.ffgimportid] = data.name;
+
+ if (createJournalCompendium) {
+ switch (item.TypeValue) {
+ case "stKnowledge": {
+ data.img = `icons/svg/book.svg`;
+ break;
+ }
+ case "stCombat": {
+ data.img = `icons/svg/combat.svg`;
+ break;
+ }
+ default: {
+ data.img = `icons/svg/eye.svg`;
+ }
+ }
+ data.content += ImportHelpers.getSources(item?.Sources ?? item?.Source);
+ await ImportHelpers.addImportItemToCompendium("JournalEntry", data, pack);
+
+ currentCount += 1;
+
+ $(".skills .import-progress-bar")
+ .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
+ .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
+ }
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ });
+ CONFIG.logger.debug(`Completed Oggdude Skill Import`);
+ }
+}
diff --git a/modules/importer/oggdude/importers/specializations.js b/modules/importer/oggdude/importers/specializations.js
new file mode 100644
index 00000000..149f346b
--- /dev/null
+++ b/modules/importer/oggdude/importers/specializations.js
@@ -0,0 +1,103 @@
+import ImportHelpers from "../../import-helpers.js";
+
+export default class Specializations {
+ static async Import(zip) {
+ try {
+ const files = Object.values(zip.files).filter((file) => {
+ return !file.dir && file.name.split(".").pop() === "xml" && file.name.includes("/Specializations/");
+ });
+ let totalCount = files.length;
+ let currentCount = 0;
+
+ if (files.length) {
+ let pack = await ImportHelpers.getCompendiumPack("Item", `oggdude.Specializations`);
+ CONFIG.logger.debug(`Starting Oggdude Specialization Import`);
+ $(".import-progress.specializations").toggleClass("import-hidden");
+
+ await ImportHelpers.asyncForEach(files, async (file) => {
+ try {
+ const zipData = await zip.file(file.name).async("text");
+ const xmlData = ImportHelpers.stringToXml(zipData);
+ const specializationData = JXON.xmlToJs(xmlData);
+ const item = specializationData.Specialization;
+
+ let data = ImportHelpers.prepareBaseObject(item, "specialization");
+ data.data = {
+ attributes: {},
+ description: item.Description,
+ talents: {},
+ careerskills: {},
+ isReadOnly: true,
+ };
+
+ data.data.description += ImportHelpers.getSources(item?.Sources ?? item?.Source);
+ data.data.attributes = mergeObject(data.data.attributes, ImportHelpers.processStatMod(item?.Attributes));
+ data.data.attributes = mergeObject(data.data.attributes, ImportHelpers.processCareerSkills(item?.CareerSkills, true));
+
+ for (let i = 0; i < item.TalentRows.TalentRow.length; i += 1) {
+ const row = item.TalentRows.TalentRow[i];
+
+ await ImportHelpers.asyncForEach(row.Talents.Key, async (keyName, index) => {
+ let rowTalent = {};
+
+ let talentItem = await ImportHelpers.findCompendiumEntityByImportId("Item", keyName, undefined, "talent");
+ if (!talentItem) {
+ talentItem = ImportHelpers.findEntityByImportId("items", keyName);
+ }
+
+ if (talentItem) {
+ rowTalent.name = talentItem.name;
+ rowTalent.description = talentItem.data.description;
+ rowTalent.activation = talentItem.data.activation.value;
+ rowTalent.activationLabel = talentItem.data.activation.label;
+ rowTalent.isForceTalent = talentItem?.data?.isForceTalent ? true : false;
+ rowTalent.isConflictTalent = talentItem?.data?.isConflictTalent ? true : false;
+ rowTalent.isRanked = talentItem?.data?.ranks?.ranked ? true : false;
+ rowTalent.size = "single";
+ rowTalent.canLinkTop = true;
+ rowTalent.canLinkRight = true;
+ rowTalent.itemId = talentItem._id;
+ rowTalent.attributes = talentItem.data.attributes;
+
+ if (row.Directions.Direction[index].Up && row.Directions.Direction[index].Up === "true") {
+ rowTalent["links-top-1"] = true;
+ }
+
+ if (row.Directions.Direction[index].Right && row.Directions.Direction[index].Right === "true") {
+ rowTalent["links-right"] = true;
+ }
+
+ if (talentItem.compendium) {
+ rowTalent.pack = `${talentItem.compendium.metadata.package}.${talentItem.compendium.metadata.name}`;
+ }
+
+ const talentKey = `talent${i * 4 + index}`;
+ data.data.talents[talentKey] = rowTalent;
+ } else {
+ CONFIG.logger.warn(`Unable to find ${keyName}`);
+ }
+ });
+ }
+
+ let imgPath = await ImportHelpers.getImageFilename(zip, "Specialization", "", data.flags.ffgimportid);
+ if (imgPath) {
+ data.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
+ }
+
+ await ImportHelpers.addImportItemToCompendium("Item", data, pack, true);
+
+ currentCount += 1;
+
+ $(".specializations .import-progress-bar")
+ .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
+ .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ });
+ }
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ }
+}
diff --git a/modules/importer/oggdude/importers/species.js b/modules/importer/oggdude/importers/species.js
new file mode 100644
index 00000000..f5284c69
--- /dev/null
+++ b/modules/importer/oggdude/importers/species.js
@@ -0,0 +1,126 @@
+import ImportHelpers from "../../import-helpers.js";
+
+export default class Species {
+ static async Import(zip) {
+ try {
+ const files = Object.values(zip.files).filter((file) => {
+ return !file.dir && file.name.split(".").pop() === "xml" && file.name.includes("/Species/");
+ });
+ let totalCount = files.length;
+ let currentCount = 0;
+
+ if (files.length) {
+ let pack = await ImportHelpers.getCompendiumPack("Item", `oggdude.Species`);
+ CONFIG.logger.debug(`Starting Oggdude Species Import`);
+ $(".import-progress.species").toggleClass("import-hidden");
+
+ await ImportHelpers.asyncForEach(files, async (file) => {
+ try {
+ const zipData = await zip.file(file.name).async("text");
+ const xmlData = ImportHelpers.stringToXml(zipData);
+ const speciesData = JXON.xmlToJs(xmlData);
+ const item = speciesData.Species;
+
+ let data = ImportHelpers.prepareBaseObject(item, "species");
+
+ data.data = {
+ attributes: {},
+ description: item.Description,
+ };
+
+ // populate starting characteristics
+ Object.keys(item.StartingChars).forEach((char) => {
+ data.data.attributes[char] = {
+ mod: char,
+ modtype: "Characteristic",
+ value: parseInt(item.StartingChars[char], 10),
+ exclude: true,
+ };
+ });
+
+ // populate starting stats
+ Object.keys(item.StartingAttrs).forEach((attr) => {
+ let mod;
+ switch (attr) {
+ case "WoundThreshold":
+ case "StrainThreshold": {
+ mod = attr.replace("Threshold", "");
+ break;
+ }
+ }
+ if (mod === "Wound") mod = "Wounds";
+
+ if (mod) {
+ data.data.attributes[mod] = {
+ mod,
+ modtype: "Stat",
+ value: item?.StartingAttrs?.[attr] ? parseInt(item.StartingAttrs[attr], 10) : 0,
+ exclude: true,
+ };
+ }
+ });
+
+ // populate species bonus skills
+ if (item?.SkillModifiers?.SkillModifier) {
+ if (!Array.isArray(item?.SkillModifiers?.SkillModifier)) {
+ item.SkillModifiers.SkillModifier = [item.SkillModifiers.SkillModifier];
+ }
+
+ item.SkillModifiers.SkillModifier.forEach((skillMod) => {
+ let skill = CONFIG.temporary.skills[skillMod.Key];
+ if (skill) {
+ data.data.attributes[skill] = {
+ mod: skill,
+ modtype: "Skill Rank",
+ value: skillMod.RankStart ? parseInt(skillMod.RankStart, 10) : 0,
+ exclude: false,
+ };
+ }
+ });
+ }
+
+ if (item?.OptionChoices?.OptionChoice) {
+ if (!Array.isArray(item.OptionChoices.OptionChoice)) {
+ item.OptionChoices.OptionChoice = [item.OptionChoices.OptionChoice];
+ }
+
+ data.data.description += "Abilities
";
+
+ await ImportHelpers.asyncForEach(item.OptionChoices.OptionChoice, async (o) => {
+ let option = o.Options.Option;
+ if (!Array.isArray(o.Options.Option)) {
+ option = [o.Options.Option];
+ }
+
+ if (option[0].DieModifiers) {
+ const dieModifiers = await ImportHelpers.processDieMod(option[0].DieModifiers);
+ data.data.attributes = mergeObject(data.data.attributes, dieModifiers.attributes);
+ }
+
+ data.data.description += `${option[0].Name} : ${option[0].Description}
`;
+ });
+ }
+
+ let imgPath = await ImportHelpers.getImageFilename(zip, "Species", "", data.flags.ffgimportid);
+ if (imgPath) {
+ data.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
+ }
+
+ data.data.description += ImportHelpers.getSources(item.Sources ?? item.Source);
+
+ await ImportHelpers.addImportItemToCompendium("Item", data, pack);
+ currentCount += 1;
+
+ $(".species .import-progress-bar")
+ .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
+ .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ });
+ }
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ }
+}
diff --git a/modules/importer/oggdude/importers/talents.js b/modules/importer/oggdude/importers/talents.js
new file mode 100644
index 00000000..c03d989d
--- /dev/null
+++ b/modules/importer/oggdude/importers/talents.js
@@ -0,0 +1,88 @@
+import ImportHelpers from "../../import-helpers.js";
+
+export default class Talents {
+ static async Import(xml, zip) {
+ try {
+ const base = JXON.xmlToJs(xml);
+ let items = base?.Talents?.Talent;
+ if (items?.length) {
+ let totalCount = items.length;
+ let currentCount = 0;
+ let pack = await ImportHelpers.getCompendiumPack("Item", `oggdude.Talents`);
+ CONFIG.logger.debug(`Starting Oggdude Talents Import`);
+ $(".import-progress.talents").toggleClass("import-hidden");
+
+ await ImportHelpers.asyncForEach(items, async (item) => {
+ try {
+ let data = ImportHelpers.prepareBaseObject(item, "talent");
+
+ let activation = "Passive";
+ let activationLabel = "SWFFG.TalentActivationsPassive";
+
+ switch (item.ActivationValue) {
+ case "taManeuver":
+ activation = "Active (Maneuver)";
+ activationLabel = "SWFFG.TalentActivationsActiveManeuver";
+ break;
+ case "taAction":
+ activation = "Active (Action)";
+ activationLabel = "SWFFG.TalentActivationsActiveAction";
+ break;
+ case "taIncidental":
+ activation = "Active (Incidental)";
+ activationLabel = "SWFFG.TalentActivationsActiveIncidental";
+ break;
+ case "taIncidentalOOT":
+ activation = "Active (Incidental, Out of Turn)";
+ activationLabel = "SWFFG.TalentActivationsActiveIncidentalOutofTurn";
+ break;
+ default:
+ activation = "Passive";
+ activationLabel = "SWFFG.TalentActivationsPassive";
+ }
+
+ data.data = {
+ attributes: {},
+ description: item.Description,
+ ranks: {
+ ranked: item.Ranked === "true" ? true : false,
+ },
+ activation: {
+ value: activation,
+ label: activationLabel,
+ },
+ isForceTalent: item.ForceTalent === "true" ? true : false,
+ isConflictTalent: item?.Conflict ? true : false,
+ };
+
+ data.data.description += ImportHelpers.getSources(item?.Sources ?? item?.Source);
+ data.data.attributes = mergeObject(data.data.attributes, ImportHelpers.processStatMod(item?.Attributes));
+ data.data.attributes = mergeObject(data.data.attributes, ImportHelpers.processCareerSkills(item?.ChooseCareerSkills?.NewSkills));
+ if (item?.DieModifiers) {
+ const dieModifiers = await ImportHelpers.processDieMod(item.DieModifiers);
+ data.data.attributes = mergeObject(data.data.attributes, dieModifiers.attributes);
+ }
+
+ let imgPath = await ImportHelpers.getImageFilename(zip, "Talent", "", data.flags.ffgimportid);
+ if (imgPath) {
+ data.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
+ }
+
+ await ImportHelpers.addImportItemToCompendium("Item", data, pack);
+
+ currentCount += 1;
+
+ $(".talents .import-progress-bar")
+ .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
+ .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ });
+ }
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ CONFIG.logger.debug(`Completed Oggdude Talents Import`);
+ }
+}
diff --git a/modules/importer/oggdude/importers/vehicles.js b/modules/importer/oggdude/importers/vehicles.js
new file mode 100644
index 00000000..380be5f9
--- /dev/null
+++ b/modules/importer/oggdude/importers/vehicles.js
@@ -0,0 +1,138 @@
+import ImportHelpers from "../../import-helpers.js";
+
+export default class Vehicles {
+ static async Import(zip) {
+ try {
+ const files = Object.values(zip.files).filter((file) => {
+ return !file.dir && file.name.split(".").pop() === "xml" && file.name.includes("/Vehicles/");
+ });
+ let totalCount = files.length;
+ let currentCount = 0;
+
+ if (files.length) {
+ CONFIG.logger.debug(`Starting Oggdude Vehicles Import`);
+ $(".import-progress.vehicles").toggleClass("import-hidden");
+
+ await ImportHelpers.asyncForEach(files, async (file) => {
+ try {
+ const zipData = await zip.file(file.name).async("text");
+ const xmlData = ImportHelpers.stringToXml(zipData);
+ const vehicleData = JXON.xmlToJs(xmlData);
+ const item = vehicleData.Vehicle;
+
+ const isSpaceVehicle = ["Starship", "Non-Fighter Starship", "Capital Ship", "Station", "Medium Transport"].some((element) => item?.Categories?.Category?.includes(element)) || ["Space-dwelling Creature", "Hyperdrive Sled", "Hyperdrive Docking Ring"].includes(item.Type);
+
+ const pack = isSpaceVehicle ? await ImportHelpers.getCompendiumPack("Actor", `oggdude.Vehicles.Space`) : await ImportHelpers.getCompendiumPack("Actor", `oggdude.Vehicles.Planetary`);
+
+ let data = ImportHelpers.prepareBaseObject(item, "vehicle");
+ data.items = [];
+ data.data = {
+ biography: item.Description,
+ stats: {
+ silhouette: {
+ value: item.Silhouette ? parseInt(item.Silhouette, 10) : 0,
+ },
+ speed: {
+ max: item.Speed ? parseInt(item.Speed, 10) : 0,
+ },
+ handling: {
+ value: item.Handling ? parseInt(item.Handling, 10) : 0,
+ },
+ hullTrauma: {
+ max: item.HullTrauma ? parseInt(item.HullTrauma, 10) : 0,
+ },
+ systemStrain: {
+ max: item.SystemStrain ? parseInt(item.SystemStrain, 10) : 0,
+ },
+ shields: {
+ fore: item.DefFore ? parseInt(item.DefFore, 10) : 0,
+ port: item.DefPort ? parseInt(item.DefPort, 10) : 0,
+ starboard: item.DefStarboard ? parseInt(item.DefStarboard, 10) : 0,
+ aft: item.DefAft ? parseInt(item.DefAft, 10) : 0,
+ },
+ armour: {
+ value: item.Armor ? parseInt(item.Armor, 10) : 0,
+ },
+ sensorRange: {
+ value: item.SensorRangeValue?.includes("sr") ? item.SensorRangeValue.replace("sr", "") : "None",
+ },
+ crew: {},
+ passengerCapacity: {
+ value: item.Passengers ? parseInt(item.Passengers, 10) : 0,
+ },
+ encumbrance: {
+ max: item.EncumbranceCapacity ? parseInt(item.EncumbranceCapacity, 10) : 0,
+ },
+ cost: {
+ value: item.Price ? parseInt(item.Price, 10) : 0,
+ },
+ rarity: {
+ value: item.Rarity ? parseInt(item.Rarity, 10) : 0,
+ },
+ customizationHardPoints: {
+ value: item.HP ? parseInt(item.HP, 10) : 0,
+ },
+ hyperdrive: {
+ value: item.HyperdrivePrimary ? parseInt(item.HyperdrivePrimary, 10) : 1,
+ },
+ consumables: {
+ value: 1,
+ duration: "months",
+ },
+ },
+ itemmodifier: [],
+ itemattachment: [],
+ };
+
+ data.data.biography += ImportHelpers.getSources(item?.Sources ?? item?.Source);
+
+ if (item.VehicleWeapons?.VehicleWeapon) {
+ if (!Array.isArray(item.VehicleWeapons.VehicleWeapon)) {
+ item.VehicleWeapons.VehicleWeapon = [item.VehicleWeapons.VehicleWeapon];
+ }
+ await ImportHelpers.asyncForEach(item.VehicleWeapons.VehicleWeapon, async (weapon) => {
+ const weaponEntity = await ImportHelpers.findCompendiumEntityByImportId("Item", weapon.Key, undefined, "shipweapon");
+ if (weaponEntity) {
+ const weaponData = JSON.parse(JSON.stringify(weaponEntity));
+ delete weaponData._id;
+
+ if (!Array.isArray(weaponData.data.itemmodifier)) {
+ weaponData.data.itemmodifier = [];
+ }
+ const count = weapon.Count ? parseInt(weapon.Count, 10) : 1;
+ if (!weaponData.data?.firingarc) weaponData.data.firingarc = {};
+ ["Fore", "Aft", "Port", "Starboard", "Dorsal", "Ventral"].forEach((location) => {
+ weaponData.data.firingarc[location.toLowerCase()] = weapon?.FiringArcs?.[location] === "true" ? true : false;
+ });
+
+ if (weapon?.Qualities) {
+ const mods = await ImportHelpers.processMods(weapon);
+ if (mods.qualities) {
+ weaponData.data.itemmodifier = weaponData.data.itemmodifier.concat(mods.qualities.itemmodifier);
+ }
+ }
+
+ data.items.push(weaponData);
+ } else {
+ CONFIG.logger.warn(`Unable to find weaon : ${weapon.Key}`);
+ }
+ });
+ }
+
+ await ImportHelpers.addImportItemToCompendium("Actor", data, pack);
+
+ currentCount += 1;
+
+ $(".vehicles .import-progress-bar")
+ .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
+ .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ });
+ }
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ }
+}
diff --git a/modules/importer/oggdude/importers/weapons.js b/modules/importer/oggdude/importers/weapons.js
new file mode 100644
index 00000000..8103b38c
--- /dev/null
+++ b/modules/importer/oggdude/importers/weapons.js
@@ -0,0 +1,101 @@
+import ImportHelpers from "../../import-helpers.js";
+
+export default class Weapons {
+ static async Import(xml, zip) {
+ try {
+ const base = JXON.xmlToJs(xml);
+ let items = base?.Weapons?.Weapon;
+ if (items?.length) {
+ let totalCount = items.length;
+ let currentCount = 0;
+ let packs = {
+ weapon: await ImportHelpers.getCompendiumPack("Item", `oggdude.Weapons`),
+ shipweapon: await ImportHelpers.getCompendiumPack("Item", `oggdude.VehicleWeapons`),
+ };
+ CONFIG.logger.debug(`Starting Oggdude Weapons Import`);
+ $(".import-progress.weapons").toggleClass("import-hidden");
+
+ await ImportHelpers.asyncForEach(items, async (item) => {
+ try {
+ const itemType = item.Type === "Vehicle" ? "shipweapon" : "weapon";
+ let pack = packs[itemType];
+
+ let data = ImportHelpers.prepareBaseObject(item, itemType);
+ data.data = {
+ attributes: {},
+ description: item.Description,
+ encumbrance: {
+ value: item.Encumbrance ? parseInt(item.Encumbrance, 10) : 0,
+ },
+ price: {
+ value: item.Price ? parseInt(item.Price, 10) : 0,
+ },
+ rarity: {
+ value: item.Rarity ? parseInt(item.Rarity, 10) : 0,
+ isrestricted: item.Restricted === "true" ? true : false,
+ },
+ damage: {
+ value: parseInt(!item?.Damage ? item.DamageAdd : item.Damage, 10),
+ },
+ crit: {
+ value: item.Crit ? parseInt(item.Crit, 10) : 0,
+ },
+ skill: {
+ value: CONFIG.temporary.skills[item.SkillKey],
+ },
+ range: {
+ value: item?.RangeValue ? item.RangeValue.replace("wr", "") : "",
+ },
+ hardpoints: {
+ value: item.HP ? parseInt(item.HP, 10) : 0,
+ },
+ itemmodifier: [],
+ itemattachment: [],
+ };
+ data.data.description += ImportHelpers.getSources(item?.Sources ?? item?.Source);
+
+ data.data.skill.useBrawn = ["Melee", "Brawl", "Lightsaber"].some((element) => data.data.skill.value.includes(element)) && (!item.Damage || item.Damage === "0");
+
+ const mods = await ImportHelpers.processMods(item);
+ if (mods) {
+ if (mods.baseMods) {
+ data.data.attributes = mods.baseMods.attributes;
+ data.data.itemmodifier = data.data.itemmodifier.concat(mods.baseMods.itemmodifier);
+ data.data.itemattachment = mods.baseMods.itemattachment;
+ data.data.description += mods.baseMods.description;
+ }
+ if (mods.qualities) {
+ data.data.itemmodifier = data.data.itemmodifier.concat(mods.qualities.itemmodifier);
+ }
+ }
+
+ if (item?.DamageAdd && parseInt(item.DamageAdd, 10) > 0 && data.type === "weapon") {
+ data.data.attributes[randomID()] = {
+ isCheckbox: false,
+ mod: "damage",
+ modtype: "Weapon Stat",
+ value: parseInt(item.DamageAdd, 10),
+ };
+ }
+
+ let imgPath = await ImportHelpers.getImageFilename(zip, "Equipment", "Weapon", data.flags.ffgimportid);
+ if (imgPath) {
+ data.img = await ImportHelpers.importImage(imgPath.name, zip, pack);
+ }
+ await ImportHelpers.addImportItemToCompendium("Item", data, pack);
+
+ currentCount += 1;
+
+ $(".weapons .import-progress-bar")
+ .width(`${Math.trunc((currentCount / totalCount) * 100)}%`)
+ .html(`${Math.trunc((currentCount / totalCount) * 100)}%`);
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ });
+ }
+ } catch (err) {
+ CONFIG.logger.error(`Error importing record : `, err);
+ }
+ }
+}
diff --git a/modules/importer/oggdude/oggdude.js b/modules/importer/oggdude/oggdude.js
new file mode 100644
index 00000000..75cac6fd
--- /dev/null
+++ b/modules/importer/oggdude/oggdude.js
@@ -0,0 +1,31 @@
+import Armor from "./importers/armor.js";
+import Career from "./importers/careers.js";
+import ForcePowers from "./importers/forcepowers.js";
+import Gear from "./importers/gear.js";
+import ItemAttachments from "./importers/item-attachments.js";
+import ItemDescriptors from "./importers/item-descriptors.js";
+import SignatureAbilities from "./importers/signature-abilities.js";
+import Skills from "./importers/skills.js";
+import Specializations from "./importers/specializations.js";
+import Species from "./importers/species.js";
+import Talents from "./importers/talents.js";
+import Vehicles from "./importers/vehicles.js";
+import Weapons from "./importers/weapons.js";
+
+export default class OggDude {
+ static Import = {
+ Armor: Armor.Import,
+ Career: Career.Import,
+ ForcePowers: ForcePowers.Import,
+ Gear: Gear.Import,
+ ItemAttachments: ItemAttachments.Import,
+ ItemDescriptors: ItemDescriptors.Import,
+ SignatureAbilities: SignatureAbilities.Import,
+ Skills: Skills.Import,
+ Specializations: Specializations.Import,
+ Species: Species.Import,
+ Talents: Talents.Import,
+ Vehicles: Vehicles.Import,
+ Weapons: Weapons.Import,
+ };
+}
diff --git a/modules/importer/swa-importer.js b/modules/importer/swa-importer.js
index a71f25af..32279b5f 100644
--- a/modules/importer/swa-importer.js
+++ b/modules/importer/swa-importer.js
@@ -1,3 +1,4 @@
+import ItemBaseFFG from "../items/itembase-ffg.js";
import ImportHelpers from "./import-helpers.js";
export default class SWAImporter extends FormApplication {
@@ -61,6 +62,7 @@ export default class SWAImporter extends FormApplication {
if (form.data.files.length) {
zip = await ImportHelpers.readBlobFromFile(form.data.files[0]).then(JSZip.loadAsync);
}
+ let filter = $(form).find("[name=tag-filter]").val();
if (zip) {
// load ancillary files
@@ -91,7 +93,7 @@ export default class SWAImporter extends FormApplication {
const adversaries = this._enableImportSelection(zip.files, "adversaries", true, true);
if (adversaries) {
- await this._handleAdversaries(zip);
+ await this._handleAdversaries(zip, filter);
}
}
}
@@ -120,7 +122,7 @@ export default class SWAImporter extends FormApplication {
}
}
- async _handleAdversaries(zip) {
+ async _handleAdversaries(zip, filter) {
this._importLogger(`Starting Adversary Import`);
const adversaryFiles = Object.values(zip.files).filter((file) => {
return !file.dir && file.name.split(".").pop() === "json" && file.name.includes("/adversaries/");
@@ -162,6 +164,10 @@ export default class SWAImporter extends FormApplication {
await ImportHelpers.asyncForEach(fileData, async (item) => {
try {
+ if (filter.length > 0 && !item.tags.includes(filter)) {
+ return;
+ }
+
let skills = {
"Astrogation": {
"rank": 0,
@@ -360,80 +366,84 @@ export default class SWAImporter extends FormApplication {
type: item.type === "Nemesis" ? "character" : "minion",
flags: {
ffgimportid: `${f.name}-${item.type}-${item.name}`,
+ config: {
+ enableAutoSoakCalculation: false,
+ enableCriticalInjuries: item.type === "Minion" ? false : true,
+ },
},
+ flags: {},
data: {
- characteristics: {
- "Brawn": {
- "value": item.characteristics.Brawn,
- },
- "Agility": {
- "value": item.characteristics.Agility,
- },
- "Intellect": {
- "value": item.characteristics.Intellect,
- },
- "Cunning": {
- "value": item.characteristics.Cunning,
- },
- "Willpower": {
- "value": item.characteristics.Willpower,
- },
- "Presence": {
- "value": item.characteristics.Presence,
- },
- },
+ attributes: {},
+ characteristics: {},
skills,
stats: {},
},
items: [],
};
+ Object.values(CONFIG.FFG.characteristics).forEach((char) => {
+ adversary.data.characteristics[char.value] = { value: item?.characteristics?.[char.value] ? item?.characteristics?.[char.value] : 0 };
+ });
+
if (item.derived) {
- if (item.derived.defence) {
- adversary.data.stats.defence = {
- ranged: item.derived.defence[0],
- melee: item.derived.defence[1],
- };
- }
- if (item.derived.soak) {
- adversary.data.stats.soak = {
- value: item.derived.soak - adversary.data.characteristics.Brawn,
- };
- }
- if (item.derived.wounds) {
- if (adversary.type === "minion") {
- adversary.data.quantity = {
- value: 1,
- max: 1,
+ ["soak", "wounds", "strain"].forEach((stat) => {
+ if (stat === "soak") {
+ adversary.data.stats[stat] = {
+ value: parseInt(item.derived[stat], 10),
};
- adversary.data["unit_wounds"] = {
- value: item.derived.wounds,
+ adversary.data.attributes.Soak = {
+ mod: "Soak",
+ modtype: "Stat",
+ value: Math.abs(adversary.data.stats[stat].value - parseInt(adversary.data.characteristics.Brawn.value, 10)),
};
+ } else {
+ if (stat === "wounds" && adversary.type === "minion") {
+ adversary.data.quantity = {
+ value: 1,
+ max: 1,
+ };
+
+ adversary.data["unit_wounds"] = {
+ value: item.derived[stat],
+ };
+ } else {
+ adversary.data.stats[stat] = {
+ value: 0,
+ min: 0,
+ max: item.derived[stat],
+ };
+ }
}
- adversary.data.stats.wounds = {
- value: 0,
- min: 0,
- max: item.derived.wounds,
- };
- }
- if (item.derived.strain) {
- adversary.data.stats.strain = {
- value: 0,
- min: 0,
- max: item.derived.strain,
- };
- }
+ });
}
if (item.skills) {
- Object.keys(item.skills).forEach((skill) => {
+ let isMinion = Array.isArray(item.skills);
+ let adversarySkills = isMinion ? item.skills : Object.keys(item.skills);
+ adversarySkills.forEach((skillRaw) => {
+ let skill = skillRaw.match(/^[^\(]*/)[0];
+ skill = $.trim(skill);
+ let alternateCharacteristic = skillRaw.match(/(?<=\()(.*?)(?=\))/)?.length ? skillRaw.match(/(?<=\()(.*?)(?=\))/)[0] : undefined;
+
const ffgSkill = Object.keys(skills).find((s) => skill.toLowerCase() === s.toLowerCase());
if (ffgSkill) {
- adversary.data.skills[ffgSkill].rank = item.skills[skill];
+ adversary.data.skills[ffgSkill].groupskill = isMinion ? true : false;
+ if (!isMinion) adversary.data.skills[ffgSkill].rank = item.skills[skill];
if (CONFIG.temporary.swa.skills?.[skill]?.characteristic) {
- adversary.data.skills[ffgSkill].characteristic = CONFIG.temporary.swa.skills[skill].characteristic;
+ if (alternateCharacteristic) {
+ adversary.data.skills[ffgSkill].characteristic = alternateCharacteristic;
+ } else {
+ adversary.data.skills[ffgSkill].characteristic = CONFIG.temporary.swa.skills[skill].characteristic;
+ }
+ }
+
+ if ((!adversary.data.skills[ffgSkill]?.type && skilltheme !== "starwars") || (skilltheme === "starwars" && !CONFIG.FFG.skills[ffgSkill])) {
+ adversary.data.skills[ffgSkill].Key = ffgSkill.toUpperCase();
+ adversary.data.skills[ffgSkill].custom = true;
+ adversary.data.skills[ffgSkill].type = "General";
+ adversary.data.skills[ffgSkill].label = ffgSkill;
}
}
});
@@ -445,6 +455,7 @@ export default class SWAImporter extends FormApplication {
let adversaryTalent = {
name: talent.name,
type: "talent",
+ flags: {},
data: {
attributes: {},
description: talent.description,
@@ -470,6 +481,7 @@ export default class SWAImporter extends FormApplication {
let adversaryTalent = {
name: swaTalent.name,
type: "talent",
+ flags: {},
data: {
attributes: {},
description: swaTalent.description,
@@ -491,18 +503,24 @@ export default class SWAImporter extends FormApplication {
}
if (item.weapons) {
+ const template = await ImportHelpers.getTemplate("weapon");
item.weapons.forEach((weapon) => {
+ let data = JSON.parse(JSON.stringify(template));
+ let weaponData;
+
if (typeof weapon === "object") {
- let weaponData = {
+ weaponData = {
name: weapon.name,
type: "weapon",
+ flags: {},
data: {
+ attributes: {},
description: "No description provided",
damage: {
- value: weapon.damage,
+ value: parseInt(!weapon?.damage ? weapon["plus-damage"] : weapon.damage, 10),
},
crit: {
- value: weapon.critical,
+ value: parseInt(weapon.critical, 10),
},
special: {
value: weapon?.qualities?.length ? weapon.qualities.join(",") : "",
@@ -515,23 +533,32 @@ export default class SWAImporter extends FormApplication {
},
},
};
- adversary.items.push(weaponData);
+ weaponData.data.skill.useBrawn = ["Melee", "Brawl", "Lightsaber"].some((element) => weaponData.data.skill.value.includes(element)) && (!weapon.damage || weapon.damage === "0");
+ if (weapon?.["plus-damage"] && parseInt(weapon["plus-damage"], 10) > 0) {
+ weaponData.data.attributes[randomID()] = {
+ isCheckbox: false,
+ mod: "damage",
+ modtype: "Weapon Stat",
+ value: parseInt(weapon["plus-damage"], 10),
+ };
+ }
} else {
const swaWeaponKey = Object.keys(CONFIG.temporary.swa.weapons).find((t) => weapon.includes(t));
if (swaWeaponKey) {
const swaWeapon = CONFIG.temporary.swa.weapons[swaWeaponKey];
-
- let weaponData = {
+ weaponData = {
name: swaWeapon.name,
type: "weapon",
+ flags: {},
data: {
+ attributes: {},
description: "No description provided",
damage: {
- value: swaWeapon.damage,
+ value: parseInt(!swaWeapon?.damage ? swaWeapon["plus-damage"] : swaWeapon.damage, 10),
},
crit: {
- value: swaWeapon.critical,
+ value: parseInt(swaWeapon.critical, 10),
},
special: {
value: swaWeapon?.qualities?.length ? swaWeapon.qualities.join(",") : "",
@@ -545,8 +572,47 @@ export default class SWAImporter extends FormApplication {
},
};
- adversary.items.push(weaponData);
+ weaponData.data.skill.useBrawn = ["Melee", "Brawl", "Lightsaber"].some((element) => weaponData.data.skill.value.includes(element)) && (!swaWeapon.damage || swaWeapon.damage === "0");
+
+ if (swaWeapon?.["plus-damage"] && parseInt(swaWeapon["plus-damage"], 10) > 0) {
+ weaponData.data.attributes[randomID()] = {
+ isCheckbox: false,
+ mod: "damage",
+ modtype: "Weapon Stat",
+ value: parseInt(swaWeapon["plus-damage"], 10),
+ };
+ }
+ }
+ }
+
+ if (weaponData) {
+ const templatedData = weaponData;
+ templatedData.data = mergeObject(data, weaponData.data);
+
+ if (templatedData.data.special?.value?.length > 0) {
+ templatedData.data.special.value.split(",").forEach((w) => {
+ const wName = w.match(/^.*([^0-9\s]+)/gim);
+ const wRank = w.match(/[^\w][0-9]/gim);
+
+ const unique = {
+ name: wName[0],
+ type: "itemmodifier",
+ flags: {},
+ data: {
+ description: CONFIG.temporary.swa?.qualities?.[wName]?.description ? CONFIG.temporary.swa.qualities[wName].description : "No description provided",
+ attributes: {},
+ type: "all",
+ rank: wRank ? parseInt(wRank[0].replace(" ", ""), 10) : 1,
+ },
+ };
+ const descriptor = new Item(unique, { temporary: true });
+ descriptor.data._id = randomID();
+ templatedData.data.itemmodifier.push(descriptor.data);
+ });
}
+
+ let w = new Item(templatedData, { temporary: true });
+ adversary.items.push(duplicate(w));
}
});
}
@@ -554,20 +620,109 @@ export default class SWAImporter extends FormApplication {
if (item.gear) {
if (Array.isArray(item.gear)) {
item.gear.forEach((gear) => {
- let gearData = {
- name: gear,
- type: "gear",
- data: {
- description: "No description provided",
- },
- };
+ if (gear.includes("Soak") && gear.includes("Defence")) {
+ let Armordata = {
+ name: gear.slice(0, gear.indexOf("(") - 1).trim(),
+ type: "armour",
+ flags: {},
+ data: {
+ description: "No description provided",
+ defence: {
+ value: gear.charAt(gear.indexOf("Defence") - 2),
+ },
+ soak: {
+ value: gear.charAt(gear.indexOf("Soak") - 2),
+ },
+ },
+ };
+ adversary.items.push(Armordata);
+ } else if (gear.includes("Soak")) {
+ let Armordata = {
+ name: gear.slice(0, gear.indexOf("(") - 1).trim(),
+ type: "armour",
+ flags: {},
+ data: {
+ description: "No description provided",
- adversary.items.push(gearData);
+ soak: {
+ value: gear.charAt(gear.indexOf("Soak") - 2),
+ },
+ },
+ };
+ adversary.items.push(Armordata);
+ } else if (gear.includes("Defence")) {
+ let Armordata = {
+ name: gear.slice(0, gear.indexOf("(") - 1).trim(),
+ type: "armour",
+ flags: {},
+ data: {
+ description: "No description provided",
+ defence: {
+ value: gear.charAt(gear.indexOf("Defence") - 2),
+ },
+ },
+ };
+ adversary.items.push(Armordata);
+ } else {
+ let gearData = {
+ name: gear,
+ type: "gear",
+ flags: {},
+ data: {
+ description: "No description provided",
+ },
+ };
+ adversary.items.push(gearData);
+ }
});
+ } else if (item.gear.includes("Soak") && item.gear.includes("Defence")) {
+ let Armordata = {
+ name: item.gear.slice(0, item.gear.indexOf("(") - 1).trim(),
+ type: "armour",
+ flags: {},
+ data: {
+ description: "No description provided",
+ defence: {
+ value: item.gear.charAt(item.gear.indexOf("Defence") - 2),
+ },
+ soak: {
+ value: item.gear.charAt(item.gear.indexOf("Soak") - 2),
+ },
+ },
+ };
+ adversary.items.push(Armordata);
+ } else if (item.gear.includes("Soak")) {
+ let Armordata = {
+ name: item.gear.slice(0, item.gear.indexOf("(") - 1).trim(),
+ type: "armour",
+ flags: {},
+ data: {
+ description: "No description provided",
+
+ soak: {
+ value: item.gear.charAt(item.gear.indexOf("Soak") - 2),
+ },
+ },
+ };
+ adversary.items.push(Armordata);
+ } else if (item.gear.includes("Defence")) {
+ let Armordata = {
+ name: item.gear.slice(0, item.gear.indexOf("(") - 1).trim(),
+ type: "armour",
+ flags: {},
+ data: {
+ description: "No description provided",
+ defence: {
+ value: item.gear.charAt(item.gear.indexOf("Defence") - 2),
+ },
+ },
+ };
+ adversary.items.push(Armordata);
} else {
let gearData = {
name: item.gear,
type: "gear",
+ flags: {},
data: {
description: "No description provided",
},
@@ -608,14 +763,14 @@ export default class SWAImporter extends FormApplication {
CONFIG.logger.debug(`Importing Adversary - Actor`);
compendiumItem = new Actor(adversary, { temporary: true });
this._importLogger(`New Adversary ${name} : ${JSON.stringify(compendiumItem)}`);
- pack.importEntity(compendiumItem);
+ await pack.importEntity(compendiumItem);
} else {
CONFIG.logger.debug(`Update Adversary - Actor`);
//let updateData = ImportHelpers.buildUpdateData(item);
let updateData = adversary;
updateData["_id"] = entry._id;
this._importLogger(`Updating talent ${name} : ${JSON.stringify(updateData)}`);
- pack.updateEntity(updateData);
+ await pack.updateEntity(updateData);
}
} catch (err) {
CONFIG.logger.error(`Error importing ${item.name} from ${f.name}`, err);
diff --git a/modules/items/item-ffg.js b/modules/items/item-ffg.js
index 522fa8f9..76c04931 100644
--- a/modules/items/item-ffg.js
+++ b/modules/items/item-ffg.js
@@ -2,6 +2,7 @@ import ItemBaseFFG from "./itembase-ffg.js";
import PopoutEditor from "../popout-editor.js";
import ActorOptions from "../actors/actor-ffg-options.js";
import ImportHelpers from "../importer/import-helpers.js";
+import ModifierHelpers from "../helpers/modifiers.js";
import Helpers from "../helpers/common.js";
/**
@@ -22,44 +23,105 @@ export class ItemFFG extends ItemBaseFFG {
const actorData = this.actor ? this.actor.data : {};
const data = itemData.data;
+ if (this.compendium) {
+ itemData.flags.isCompendium = true;
+ itemData.flags.ffgUuid = this.uuid;
+ } else {
+ itemData.flags.isCompendium = false;
+ itemData.flags.ffgIsOwned = false;
+ if (this.isOwned) {
+ itemData.flags.ffgIsOwned = true;
+ itemData.flags.ffgUuid = this.uuid;
+ } else if (itemData._id) {
+ itemData.flags.ffgTempId = itemData._id;
+ }
+ }
+
data.renderedDesc = PopoutEditor.renderDiceImages(data.description, actorData);
// perform localisation of dynamic values
switch (this.type) {
case "weapon":
- const rangeId = `SWFFG.WeaponRange${this._capitalize(data.range.value)}`;
- data.range.label = rangeId;
+ case "shipweapon":
+ // Apply item attachments / modifiers
+ data.damage.value = parseInt(data.damage.value, 10);
+ data.crit.value = parseInt(data.crit.value, 10);
+ data.encumbrance.value = parseInt(data.encumbrance.value, 10);
+ data.price.value = parseInt(data.price.value, 10);
+ data.rarity.value = parseInt(data.rarity.value, 10);
+ data.hardpoints.value = parseInt(data.hardpoints.value, 10);
+
+ data.range.adjusted = data.range.value;
+ data.damage.adjusted = parseInt(data.damage.value, 10);
+ data.crit.adjusted = parseInt(data.crit.value, 10);
+ data.encumbrance.adjusted = parseInt(data.encumbrance.value, 10);
+ data.price.adjusted = parseInt(data.price.value, 10);
+ data.rarity.adjusted = parseInt(data.rarity.value, 10);
+ data.hardpoints.adjusted = parseInt(data.hardpoints.value, 10);
+
+ data.adjusteditemmodifier = [];
+
+ if (data?.itemmodifier) {
+ data.itemmodifier.forEach((modifier) => {
+ modifier.data.rank_current = modifier.data.rank;
+ data.adjusteditemmodifier.push({ ...modifier });
+ data.damage.adjusted += ModifierHelpers.getCalculatedValueFromCurrentAndArray(modifier, [], "damage", "Weapon Stat");
+ });
+ }
- if (this.isOwned && this.actor && this.actor.type !== "vehicle" && this.actor.data.type !== "vehicle") {
+ if (data?.itemattachment) {
+ data.itemattachment.forEach((attachment) => {
+ const activeModifiers = attachment.data.itemmodifier.filter((i) => i.data?.active);
+ data.damage.adjusted += ModifierHelpers.getCalculatedValueFromCurrentAndArray(attachment, activeModifiers, "damage", "Weapon Stat");
+ data.crit.adjusted += ModifierHelpers.getCalculatedValueFromCurrentAndArray(attachment, activeModifiers, "critical", "Weapon Stat");
+ if (data.crit.adjusted < 1) data.crit.adjusted = 1;
+ const range = ModifierHelpers.getCalculatedValueFromCurrentAndArray(attachment, activeModifiers, "range", "Weapon Stat");
+ const currentRangeIndex = Object.values(CONFIG.FFG.ranges).findIndex((r) => r.value === data.range.value);
+ const newRange = currentRangeIndex + range;
+ if (newRange < 0) newRange = 0;
+ if (newRange >= Object.values(CONFIG.FFG.ranges).length) newRange = Object.values(CONFIG.FFG.ranges).length - 1;
+
+ data.range.adjusted = Object.values(CONFIG.FFG.ranges)[newRange].value;
+
+ if (attachment?.data?.itemmodifier) {
+ const activeMods = attachment.data.itemmodifier.filter((i) => i?.data?.active);
+
+ activeMods.forEach((am) => {
+ const foundItem = data.adjusteditemmodifier.find((i) => i.name === am.name);
+
+ if (foundItem) {
+ foundItem.data.rank_current = parseInt(foundItem.data.rank_current, 10) + 1;
+ } else {
+ am.data.rank_current = 1;
+ data.adjusteditemmodifier.push({ ...am, adjusted: true });
+ }
+ });
+ }
+ });
+ }
+
+ if (this.isOwned && this.actor) {
let damageAdd = 0;
for (let attr in data.attributes) {
if (data.attributes[attr].mod === "damage" && data.attributes[attr].modtype === "Weapon Stat") {
damageAdd += parseInt(data.attributes[attr].value, 10);
}
}
- if ((data.skill.value.includes("Melee") || data.skill.value.includes("Brawl")) && data.skill.useBrawn) {
- data.damage.value = parseInt(actorData.data.characteristics.Brawn.value, 10) + damageAdd;
- data.damage.adjusted = +data.damage.value;
- } else {
- data.damage.value = parseInt(data.damage.value, 10);
- data.damage.adjusted = +data.damage.value + damageAdd;
+ if (this.actor.type !== "vehicle" && this.actor.data.type !== "vehicle") {
+ if (ModifierHelpers.applyBrawnToDamage(data)) {
+ const olddamage = data.damage.value;
+ data.damage.value = parseInt(actorData.data.characteristics.Brawn.value, 10) + damageAdd;
+ data.damage.adjusted += parseInt(data.damage.value, 10) - olddamage;
+ } else {
+ data.damage.value = parseInt(data.damage.value, 10);
+ data.damage.adjusted += damageAdd;
+ }
}
}
- break;
- case "shipweapon":
- const vehiclerangeId = `SWFFG.VehicleRange${this._capitalize(data.range.value)}`;
- data.range.label = vehiclerangeId;
-
- let damageAdd = 0;
- for (let attr in data.attributes) {
- if (data.attributes[attr].mod === "damage" && data.attributes[attr].modtype === "Weapon Stat") {
- damageAdd += parseInt(data.attributes[attr].value, 10);
- }
- }
+ const rangeLabel = (this.type === "weapon" ? `SWFFG.WeaponRange` : `SWFFG.VehicleRange`) + this._capitalize(data.range.adjusted);
+ data.range.label = rangeLabel;
- data.damage.value = parseInt(data.damage.value, 10);
- data.damage.adjusted = +data.damage.value + damageAdd;
break;
case "talent":
const cleanedActivationName = data.activation.value.replace(/[\W_]+/g, "");
@@ -69,6 +131,19 @@ export class ItemFFG extends ItemBaseFFG {
default:
}
+ if (["weapon", "armor"].includes(this.type)) {
+ // get all item attachments
+ let totalHPUsed = 0;
+
+ if (data?.itemattachment?.length) {
+ data.itemattachment.forEach((attachment) => {
+ totalHPUsed += attachment.data.hardpoints.value;
+ });
+ }
+
+ data.hardpoints.current = data.hardpoints.value - totalHPUsed;
+ }
+
if (this.type === "forcepower") {
this._prepareForcePowers();
}
@@ -240,20 +315,20 @@ export class ItemFFG extends ItemBaseFFG {
}
// General equipment properties
else if (this.type !== "talent") {
- if (data.hasOwnProperty("special")) {
- props.push(`Special qualities: ${data.special.value}
`);
- }
- if (data.hasOwnProperty("equippable")) {
- props.push(game.i18n.localize(data.equippable.equipped ? "SWFFG.Equipped" : "SWFFG.Unequipped"));
+ if (data.hasOwnProperty("adjusteditemmodifier")) {
+ const qualities = data.adjusteditemmodifier.map((m) => `${m.name} ${m.data.rank_current > 0 ? m.data.rank_current : ""} ${m.adjusted ? "" + game.i18n.localize("SWFFG.FromAttachment") + "
" : ""}`);
+
+ props.push(`${game.i18n.localize("SWFFG.ItemDescriptors")}:
`);
}
+
if (data.hasOwnProperty("encumbrance")) {
- props.push(`Encumbrance: ${data.encumbrance.value}`);
+ props.push(`${game.i18n.localize("SWFFG.Encumbrance")}: ${data.encumbrance?.adjusted ? data.encumbrance.adjusted : data.encumbrance.value}`);
}
if (data.hasOwnProperty("price")) {
- props.push(`Price: ${data.price.value}`);
+ props.push(`${game.i18n.localize("SWFFG.ItemsPrice")}: ${data.price?.adjusted ? data.price.adjusted : data.price.value}`);
}
if (data.hasOwnProperty("rarity")) {
- props.push(`Rarity: ${data.rarity.value}`);
+ props.push(`${game.i18n.localize("SWFFG.ItemsRarity")}: ${data.rarity?.adjusted ? data.rarity.adjusted : data.rarity.value} ${data.rarity.isrestricted ? "" + game.i18n.localize("SWFFG.IsRestricted") + "" : ""}`);
}
}
diff --git a/modules/items/item-sheet-ffg-v2.js b/modules/items/item-sheet-ffg-v2.js
index 793ee736..857d90f0 100644
--- a/modules/items/item-sheet-ffg-v2.js
+++ b/modules/items/item-sheet-ffg-v2.js
@@ -1,10 +1,6 @@
import { ItemSheetFFG } from "./item-sheet-ffg.js";
export class ItemSheetFFGV2 extends ItemSheetFFG {
- constructor(...args) {
- super(...args);
- }
-
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
@@ -14,12 +10,6 @@ export class ItemSheetFFGV2 extends ItemSheetFFG {
});
}
- /** @override */
- get template() {
- const path = "systems/starwarsffg/templates/items";
- return `${path}/ffg-${this.item.data.type}-sheet.html`;
- }
-
getData() {
const data = super.getData();
return data;
diff --git a/modules/items/item-sheet-ffg.js b/modules/items/item-sheet-ffg.js
index 1e0bb38b..0b68a089 100644
--- a/modules/items/item-sheet-ffg.js
+++ b/modules/items/item-sheet-ffg.js
@@ -4,6 +4,8 @@ import ModifierHelpers from "../helpers/modifiers.js";
import ItemHelpers from "../helpers/item-helpers.js";
import ImportHelpers from "../importer/import-helpers.js";
import DiceHelpers from "../helpers/dice-helpers.js";
+import item from "../helpers/embeddeditem-helpers.js";
+import EmbeddedItemHelpers from "../helpers/embeddeditem-helpers.js";
/**
* Extend the basic ItemSheet with some very simple modifications
@@ -22,7 +24,7 @@ export class ItemSheetFFG extends ItemSheet {
/** @override */
get template() {
const path = "systems/starwarsffg/templates/items";
- return `${path}/ffg-${this.item.data.type}-sheet.html`;
+ return `${path}/ffg-${this.item.type}-sheet.html`;
}
/* -------------------------------------------- */
@@ -33,6 +35,12 @@ export class ItemSheetFFG extends ItemSheet {
if (options?.action === "update" && this.object.compendium) {
data.item = mergeObject(data.item, options.data);
+ } else if (options?.action === "ffgUpdate") {
+ if (options?.data?.data) {
+ data.item = mergeObject(data.item, options.data);
+ } else {
+ data.item.data = mergeObject(data.item.data, options.data);
+ }
}
data.classType = this.constructor.name;
@@ -41,16 +49,26 @@ export class ItemSheetFFG extends ItemSheet {
data.dtypes = ["String", "Number", "Boolean"];
if (data?.data?.attributes) {
for (let attr of Object.values(data.data.attributes)) {
- attr.isCheckbox = attr.dtype === "Boolean";
+ if (attr?.dtype) {
+ attr.isCheckbox = attr.dtype === "Boolean";
+ }
}
}
+ data.isTemp = false;
+ if (this.object.data?.flags?.ffgTempId) {
+ data.isTemp = true;
+ }
+
switch (this.object.data.type) {
case "weapon":
case "shipweapon":
- this.position.width = 530;
+ this.position.width = 550;
this.position.height = 750;
break;
+ case "itemattachment":
+ this.position.width = 500;
+ this.position.height = 450;
case "armour":
case "gear":
case "shipattachment":
@@ -257,6 +275,7 @@ export class ItemSheetFFG extends ItemSheet {
}
// Everything below here is only needed if the sheet is editable
+ if (this.object.data.flags.readonly) this.options.editable = false;
if (!this.options.editable) return;
// Add or Remove Attribute
@@ -308,15 +327,254 @@ export class ItemSheetFFG extends ItemSheet {
}
});
- if (["weapon", "armor"].includes(this.object.data.type)) {
+ if (["weapon", "armor", "itemattachment", "shipweapon"].includes(this.object.data.type)) {
const itemToItemAssociation = new DragDrop({
dragSelector: ".item",
- dropSelector: ".window-content",
+ dropSelector: null,
permissions: { dragstart: true, drop: true },
callbacks: { drop: this._onDropItem.bind(this) },
});
itemToItemAssociation.bind(html[0]);
+
+ //commented out the ability to add on-the-fly qualities/attachments
+ //html.find(".resource.pills.itemmodifier .block-title, .resource.pills.itemattachment .block-title").append("");
+
+ // html.find(".resource.pills.itemmodifier").on("click", async (event) => {
+
+ // const tempItem = await EmbeddedItemHelpers.createNewEmbeddedItem("itemmodifier", {attributes: {}, description: "", rank: 1}, {ffgTempId: this.object.id, ffgParentApp: this.appId} );
+
+ // let data = {};
+ // this.object.data.data[tempItem.type].push(tempItem);
+ // setProperty(data, `data.${tempItem.type}`, this.object.data.data[tempItem.type]);
+ // await this.object.update(data);
+ // tempItem.data.flags.ffgTempItemIndex = this.object.data.data[tempItem.type].findIndex((i) => i._id === tempItem.data._id);
+ // tempItem.sheet.render(true);
+ // });
+
+ // html.find(".resource.pills.itemattachment").on("click", async (event) => {
+ // const tempItem = await EmbeddedItemHelpers.createNewEmbeddedItem("itemattachment", {attributes: {}, description: "", itemmodifier: []}, {ffgTempId: this.object.id, ffgUuid: this.item.uuid, ffgParentApp: this.appId,});
+
+ // let data = {};
+ // this.object.data.data[tempItem.type].push(tempItem);
+ // setProperty(data, `data.${tempItem.type}`, this.object.data.data[tempItem.type]);
+ // await this.object.update(data);
+
+ // tempItem.data.flags.ffgTempItemIndex = this.object.data.data[tempItem.type].findIndex((i) => i._id === tempItem.data._id);
+
+ // tempItem.sheet.render(true);
+ // });
}
+
+ html.find(".item-pill .item-delete, .additional .add-modifier .item-delete").on("click", (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+
+ const li = event.currentTarget;
+ const parent = $(li).parent()[0];
+ const itemType = parent.dataset.itemName;
+ const itemIndex = parent.dataset.itemIndex;
+
+ const items = this.object.data.data[itemType];
+ items.splice(itemIndex, 1);
+
+ let formData = {};
+ setProperty(formData, `data.${itemType}`, items);
+
+ this.object.update(formData);
+ });
+
+ html.find(".item-pill .rank").on("click", (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ const li = $(event.currentTarget).parent()[0];
+ const itemType = li.dataset.itemName;
+ const itemIndex = li.dataset.itemIndex;
+
+ const item = this.object.data.data[itemType][parseInt(itemIndex, 10)];
+ const title = `${this.object.name} ${item.name}`;
+
+ new Dialog(
+ {
+ title,
+ content: {
+ item,
+ type: itemType,
+ parenttype: this.object.data.type,
+ },
+ buttons: {
+ done: {
+ icon: '',
+ label: game.i18n.localize("SWFFG.ButtonAccept"),
+ callback: (html) => {
+ switch (itemType) {
+ case "itemmodifier": {
+ const formData = {};
+ const items = $(html).find("input");
+
+ items.each((index) => {
+ const input = $(items[index]);
+ const name = input.attr("name");
+ const id = input[0].dataset.itemId;
+
+ let arrayItem = this.object.data.data[itemType].findIndex((i) => i._id === id);
+
+ if (arrayItem > -1) {
+ setProperty(this.object.data.data[itemType][arrayItem], name, parseInt(input.val(), 10));
+ }
+ });
+
+ setProperty(formData, `data.${itemType}`, this.object.data.data[itemType]);
+ this.object.update(formData);
+
+ break;
+ }
+ case "itemattachment": {
+ console.log(itemType);
+ break;
+ }
+ }
+ },
+ },
+ cancel: {
+ icon: '',
+ label: game.i18n.localize("SWFFG.Cancel"),
+ },
+ },
+ },
+ {
+ classes: ["dialog", "starwarsffg"],
+ template: `systems/starwarsffg/templates/items/dialogs/ffg-edit-${itemType}.html`,
+ }
+ ).render(true);
+ });
+
+ html.find(".item-pill, .additional .add-modifier .fa-edit").on("click", async (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ const li = event.currentTarget;
+ let itemType = li.dataset.itemName;
+ let itemIndex = li.dataset.itemIndex;
+
+ if ($(li).hasClass("adjusted")) {
+ return await EmbeddedItemHelpers.loadItemModifierSheet(this.object._id, itemType, itemIndex, this.object?.actor?._id);
+ }
+
+ if ($(li).hasClass("fa-edit")) {
+ const parent = $(li).parent()[0];
+ itemType = parent.dataset.itemName;
+ itemIndex = parent.dataset.itemIndex;
+ }
+
+ const item = this.object.data.data[itemType][itemIndex];
+
+ let temp = {
+ ...item,
+ flags: {
+ ffgTempId: this.object.id,
+ ffgTempItemType: itemType,
+ ffgTempItemIndex: itemIndex,
+ ffgIsTemp: true,
+ ffgParent: this.object.data.flags,
+ ffgParentApp: this.appId,
+ },
+ };
+ if (this.object.isOwned) {
+ let ownerObject = await fromUuid(this.object.uuid);
+
+ temp = {
+ ...item,
+ flags: {
+ ffgTempId: this.object.id,
+ ffgTempItemType: itemType,
+ ffgTempItemIndex: itemIndex,
+ ffgIsTemp: true,
+ ffgUuid: this.object.uuid,
+ ffgIsOwned: this.object.isOwned,
+ },
+ };
+ }
+
+ let tempItem = await Item.create(temp, { temporary: true });
+
+ tempItem.data._id = temp._id;
+ if (!temp._id) {
+ tempItem.data._id = randomID();
+ }
+ tempItem.sheet.render(true);
+ });
+
+ html.find(".additional .modifier-active").on("click", async (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ const li = event.currentTarget;
+ const parent = $(li).parent()[0];
+ let itemType = parent.dataset.itemName;
+ let itemIndex = parent.dataset.itemIndex;
+ const item = this.object.data.data[itemType][itemIndex];
+ item.data.active = !item.data.active;
+
+ if (this.object.data.flags.ffgTempId) {
+ // this is a temporary sheet for an embedded item
+
+ item.flags = {
+ ffgTempId: this.object.id,
+ ffgTempItemType: itemType,
+ ffgTempItemIndex: itemIndex,
+ ffgParent: this.object.data.flags,
+ ffgIsTemp: true,
+ };
+
+ await EmbeddedItemHelpers.updateRealObject({ data: item }, {});
+ } else {
+ let formData = {};
+ setProperty(formData, `data.${itemType}`, this.object.data.data[itemType]);
+ this.object.update(formData);
+ }
+
+ this.object.sheet.render(true);
+ });
+
+ html.find(".additional .add-new-item").on("click", async (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ const li = event.currentTarget;
+ let itemType = li.dataset.acceptableType;
+
+ let temp = {
+ img: "icons/svg/mystery-man.svg",
+ name: "",
+ type: itemType,
+ flags: {
+ ffgTempId: this.object.id,
+ ffgTempItemType: itemType,
+ ffgTempItemIndex: -1,
+ ffgParent: this.object.data.flags,
+ ffgIsTemp: true,
+ ffgUuid: this.object.uuid,
+ ffgParentApp: this.appId,
+ ffgIsOwned: this.object.isOwned,
+ },
+ data: {
+ attributes: {},
+ description: "",
+ },
+ };
+
+ let tempItem = await Item.create(temp, { temporary: true });
+ tempItem.data._id = temp._id;
+ if (!temp._id) {
+ tempItem.data._id = randomID();
+ }
+
+ let data = {};
+ this.object.data.data[itemType].push(tempItem);
+ setProperty(data, `data.${itemType}`, this.object.data.data[itemType]);
+ await this.object.update(data);
+
+ tempItem.data.flags.ffgTempItemIndex = this.object.data.data[itemType].findIndex((i) => i._id === tempItem.data._id);
+
+ tempItem.sheet.render(true);
+ });
}
/* -------------------------------------------- */
@@ -590,21 +848,55 @@ export class ItemSheetFFG extends ItemSheet {
// Case 1 - Import from a Compendium pack
let itemObject;
if (data.pack) {
- itemObject = await this.importItemFromCollection(data.pack, data.id);
+ const compendiumObject = await this.importItemFromCollection(data.pack, data.id);
+ itemObject = compendiumObject.data;
}
// Case 2 - Import from World entity
else {
- itemObject = await game.items.get(data.id);
+ itemObject = duplicate(await game.items.get(data.id));
if (!itemObject) return;
}
+ itemObject._id = randomID();
+
+ if ((itemObject.type === "itemattachment" || itemObject.type === "itemmodifier") && (obj.data.type === itemObject.data.type || itemObject.data.type === "all" || obj.data.type === "itemattachment")) {
+ let items = obj?.data?.data?.[itemObject.type];
+ if (!items) {
+ items = [];
+ }
+
+ const foundItem = items.find((i) => {
+ return i._id === itemObject._id || (i.flags?.ffgimportid?.length ? i.flags.ffgimportid === itemObject.flags.ffgimportid : false);
+ });
- if (itemObject.data.type === "attachment") {
- let items = this.object.items.entries;
- items.push(duplicate(itemObject));
+ switch (itemObject.type) {
+ case "itemmodifier": {
+ if (parseInt(itemObject.data.rank, 10) === 0) {
+ itemObject.data.rank = 1;
+ }
+
+ if (foundItem && this.object.type !== "itemattachment") {
+ foundItem.data.rank += itemObject.data.rank;
+ } else {
+ items.push(itemObject);
+ }
+ break;
+ }
+ case "itemattachment": {
+ if (this.object.data.data.hardpoints.current - itemObject.data.hardpoints.value >= 0) {
+ items.push(itemObject);
+ } else {
+ ui.notifications.warn(`Item does not have enough available hardpoints (${this.object.data.data.hardpoints.current} left)`);
+ }
+ break;
+ }
+ default: {
+ return;
+ }
+ }
let formData = {};
- setProperty(formData, `items`, items);
+ setProperty(formData, `data.${itemObject.type}`, items);
obj.update(formData);
}
diff --git a/modules/items/itembase-ffg.js b/modules/items/itembase-ffg.js
index 91a92ff6..e8bcfedb 100644
--- a/modules/items/itembase-ffg.js
+++ b/modules/items/itembase-ffg.js
@@ -1,35 +1,35 @@
-export default class ItemBaseFFG extends Item {
- constructor(...args) {
- super(...args);
+import EmbeddedItemHelpers from "../helpers/embeddeditem-helpers.js";
- this.items = this.items || [];
- }
+export default class ItemBaseFFG extends Item {
+ async update(data, options = {}) {
+ if (!this.data?.flags?.ffgTempId || (this.data?.flags?.ffgTempId === this.data._id && !this.data.isTemp) || this.data?.flags?.ffgIsOwned) {
+ super.update(data, options);
+ // if (this.compendium) {
+ // return this.sheet.render(true);
+ // }
+ return;
+ } else {
+ const preState = Object.values(this.apps)[0]._state;
- /** @override */
- prepareEmbeddedEntities() {
- this.items = this._prepareAssociatedItems(this.data.items || []);
- }
+ await EmbeddedItemHelpers.updateRealObject(this, data);
- _prepareAssociatedItems(items) {
- const prior = this.items;
- const c = new Collection();
- for (let i of items) {
- let item = null;
+ if (this.data.flags?.ffgParent?.isCompendium || Object.values(this.apps)[0]._state !== preState) {
+ if (this.data.flags?.ffgParent?.ffgUuid) {
+ this.sheet.render(false);
+ }
+ } else {
+ let me = this;
- // Prepare item data
- try {
- if (prior && prior.has(i._id)) {
- item = prior.get(i._id);
- item._data = i;
- item.prepareData();
- } else item = Item.createOwned(i, this);
- c.set(i._id, item);
- } catch (err) {
- // Handle preparation failures gracefully
- err.message = `Owned Item preparation failed for ${item.id} (${item.name}) in Item ${this.id} (${this.name})`;
- console.error(err);
+ // we're working on an embedded item
+ await this.sheet.render(true);
+ const appId = this.data?.flags?.ffgParentApp;
+ if (appId) {
+ const newData = ui.windows[appId].object.data.data;
+ newData[this.data.flags.ffgTempItemType][this.data.flags.ffgTempItemIndex] = mergeObject(newData[this.data.flags.ffgTempItemType][this.data.flags.ffgTempItemIndex], this.data);
+ await ui.windows[appId].render(true, { action: "ffgUpdate", data: newData });
+ }
+ return;
}
}
- return c;
}
}
diff --git a/modules/items/journalentry-ffg.js b/modules/items/journalentry-ffg.js
new file mode 100644
index 00000000..8e8b7d55
--- /dev/null
+++ b/modules/items/journalentry-ffg.js
@@ -0,0 +1,35 @@
+/**
+ * Extend the basic Item with some very simple modifications.
+ * @extends {Item}
+ */
+export default class JournalEntryFFG extends JournalEntry {
+ /** @override */
+ static get config() {
+ return {
+ baseEntity: JournalEntry,
+ collection: game.journal,
+ embeddedEntities: {},
+ label: "ENTITY.JournalEntry",
+ permissions: {
+ create: "TEMPLATE_CREATE",
+ },
+ };
+ }
+
+ /** @override */
+ static get defaultOptions() {
+ return mergeObject(super.defaultOptions, {
+ classes: ["sheet", "journal-sheet"],
+ width: 720,
+ height: 800,
+ resizable: true,
+ closeOnSubmit: false,
+ submitOnClose: true,
+ viewPermission: ENTITY_PERMISSIONS.OBSERVER,
+ });
+ }
+
+ async update(data, options = {}) {
+ return;
+ }
+}
diff --git a/modules/popout-editor.js b/modules/popout-editor.js
index b153c078..27868501 100644
--- a/modules/popout-editor.js
+++ b/modules/popout-editor.js
@@ -72,73 +72,109 @@ export default class PopoutEditor extends FormApplication {
type: "ability",
character: "d",
class: "starwars",
- pattern: /\[(AB)(ILITY)?\]/gim,
+ pattern: /:(ability):|\[(AB)(ILITY)?\]/gim,
},
{
type: "advantage",
character: "a",
class: dicetheme,
- pattern: /\[(AD)(VANTAGE)?\]/gim,
+ pattern: /:(advantage):|\[(AD)(VANTAGE)?\]/gim,
+ },
+ {
+ type: "difficulty",
+ character: "dd",
+ class: "starwars",
+ pattern: /:(average):/gim,
},
{
type: "boost",
character: "b",
class: "starwars",
- pattern: /\[(BO)(OST)?\]/gim,
+ pattern: /:(boost):|\[(BO)(OST)?\]/gim,
},
{
type: "challenge",
character: "c",
class: "starwars",
- pattern: /\[(CH)(ALLENGE)?\]/gim,
+ pattern: /:(challenge):|\[(CH)(ALLENGE)?\]/gim,
},
{
type: "dark",
character: "z",
class: "starwars",
- pattern: /\[(DA)(RK)?\]/gim,
+ pattern: /:(darkside):|\[(DA)(RK)?\]/gim,
+ },
+ {
+ type: "difficulty",
+ character: "dddd",
+ class: "starwars",
+ pattern: /:(daunting):/gim,
},
{
type: "despair",
character: dicetheme === "starwars" ? "y" : "d",
class: dicetheme,
- pattern: /\[(DE)(SPAIR)?\]/gim,
+ pattern: /:(despair):|\[(DE)(SPAIR)?\]/gim,
+ },
+ {
+ type: "difficulty",
+ character: "d",
+ class: "starwars",
+ pattern: /:(difficulty):|\[(DI)(FFICULTY)?\]/gim,
},
{
type: "difficulty",
character: "d",
class: "starwars",
- pattern: /\[(DI)(FFICULTY)?\]/gim,
+ pattern: /:(easy):/gim,
+ },
+ {
+ type: "challenge",
+ character: "c",
+ class: "starwars",
+ pattern: /:(easy-1):/gim,
},
{
type: "forcepoint",
character: "Y",
class: "starwars",
- pattern: /\[(FP|FORCEPOINT)\]/gim,
+ pattern: /:(forcepip):|\[(FP|FORCEPOINT)\]/gim,
+ },
+ {
+ type: "difficulty",
+ character: "ddddd",
+ class: "starwars",
+ pattern: /:(formidable):/gim,
},
{
type: "failure",
character: "f",
class: dicetheme,
- pattern: /\[(FA)(ILURE)?\]/gim,
+ pattern: /:(failure):|\[(FA)(ILURE)?\]/gim,
},
{
type: "force",
character: "C",
class: "starwars",
- pattern: /\[(FO)(RCE)?\]/gim,
+ pattern: /:(force):|\[(FO)(RCE)?\]/gim,
+ },
+ {
+ type: "difficulty",
+ character: "ddd",
+ class: "starwars",
+ pattern: /:(hard):/gim,
},
{
type: "light",
character: "Z",
class: "starwars",
- pattern: /\[(LI)(GHT)?\]/gim,
+ pattern: /:(lightside):|\[(LI)(GHT)?\]/gim,
},
{
type: "proficiency",
character: "c",
class: "starwars",
- pattern: /\[(PR)(OFICIENCY)?\]/gim,
+ pattern: /:(proficiency):|\[(PR)(OFICIENCY)?\]/gim,
},
{
type: "remsetback",
@@ -156,25 +192,25 @@ export default class PopoutEditor extends FormApplication {
type: "setback",
character: "b",
class: "starwars",
- pattern: /\[(SE)(TBACK)?\]/gim,
+ pattern: /:(setback):|\[(SE)(TBACK)?\]/gim,
},
{
type: "success",
character: "s",
class: dicetheme,
- pattern: /\[(SU)(CCESS)?\]/gim,
+ pattern: /:(success):|\[(SU)(CCESS)?\]/gim,
},
{
type: "threat",
character: dicetheme === "starwars" ? "t" : "h",
class: dicetheme,
- pattern: /\[(TH)(REAT)?\]/gim,
+ pattern: /:(threat):|\[(TH)(REAT)?\]/gim,
},
{
type: "triumph",
character: dicetheme === "starwars" ? "x" : "t",
class: dicetheme,
- pattern: /\[(TR)(IUMPH)?\]/gim,
+ pattern: /:(triumph):|\[(TR)(IUMPH)?\]/gim,
},
{
type: "adddifficulty",
@@ -200,6 +236,47 @@ export default class PopoutEditor extends FormApplication {
html = html.replace(item.pattern, `${item.character}`);
});
+ const regex = /:([\w]*)-(\d):/gim;
+ let m;
+ while ((m = regex.exec(html)) !== null) {
+ // This is necessary to avoid infinite loops with zero-width matches
+ if (m.index === regex.lastIndex) {
+ regex.lastIndex++;
+ }
+
+ let symbolCount = 0;
+ let replaceString = "";
+
+ switch (m[1]) {
+ case "average": {
+ symbolCount = 2;
+ break;
+ }
+ case "hard": {
+ symbolCount = 3;
+ break;
+ }
+ case "daunting": {
+ symbolCount = 4;
+ break;
+ }
+ case "formidable": {
+ symbolCount = 5;
+ break;
+ }
+ }
+
+ for (let i = 0; i < symbolCount; i += 1) {
+ if (i + 1 <= parseInt(m[2], 10)) {
+ replaceString += `c`;
+ } else {
+ replaceString += `d`;
+ }
+ }
+
+ html = html.replace(m[0], replaceString);
+ }
+
const oggdudeTags = [
{
startTag: "",
diff --git a/modules/popout-modifiers.js b/modules/popout-modifiers.js
index 4ac4b107..213619d4 100644
--- a/modules/popout-modifiers.js
+++ b/modules/popout-modifiers.js
@@ -32,6 +32,7 @@ export default class PopoutModifiers extends FormApplication {
/** @override */
getData() {
const data = this.object.data;
+
if (this.object.isUpgrade) {
data.data = this.object.parent.data.data.upgrades[this.object.keyname];
} else if (this.object.isTalent) {
@@ -61,6 +62,8 @@ export default class PopoutModifiers extends FormApplication {
/** @override */
async _updateObject(event, formData) {
+ formData = expandObject(formData);
+
// Handle the free-form attributes list
const formAttrs = expandObject(formData)?.data?.attributes || {};
const attributes = Object.values(formAttrs).reduce((obj, v) => {
@@ -78,6 +81,11 @@ export default class PopoutModifiers extends FormApplication {
}
}
+ // recombine attributes to formData
+ if (Object.keys(attributes).length > 0) {
+ setProperty(formData, `data.attributes`, attributes);
+ }
+
if (this.object.isUpgrade) {
let data = attributes;
@@ -95,6 +103,7 @@ export default class PopoutModifiers extends FormApplication {
// Update the Item
await this.object.update(formData);
}
+ mergeObject(this.object.data, formData);
this.render();
}
}
diff --git a/modules/settings/settings-helpers.js b/modules/settings/settings-helpers.js
new file mode 100644
index 00000000..71e78cf1
--- /dev/null
+++ b/modules/settings/settings-helpers.js
@@ -0,0 +1,303 @@
+import DataImporter from "../importer/data-importer.js";
+import SWAImporter from "../importer/swa-importer.js";
+import UISettings from "./ui-settings.js";
+
+export default class SettingsHelpers {
+ // Initialize System Settings after the Init Hook
+ static initLevelSettings() {
+ // System Migration Version
+ game.settings.register("starwarsffg", "systemMigrationVersion", {
+ name: "Current Version",
+ scope: "world",
+ default: null,
+ config: false,
+ type: String,
+ });
+
+ // Register dice theme setting
+ game.settings.register("starwarsffg", "dicetheme", {
+ name: game.i18n.localize("SWFFG.SettingsDiceTheme"),
+ hint: game.i18n.localize("SWFFG.SettingsDiceThemeHint"),
+ scope: "world",
+ config: true,
+ default: "starwars",
+ type: String,
+ onChange: (rule) => {
+ if (rule === "starwars") {
+ game.settings.set("starwarsffg", "enableForceDie", true);
+ }
+ return window.location.reload();
+ },
+ choices: {
+ starwars: "starwars",
+ genesys: "genesys",
+ },
+ });
+
+ // Enable auto Soak calculation
+ game.settings.register("starwarsffg", "enableSoakCalc", {
+ name: game.i18n.localize("SWFFG.EnableSoakCalc"),
+ hint: game.i18n.localize("SWFFG.EnableSoakCalcHint"),
+ scope: "world",
+ config: true,
+ default: true,
+ type: Boolean,
+ onChange: (rule) => window.location.reload(),
+ });
+
+ // Enable auto Soak calculation
+ game.settings.register("starwarsffg", "privateTriggers", {
+ name: game.i18n.localize("SWFFG.EnablePrivateTriggers"),
+ hint: game.i18n.localize("SWFFG.EnablePrivateTriggersHint"),
+ scope: "world",
+ config: true,
+ default: true,
+ type: Boolean,
+ onChange: (rule) => window.location.reload(),
+ });
+
+ // Register grouping talents so people can let them be ordered by purchase history
+ game.settings.register("starwarsffg", "talentSorting", {
+ name: game.i18n.localize("SWFFG.EnableSortTalentsByActivationGlobal"),
+ hint: game.i18n.localize("SWFFG.EnableSortTalentsByActivationHint"),
+ scope: "world",
+ config: true,
+ default: false,
+ type: Boolean,
+ onChange: (rule) => window.location.reload(),
+ });
+
+ // Register skill sorting by localised value setting
+ game.settings.register("starwarsffg", "skillSorting", {
+ name: game.i18n.localize("SWFFG.SettingsSkillSorting"),
+ hint: game.i18n.localize("SWFFG.SettingsSkillSortingHint"),
+ scope: "world",
+ config: true,
+ default: false,
+ type: Boolean,
+ onChange: (rule) => window.location.reload(),
+ });
+
+ // Register setting for group manager Player Character List display mode
+ game.settings.register("starwarsffg", "pcListMode", {
+ name: game.i18n.localize("SWFFG.SettingsPCListMode"),
+ hint: game.i18n.localize("SWFFG.SettingsPCListModeHint"),
+ scope: "world",
+ config: true,
+ default: "active",
+ type: String,
+ choices: {
+ active: game.i18n.localize("SWFFG.SettingsPCListModeActive"),
+ owned: game.i18n.localize("SWFFG.SettingsPCListModeOwned"),
+ },
+ onChange: (rule) => {
+ const groupmanager = canvas?.groupmanager?.window;
+ if (groupmanager) {
+ groupmanager.render();
+ }
+ },
+ });
+
+ // Register placeholder settings to store Destiny Pool values for the group manager.
+ game.settings.register("starwarsffg", "dPoolLight", {
+ name: "Destiny Pool Light",
+ scope: "world",
+ default: 0,
+ config: false,
+ type: Number,
+ onChange: (rule) => {
+ const groupmanager = canvas?.groupmanager?.window;
+ if (groupmanager) {
+ groupmanager.render();
+ }
+ let destinyLight = game.settings.get("starwarsffg", "dPoolLight");
+ document.getElementById("destinyLight").setAttribute("data-value", destinyLight);
+ document.getElementById("destinyLight").innerHTML = destinyLight + `${game.i18n.localize(game.settings.get("starwarsffg", "destiny-pool-light"))}`;
+ },
+ });
+ game.settings.register("starwarsffg", "dPoolDark", {
+ name: "Destiny Pool Dark",
+ scope: "world",
+ default: 0,
+ config: false,
+ type: Number,
+ onChange: (rule) => {
+ const groupmanager = canvas?.groupmanager?.window;
+ if (groupmanager) {
+ groupmanager.render();
+ }
+ let destinyDark = game.settings.get("starwarsffg", "dPoolDark");
+ document.getElementById("destinyDark").setAttribute("data-value", destinyDark);
+ document.getElementById("destinyDark").innerHTML = destinyDark + `${game.i18n.localize(game.settings.get("starwarsffg", "destiny-pool-dark"))}`;
+ },
+ });
+
+ // OggDude Importer Control Menu
+ game.settings.registerMenu("starwarsffg", "odImporter", {
+ name: game.i18n.localize("SWFFG.SettingsOggDudeImporter"),
+ hint: game.i18n.localize("SWFFG.SettingsOggDudeImporterHint"),
+ label: game.i18n.localize("SWFFG.SettingsOggDudeImporterLabel"),
+ icon: "fas fa-file-import",
+ type: DataImporter,
+ restricted: true,
+ });
+ game.settings.register("starwarsffg", "odImporter", {
+ name: "Item Importer",
+ scope: "world",
+ default: {},
+ config: false,
+ default: {},
+ type: Object,
+ });
+
+ // SWA Importer Control Menu
+ game.settings.registerMenu("starwarsffg", "swaImporter", {
+ name: game.i18n.localize("SWFFG.SettingsSWAdversariesImporter"),
+ hint: game.i18n.localize("SWFFG.SettingsSWAdversariesImporterHint"),
+ label: game.i18n.localize("SWFFG.SettingsSWAdversariesImporterLabel"),
+ icon: "fas fa-file-import",
+ type: SWAImporter,
+ restricted: true,
+ });
+ game.settings.register("starwarsffg", "swaImporter", {
+ name: "Adversaries Importer",
+ scope: "world",
+ default: {},
+ config: false,
+ default: {},
+ type: Object,
+ });
+
+ // Enable debug messages in console
+ game.settings.register("starwarsffg", "enableDebug", {
+ name: game.i18n.localize("SWFFG.EnableDebug"),
+ hint: game.i18n.localize("SWFFG.EnableDebugHint"),
+ scope: "world",
+ config: true,
+ default: false,
+ type: Boolean,
+ onChange: (rule) => window.location.reload(),
+ });
+
+ game.settings.registerMenu("starwarsffg", "uiSettings", {
+ name: game.i18n.localize("SWFFG.UISettings"),
+ hint: game.i18n.localize("SWFFG.UISettingsHint"),
+ label: game.i18n.localize("SWFFG.UISettingsLabel"),
+ icon: "fas fa-file-import",
+ type: UISettings,
+ restricted: true,
+ });
+ game.settings.register("starwarsffg", "uiSettings", {
+ name: "UI Settings",
+ scope: "world",
+ default: {},
+ config: false,
+ default: {},
+ type: Object,
+ });
+
+ // Register settings for UI Themes
+ game.settings.register("starwarsffg", "ui-uitheme", {
+ name: game.i18n.localize("SWFFG.SettingsUITheme"),
+ hint: game.i18n.localize("SWFFG.SettingsUIThemeHint"),
+ scope: "world",
+ config: false,
+ default: "default",
+ type: String,
+ onChange: (rule) => window.location.reload(),
+ choices: {
+ default: "Default",
+ mandar: "Mandar",
+ },
+ });
+
+ game.settings.register("starwarsffg", "ui-pausedImage", {
+ name: game.i18n.localize("SWFFG.SettingsPausedImage"),
+ hint: game.i18n.localize("SWFFG.SettingsPausedImageHint"),
+ scope: "world",
+ config: false,
+ default: "",
+ type: String,
+ valueType: "FilePicker",
+ onChange: (rule) => window.location.reload(),
+ });
+
+ game.settings.register("starwarsffg", "destiny-pool-light", {
+ name: game.i18n.localize("SWFFG.SettingsDestinyLight"),
+ hint: game.i18n.localize("SWFFG.SettingsDestinyLightHint"),
+ scope: "world",
+ config: true,
+ default: "SWFFG.Lightside",
+ type: String,
+ onChange: (rule) => {
+ if (rule === "") {
+ game.settings.set("starwarsffg", "destiny-pool-light", "SWFFG.Lightside");
+ }
+ return window.location.reload();
+ },
+ });
+
+ game.settings.register("starwarsffg", "destiny-pool-dark", {
+ name: game.i18n.localize("SWFFG.SettingsDestinyDark"),
+ hint: game.i18n.localize("SWFFG.SettingsDestinyDarkHint"),
+ scope: "world",
+ config: true,
+ default: "SWFFG.Darkside",
+ type: String,
+ onChange: (rule) => {
+ if (rule === "") {
+ game.settings.set("starwarsffg", "destiny-pool-dark", "SWFFG.Darkside");
+ }
+ return window.location.reload();
+ },
+ });
+
+ game.settings.register("starwarsffg", "enableForceDie", {
+ name: game.i18n.localize("SWFFG.SettingsEnableForceDie"),
+ hint: game.i18n.localize("SWFFG.SettingsEnableForceDieHint"),
+ scope: "world",
+ config: true,
+ default: true,
+ type: Boolean,
+ onChange: (rule) => {
+ if (game.settings.get("starwarsffg", "dicetheme") === "starwars") {
+ if (!rule) {
+ game.settings.set("starwarsffg", "enableForceDie", true);
+ }
+ }
+ return window.location.reload();
+ },
+ });
+ }
+
+ // Initialize System Settings after the Ready Hook
+ static readyLevelSetting() {
+ // Allow Users to Roll Audio
+ game.settings.register("starwarsffg", "allowUsersAddRollAudio", {
+ name: game.i18n.localize("SWFFG.EnableRollAudio"),
+ hint: game.i18n.localize("SWFFG.EnableRollAudioHint"),
+ scope: "world",
+ default: false,
+ config: true,
+ type: Boolean,
+ });
+
+ // generate a list of playlists
+ const playlists = {};
+ playlists["None"] = "";
+ game.playlists.entries.forEach((playlist, index) => {
+ playlists[playlist.id] = `${index}-${playlist.data.name}`;
+ });
+
+ // Playlist users can user for audio
+ game.settings.register("starwarsffg", "allowUsersAddRollAudioPlaylist", {
+ name: game.i18n.localize("SWFFG.EnableRollAudioPlaylist"),
+ hint: game.i18n.localize("SWFFG.EnableRollAudioPlaylistHint"),
+ scope: "world",
+ default: "None",
+ config: true,
+ type: String,
+ choices: playlists,
+ });
+ }
+}
diff --git a/modules/settings/ui-settings.js b/modules/settings/ui-settings.js
new file mode 100644
index 00000000..3d40c7d9
--- /dev/null
+++ b/modules/settings/ui-settings.js
@@ -0,0 +1,119 @@
+export default class UISettings extends FormApplication {
+ /** @override */
+ static get defaultOptions() {
+ return mergeObject(super.defaultOptions, {
+ id: "data-importer",
+ classes: ["starwarsffg", "data-import"],
+ title: `${game.i18n.localize("SWFFG.UISettingsLabel")}`,
+ template: "systems/starwarsffg/templates/dialogs/ffg-ui-settings.html",
+ });
+ }
+
+ getData(options) {
+ const gs = game.settings;
+ const canConfigure = game.user.can("SETTINGS_MODIFY");
+
+ const data = {
+ system: { title: game.system.data.title, menus: [], settings: [] },
+ };
+
+ // Classify all settings
+ for (let setting of gs.settings.values()) {
+ // Exclude settings the user cannot change
+ if ((!setting.config && !setting.key.includes("ui-")) || (!canConfigure && setting.scope !== "client")) continue;
+
+ // Update setting data
+ const s = duplicate(setting);
+ s.name = game.i18n.localize(s.name);
+ s.hint = game.i18n.localize(s.hint);
+ s.value = game.settings.get(s.module, s.key);
+ s.type = setting.type instanceof Function ? setting.type.name : "String";
+ s.isCheckbox = setting.type === Boolean;
+ s.isSelect = s.choices !== undefined;
+ s.isRange = setting.type === Number && s.range;
+ s.isFilePicker = setting.valueType === "FilePicker";
+
+ // Classify setting
+ const name = s.module;
+ if (name === game.system.id && s.key.includes("ui-")) data.system.settings.push(s);
+ }
+
+ // Return data
+ return {
+ user: game.user,
+ canConfigure: canConfigure,
+ systemTitle: game.system.data.title,
+ data: data,
+ };
+ }
+
+ activateListeners(html) {
+ super.activateListeners(html);
+ html.find(".submenu button").click(this._onClickSubmenu.bind(this));
+ html.find('button[name="reset"]').click(this._onResetDefaults.bind(this));
+ html.find("button.filepicker").click(this._onFilePicker.bind(this));
+ }
+
+ /**
+ * Handle activating the button to configure User Role permissions
+ * @param event {Event} The initial button click event
+ * @private
+ */
+ _onClickSubmenu(event) {
+ event.preventDefault();
+ const menu = game.settings.menus.get(event.currentTarget.dataset.key);
+ if (!menu) return ui.notifications.error("No submenu found for the provided key");
+ const app = new menu.type();
+ return app.render(true);
+ }
+
+ /* -------------------------------------------- */
+
+ /**
+ * Handle button click to reset default settings
+ * @param event {Event} The initial button click event
+ * @private
+ */
+ _onResetDefaults(event) {
+ event.preventDefault();
+ const button = event.currentTarget;
+ const form = button.form;
+ for (let [k, v] of game.settings.settings.entries()) {
+ if (v.config) {
+ let input = form[k];
+ if (input.type === "checkbox") input.checked = v.default;
+ else if (input) input.value = v.default;
+ }
+ }
+ }
+
+ /* -------------------------------------------- */
+
+ _onFilePicker(event) {
+ event.preventDefault();
+
+ const fp = new FilePicker({
+ type: "image",
+ callback: (path) => {
+ $(event.currentTarget).prev().val(path);
+ //this._onSubmit(event);
+ },
+ top: this.position.top + 40,
+ left: this.position.left + 10,
+ });
+ return fp.browse();
+ }
+
+ /* -------------------------------------------- */
+
+ /** @override */
+ async _updateObject(event, formData) {
+ for (let [k, v] of Object.entries(flattenObject(formData))) {
+ let s = game.settings.settings.get(k);
+ let current = game.settings.get(s.module, s.key);
+ if (v !== current) {
+ await game.settings.set(s.module, s.key, v);
+ }
+ }
+ }
+}
diff --git a/modules/swffg-config.js b/modules/swffg-config.js
index 9ad9c7c1..158569b0 100644
--- a/modules/swffg-config.js
+++ b/modules/swffg-config.js
@@ -1,12 +1,13 @@
import { itemstatus } from "./config/ffg-itemstatus.js";
import { personal_ranges, vehicle_ranges, sensor_ranges } from "./config/ffg-ranges.js";
-import { general_modifiers, weapon_modifiers, vehicle_modifiers } from "./config/ffg-modifiers.js";
-import { pool_results } from "./config/ffg-dice.js";
+import { general_modifiers, weapon_modifiers, vehicle_modifiers, modifier_types, itemmodifier_modifiertypes, itemmodifier_rollmodifiers, itemmodifier_resultmodifiers, itemmodifier_dicemodifiers } from "./config/ffg-modifiers.js";
+import { pool_results, configureDice } from "./config/ffg-dice.js";
import { vehicle_stats, vehicle_firingarcs } from "./config/ffg-vehicles.js";
import { character_characteristics, character_stats } from "./config/ffg-characters.js";
import { skills, skills_knowledge_stripped, skills_combat } from "./config/ffg-skills.js";
import { sheet_defaults } from "./config/ffg-sheetdefaults.js";
import { weapon_stats } from "./config/ffg-weapons.js";
+import { armor_stats } from "./config/ffg-armor.js";
import { talent_activations } from "./config/ffg-talents.js";
import { difficulty } from "./config/ffg-difficulty.js";
@@ -31,3 +32,10 @@ FFG.vehicle_ranges = vehicle_ranges;
FFG.vehicle_stats = vehicle_stats;
FFG.weapon_mod_types = weapon_modifiers;
FFG.weapon_stats = weapon_stats;
+FFG.itemmodifier_types = modifier_types;
+FFG.itemmodifier_mod_types = itemmodifier_modifiertypes;
+FFG.itemmodifier_rollmodifiers = itemmodifier_rollmodifiers;
+FFG.itemmodifier_dicemodifiers = itemmodifier_dicemodifiers;
+FFG.itemmodifier_resultmodifiers = itemmodifier_resultmodifiers;
+FFG.armor_stats = armor_stats;
+FFG.configureDice = configureDice;
diff --git a/modules/swffg-main.js b/modules/swffg-main.js
index d1a32258..416cbdc9 100644
--- a/modules/swffg-main.js
+++ b/modules/swffg-main.js
@@ -19,8 +19,7 @@ import { DicePoolFFG, RollFFG } from "./dice-pool-ffg.js";
import { GroupManagerLayer } from "./groupmanager-ffg.js";
import { GroupManager } from "./groupmanager-ffg.js";
import PopoutEditor from "./popout-editor.js";
-import DataImporter from "./importer/data-importer.js";
-import SWAImporter from "./importer/swa-importer.js";
+
import CharacterImporter from "./importer/character-importer.js";
import DiceHelpers from "./helpers/dice-helpers.js";
import Helpers from "./helpers/common.js";
@@ -28,11 +27,17 @@ import TemplateHelpers from "./helpers/partial-templates.js";
import SkillListImporter from "./importer/skills-list-importer.js";
import DestinyTracker from "./ffg-destiny-tracker.js";
import { defaultSkillArrayString } from "./config/ffg-skillslist.js";
+import SettingsHelpers from "./settings/settings-helpers.js";
// Import Dice Types
import { AbilityDie, BoostDie, ChallengeDie, DifficultyDie, ForceDie, ProficiencyDie, SetbackDie } from "./dice-pool-ffg.js";
import ImportHelpers from "./importer/import-helpers.js";
import { createFFGMacro } from "./helpers/macros.js";
+import ModifierHelpers from "./helpers/modifiers.js";
+import ItemHelpers from "./helpers/item-helpers.js";
+import EmbeddedItemHelpers from "./helpers/embeddeditem-helpers.js";
+import DataImporter from "./importer/data-importer.js";
+import PauseFFG from "./apps/pause-ffg.js";
/* -------------------------------------------- */
/* Foundry VTT Initialization */
@@ -40,7 +45,6 @@ import { createFFGMacro } from "./helpers/macros.js";
Hooks.once("init", async function () {
console.log(`Initializing SWFFG System`);
-
// Place our classes in their own namespace for later reference.
game.ffg = {
ActorFFG,
@@ -83,11 +87,13 @@ Hooks.once("init", async function () {
// TURN ON OR OFF HOOK DEBUGGING
CONFIG.debug.hooks = false;
+ CONFIG.ui.pause = PauseFFG;
+
// Override the default Token _drawBar function to allow for FFG style wound and strain values.
Token.prototype._drawBar = function (number, bar, data) {
let val = Number(data.value);
// FFG style behaviour for wounds and strain.
- if (data.attribute === "stats.wounds" || data.attribute === "stats.strain") {
+ if (data.attribute === "stats.wounds" || data.attribute === "stats.strain" || data.attribute === "stats.hullTrauma" || data.attribute === "stats.systemStrain") {
val = Number(data.max - data.value);
}
@@ -112,13 +118,20 @@ Hooks.once("init", async function () {
// Load character templates so that dynamic skills lists work correctly
loadTemplates(["systems/starwarsffg/templates/actors/ffg-character-sheet.html", "systems/starwarsffg/templates/actors/ffg-minion-sheet.html"]);
- game.settings.register("starwarsffg", "systemMigrationVersion", {
- name: "Current Version",
- scope: "world",
- default: null,
- config: false,
- type: String,
- });
+ SettingsHelpers.initLevelSettings();
+
+ const uitheme = game.settings.get("starwarsffg", "ui-uitheme");
+
+ switch (uitheme) {
+ case "mandar": {
+ $('link[href="systems/starwarsffg/styles/starwarsffg.css"]').prop("disabled", true);
+ $("head").append('');
+ break;
+ }
+ default: {
+ $('link[href="systems/starwarsffg/styles/starwarsffg.css"]').prop("disabled", false);
+ }
+ }
/**
* Set an initiative formula for the system
@@ -163,21 +176,6 @@ Hooks.once("init", async function () {
}
}
- // Register dice theme setting
- game.settings.register("starwarsffg", "dicetheme", {
- name: game.i18n.localize("SWFFG.SettingsDiceTheme"),
- hint: game.i18n.localize("SWFFG.SettingsDiceThemeHint"),
- scope: "world",
- config: true,
- default: "starwars",
- type: String,
- onChange: (rule) => window.location.reload(),
- choices: {
- starwars: "starwars",
- genesys: "genesys",
- },
- });
-
async function gameSkillsList() {
game.settings.registerMenu("starwarsffg", "addskilltheme", {
name: game.i18n.localize("SWFFG.SettingsSkillListImporter"),
@@ -269,9 +267,13 @@ Hooks.once("init", async function () {
skills.skills[`-=${skill}`] = null;
} else {
skills.skills[skill] = {
- ...skills.skills[skill],
...actor.data.data.skills[skill],
+ ...skills.skills[skill],
};
+
+ skills.skills[skill].rank = actor.data.data.skills[skill].rank;
+ skills.skills[skill].careerskill = actor.data.data.skills[skill].careerskill;
+ skills.skills[skill].groupskill = actor.data.data.skills[skill].groupskill;
}
});
@@ -289,236 +291,7 @@ Hooks.once("init", async function () {
gameSkillsList();
- game.settings.register("starwarsffg", "enableSoakCalc", {
- name: game.i18n.localize("SWFFG.EnableSoakCalc"),
- hint: game.i18n.localize("SWFFG.EnableSoakCalcHint"),
- scope: "world",
- config: true,
- default: true,
- type: Boolean,
- onChange: (rule) => window.location.reload(),
- });
-
- // Register grouping talents so people can let them be ordered by purchase history
- game.settings.register("starwarsffg", "talentSorting", {
- name: game.i18n.localize("SWFFG.EnableSortTalentsByActivationGlobal"),
- hint: game.i18n.localize("SWFFG.EnableSortTalentsByActivationHint"),
- scope: "world",
- config: true,
- default: false,
- type: Boolean,
- onChange: (rule) => window.location.reload(),
- });
-
- // Register skill sorting by localised value setting
- game.settings.register("starwarsffg", "skillSorting", {
- name: game.i18n.localize("SWFFG.SettingsSkillSorting"),
- hint: game.i18n.localize("SWFFG.SettingsSkillSortingHint"),
- scope: "world",
- config: true,
- default: false,
- type: Boolean,
- onChange: (rule) => window.location.reload(),
- });
-
- // Register setting for group manager Player Character List display mode
- game.settings.register("starwarsffg", "pcListMode", {
- name: game.i18n.localize("SWFFG.SettingsPCListMode"),
- hint: game.i18n.localize("SWFFG.SettingsPCListModeHint"),
- scope: "world",
- config: true,
- default: "active",
- type: String,
- choices: {
- active: game.i18n.localize("SWFFG.SettingsPCListModeActive"),
- owned: game.i18n.localize("SWFFG.SettingsPCListModeOwned"),
- },
- onChange: (rule) => {
- const groupmanager = canvas?.groupmanager?.window;
- if (groupmanager) {
- groupmanager.render();
- }
- },
- });
-
- // Register placeholder settings to store Destiny Pool values for the group manager.
- game.settings.register("starwarsffg", "dPoolLight", {
- name: "Destiny Pool Light",
- scope: "world",
- default: 0,
- config: false,
- type: Number,
- onChange: (rule) => {
- const groupmanager = canvas?.groupmanager?.window;
- if (groupmanager) {
- groupmanager.render();
- }
- let destinyLight = game.settings.get("starwarsffg", "dPoolLight");
- document.getElementById("destinyLight").setAttribute("data-value", destinyLight);
- document.getElementById("destinyLight").innerHTML = destinyLight + `${game.i18n.localize("SWFFG.Lightside")}`;
- },
- });
- game.settings.register("starwarsffg", "dPoolDark", {
- name: "Destiny Pool Dark",
- scope: "world",
- default: 0,
- config: false,
- type: Number,
- onChange: (rule) => {
- const groupmanager = canvas?.groupmanager?.window;
- if (groupmanager) {
- groupmanager.render();
- }
- let destinyDark = game.settings.get("starwarsffg", "dPoolDark");
- document.getElementById("destinyDark").setAttribute("data-value", destinyDark);
- document.getElementById("destinyDark").innerHTML = destinyDark + `${game.i18n.localize("SWFFG.Darkside")}`;
- },
- });
-
- // Importer Control Menu
- game.settings.registerMenu("starwarsffg", "odImporter", {
- name: game.i18n.localize("SWFFG.SettingsOggDudeImporter"),
- hint: game.i18n.localize("SWFFG.SettingsOggDudeImporterHint"),
- label: game.i18n.localize("SWFFG.SettingsOggDudeImporterLabel"),
- icon: "fas fa-file-import",
- type: DataImporter,
- restricted: true,
- });
-
- game.settings.register("starwarsffg", "odImporter", {
- name: "Item Importer",
- scope: "world",
- default: {},
- config: false,
- default: {},
- type: Object,
- });
-
- game.settings.registerMenu("starwarsffg", "swaImporter", {
- name: game.i18n.localize("SWFFG.SettingsSWAdversariesImporter"),
- hint: game.i18n.localize("SWFFG.SettingsSWAdversariesImporterHint"),
- label: game.i18n.localize("SWFFG.SettingsSWAdversariesImporterLabel"),
- icon: "fas fa-file-import",
- type: SWAImporter,
- restricted: true,
- });
-
- game.settings.register("starwarsffg", "swaImporter", {
- name: "Adversaries Importer",
- scope: "world",
- default: {},
- config: false,
- default: {},
- type: Object,
- });
-
- game.settings.register("starwarsffg", "enableDebug", {
- name: game.i18n.localize("SWFFG.EnableDebug"),
- hint: game.i18n.localize("SWFFG.EnableDebugHint"),
- scope: "world",
- config: true,
- default: false,
- type: Boolean,
- onChange: (rule) => window.location.reload(),
- });
-
- // Set up dice with dynamic dice theme
- const dicetheme = game.settings.get("starwarsffg", "dicetheme");
- CONFIG.FFG.theme = dicetheme;
-
- CONFIG.FFG.PROFICIENCY_ICON = `systems/starwarsffg/images/dice/${dicetheme}/yellow.png`;
- CONFIG.FFG.ABILITY_ICON = `systems/starwarsffg/images/dice/${dicetheme}/green.png`;
- CONFIG.FFG.CHALLENGE_ICON = `systems/starwarsffg/images/dice/${dicetheme}/red.png`;
- CONFIG.FFG.DIFFICULTY_ICON = `systems/starwarsffg/images/dice/${dicetheme}/purple.png`;
- CONFIG.FFG.BOOST_ICON = `systems/starwarsffg/images/dice/${dicetheme}/blue.png`;
- CONFIG.FFG.SETBACK_ICON = `systems/starwarsffg/images/dice/${dicetheme}/black.png`;
- CONFIG.FFG.REMOVESETBACK_ICON = `systems/starwarsffg/images/dice/${dicetheme}/black-minus.png`;
- CONFIG.FFG.FORCE_ICON = `systems/starwarsffg/images/dice/${dicetheme}/whiteHex.png`;
-
- CONFIG.FFG.ABILITY_RESULTS = {
- 1: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 2: { label: `
`, success: 1, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 3: { label: `
`, success: 1, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 4: { label: `
`, success: 2, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 5: { label: `
`, success: 0, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 6: { label: `
`, success: 0, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 7: { label: `
`, success: 1, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 8: { label: `
`, success: 0, failure: 0, advantage: 2, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- };
-
- CONFIG.FFG.BOOST_RESULTS = {
- 1: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 2: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 3: { label: `
`, success: 1, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 4: { label: `
`, success: 1, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 5: { label: `
`, success: 0, failure: 0, advantage: 2, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 6: { label: `
`, success: 0, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- };
-
- CONFIG.FFG.CHALLENGE_RESULTS = {
- 1: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 2: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 3: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 4: { label: `
`, success: 0, failure: 2, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 5: { label: `
`, success: 0, failure: 2, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 6: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
- 7: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
- 8: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
- 9: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
- 10: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 2, triumph: 0, despair: 0, light: 0, dark: 0 },
- 11: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 2, triumph: 0, despair: 0, light: 0, dark: 0 },
- 12: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 0, triumph: 0, despair: 1, light: 0, dark: 0 },
- };
-
- CONFIG.FFG.DIFFICULTY_RESULTS = {
- 1: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 2: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 3: { label: `
`, success: 0, failure: 2, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 4: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
- 5: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
- 6: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
- 7: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 2, triumph: 0, despair: 0, light: 0, dark: 0 },
- 8: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
- };
-
- CONFIG.FFG.FORCE_RESULTS = {
- 1: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 1 },
- 2: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 1 },
- 3: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 1 },
- 4: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 1 },
- 5: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 1 },
- 6: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 1 },
- 7: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 2 },
- 8: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 1, dark: 0 },
- 9: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 1, dark: 0 },
- 10: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 2, dark: 0 },
- 11: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 2, dark: 0 },
- 12: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 2, dark: 0 },
- };
-
- CONFIG.FFG.PROFICIENCY_RESULTS = {
- 1: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 2: { label: `
`, success: 1, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 3: { label: `
`, success: 1, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 4: { label: `
`, success: 2, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 5: { label: `
`, success: 2, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 6: { label: `
`, success: 0, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 7: { label: `
`, success: 1, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 8: { label: `
`, success: 1, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 9: { label: `
`, success: 1, failure: 0, advantage: 1, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 10: { label: `
`, success: 0, failure: 0, advantage: 2, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 11: { label: `
`, success: 0, failure: 0, advantage: 2, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 12: { label: `
`, success: 1, failure: 0, advantage: 0, threat: 0, triumph: 1, despair: 0, light: 0, dark: 0 },
- };
-
- CONFIG.FFG.SETBACK_RESULTS = {
- 1: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 2: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 3: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 4: { label: `
`, success: 0, failure: 1, advantage: 0, threat: 0, triumph: 0, despair: 0, light: 0, dark: 0 },
- 5: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
- 6: { label: `
`, success: 0, failure: 0, advantage: 0, threat: 1, triumph: 0, despair: 0, light: 0, dark: 0 },
- };
+ FFG.configureDice();
// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet);
@@ -674,6 +447,22 @@ Hooks.on("renderActorDirectory", (app, html, data) => {
});
});
+Hooks.on("renderCompendiumDirectory", (app, html, data) => {
+ if (game.user.isGM) {
+ const div = $(``);
+ const divider = $("
OggDude Import
");
+ const datasetImportButton = $('');
+ div.append(divider, datasetImportButton);
+
+ html.find(".directory-footer").append(div);
+
+ html.find(".og-character").click(async (event) => {
+ event.preventDefault();
+ new DataImporter().render(true);
+ });
+ }
+});
+
// Update chat messages with dice images
Hooks.on("renderChatMessage", (app, html, messageData) => {
const content = html.find(".message-content");
@@ -686,38 +475,38 @@ Hooks.on("renderChatMessage", (app, html, messageData) => {
DiceHelpers.displayRollDialog(poolData.roll.data, dicePool, poolData.description, poolData.roll.skillName, poolData.roll.item, poolData.roll.flavor, poolData.roll.sound);
});
-});
-// Handle migration duties
-Hooks.once("ready", async () => {
- game.settings.register("starwarsffg", "allowUsersAddRollAudio", {
- name: game.i18n.localize("SWFFG.EnableRollAudio"),
- hint: game.i18n.localize("SWFFG.EnableRollAudioHint"),
- scope: "world",
- default: false,
- config: true,
- type: Boolean,
- });
+ html.find(".item-display .item-pill, .item-properties .item-pill").on("click", async (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ const li = event.currentTarget;
+ let uuid = li.dataset.itemId;
+ let modifierId = li.dataset.modifierId;
+ let modifierType = li.dataset.modifierType;
+
+ if (li.dataset.uuid) {
+ uuid = li.dataset.uuid;
+ }
- const playlists = {};
- playlists["None"] = "";
- game.playlists.entries.forEach((playlist, index) => {
- playlists[playlist.id] = `${index}-${playlist.data.name}`;
- });
+ const parts = uuid.split(".");
- game.settings.register("starwarsffg", "allowUsersAddRollAudioPlaylist", {
- name: game.i18n.localize("SWFFG.EnableRollAudioPlaylist"),
- hint: game.i18n.localize("SWFFG.EnableRollAudioPlaylistHint"),
- scope: "world",
- default: "None",
- config: true,
- type: String,
- choices: playlists,
+ const [entityName, entityId, embeddedName, embeddedId] = parts;
+
+ await EmbeddedItemHelpers.displayOwnedItemItemModifiersAsJournal(embeddedId, modifierType, modifierId, entityId);
});
+});
+
+// Handle migration duties
+Hooks.once("ready", async () => {
+ SettingsHelpers.readyLevelSetting();
const currentVersion = game.settings.get("starwarsffg", "systemMigrationVersion");
- if ((currentVersion === "null" || parseFloat(currentVersion) < parseFloat(game.system.data.version)) && game.user.isGM) {
+ const pattern = /([1-9].[1-9])/gim;
+ const version = game.system.data.version.match(pattern);
+ const isAlpha = game.system.data.version.includes("alpha");
+
+ if ((isAlpha || currentVersion === "null" || parseFloat(currentVersion) < parseFloat(game.system.data.version)) && game.user.isGM) {
CONFIG.logger.log(`Migrating to from ${currentVersion} to ${game.system.data.version}`);
// Calculating wound and strain .value from .real_value is no longer necessary due to the Token._drawBar() override in swffg-main.js
@@ -736,6 +525,35 @@ Hooks.once("ready", async () => {
CONFIG.logger.log("Migrated stats.strain.value from stats.strain.real_value");
CONFIG.logger.log(actor.data.data.stats.strain);
}
+
+ // migrate all character to using current skill list if not default.
+ let skilllist = game.settings.get("starwarsffg", "skilltheme");
+
+ if (CONFIG.FFG?.alternateskilllists?.length) {
+ try {
+ let skills = JSON.parse(JSON.stringify(CONFIG.FFG.alternateskilllists.find((list) => list.id === skilllist)));
+ CONFIG.logger.log(`Applying skill theme ${skilllist} to actor ${actor.name}`);
+
+ Object.keys(actor.data.data.skills).forEach((skill) => {
+ if (!skills.skills[skill] && !actor.data.data.skills?.[skill]?.nontheme) {
+ skills.skills[`-=${skill}`] = null;
+ } else {
+ skills.skills[skill] = {
+ ...skills.skills[skill],
+ ...actor.data.data.skills[skill],
+ };
+ }
+ });
+
+ actor.update({
+ data: {
+ skills: skills.skills,
+ },
+ });
+ } catch (err) {
+ CONFIG.logger.warn(err);
+ }
+ }
}
});
@@ -815,8 +633,8 @@ Hooks.once("ready", async () => {
},
});
}
- resolve();
}
+ resolve();
if (isLocked) {
await pack.configure({ locked: true });
@@ -828,12 +646,13 @@ Hooks.once("ready", async () => {
Promise.all(pro)
.then(() => {
ui.notifications.info(`Starwars FFG System Migration to version ${game.system.data.version} completed!`, { permanent: true });
- game.settings.set("starwarsffg", "systemMigrationVersion", game.system.data.version);
})
.catch((err) => {
CONFIG.logger.error(`Error during system migration`, err);
});
}
+
+ game.settings.set("starwarsffg", "systemMigrationVersion", version);
}
// enable functional testing
@@ -868,18 +687,37 @@ Hooks.once("ready", async () => {
let destinyPool = { light: game.settings.get("starwarsffg", "dPoolLight"), dark: game.settings.get("starwarsffg", "dPoolDark") };
// future functionality to allow multiple menu items to be passed to destiny pool
- // const defaultDestinyMenu = [
- // {
- // name: game.i18n.localize("SWFFG.GroupManager"),
- // icon: '',
- // callback: () => {
- // new GroupManager().render(true);
- // }
- // }
- // ]
- // const dTracker = new DestinyTracker({ menu: defaultDestinyMenu});
-
- const dTracker = new DestinyTracker();
+ const defaultDestinyMenu = [
+ {
+ name: game.i18n.localize("SWFFG.GroupManager"),
+ icon: '',
+ callback: () => {
+ new GroupManager().render(true);
+ },
+ minimumRole: USER_ROLES.GAMEMASTER,
+ },
+ {
+ name: game.i18n.localize("SWFFG.RequestDestinyRoll"),
+ icon: '',
+ callback: (li) => {
+ const messageText = ``;
+
+ new Map([...game.settings.settings].filter(([k, v]) => v.key.includes("destinyrollers"))).forEach((i) => {
+ game.settings.set(i.module, i.key, undefined);
+ });
+
+ CONFIG.FFG.DestinyGM = game.user.id;
+
+ ChatMessage.create({
+ user: game.user._id,
+ content: messageText,
+ });
+ },
+ minimumRole: USER_ROLES.GAMEMASTER,
+ },
+ ];
+ const dTracker = new DestinyTracker({ menu: defaultDestinyMenu });
+
dTracker.render(true);
});
@@ -1104,3 +942,12 @@ Hooks.once("diceSoNiceReady", (dice3d) => {
background: "#ffffff",
});
});
+
+Hooks.on("pauseGame", () => {
+ if (game.data.paused) {
+ const pausedImage = game.settings.get("starwarsffg", "ui-pausedImage");
+ if (pausedImage) {
+ $("#pause img").css("content", `url(${pausedImage})`);
+ }
+ }
+});
diff --git a/scss/components/_charactersheet.scss b/scss/components/_charactersheet.scss
index aa9ba462..5227cd50 100644
--- a/scss/components/_charactersheet.scss
+++ b/scss/components/_charactersheet.scss
@@ -192,7 +192,6 @@ $swffg-table-header-color: rgb(145, 145, 145);
overflow: auto;
width:100%;
height:100%;
- max-height: 3.5rem;
}
}
}
diff --git a/scss/components/_container_base.scss b/scss/components/_container_base.scss
index f2d4bc81..492340e8 100644
--- a/scss/components/_container_base.scss
+++ b/scss/components/_container_base.scss
@@ -12,6 +12,7 @@
border: 0;
margin-bottom: 5px;
padding: 0 5px 5px 5px;
+ align-items: flex-start;
}
}
diff --git a/scss/components/_destiny_ui.scss b/scss/components/_destiny_ui.scss
index 33c91fac..dcede372 100644
--- a/scss/components/_destiny_ui.scss
+++ b/scss/components/_destiny_ui.scss
@@ -11,59 +11,67 @@
display: none;
}
-/* Dropdown Button */
-.dropbtn {
- background-color: transparent;
- color: white;
- font-size: 12px;
- border: none;
- cursor: pointer;
+ /* Dropdown Button */
+ .dropbtn {
+ background-color: transparent;
+ color: white;
+ font-size: 12px;
+ border: none;
+ cursor: pointer;
- i {
- margin: 0;
+ i {
+ margin: 0;
+ }
}
-}
-/* Dropdown button on hover & focus */
-.dropbtn:hover, .dropbtn:focus {
- background-color: black;
-}
+ /* Dropdown button on hover & focus */
+ .dropbtn:hover, .dropbtn:focus {
+ background-color: black;
+ }
-/* The container - needed to position the dropdown content */
-.dropdown {
- position: relative;
- display: inline-block;
-}
+ /* The container
- needed to position the dropdown content */
+ .dropdown {
+ position: relative;
+ display: inline-block;
+ }
-/* Dropdown Content (Hidden by Default) */
-.dropdown-content {
- display: none;
- position: absolute;
- background: url(../../../ui/denim075.png) repeat;
- max-width: 50px;
- box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
- z-index: 1;
-}
+ /* Dropdown Content (Hidden by Default) */
+ .dropdown-content {
+ display: none;
+ position: absolute;
+ background: url(../../../ui/denim075.png) repeat;
+ max-width: 50px;
+ box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
+ z-index: 1;
+
+ &.vertical {
+ margin-top : calc(-13vh + 40px);
+ }
+ }
-/* Links inside the dropdown */
-.dropdown-content a {
- color: white;
- padding: 12px 5px;
- text-decoration: none;
- display: block;
-}
+ /* Links inside the dropdown */
+ .dropdown-content a {
+ color: white;
+ padding: 5px 4px;
+ text-decoration: none;
+ display: block;
+ font-size: 15px;
+ }
-/* Change color of dropdown links on hover */
-.dropdown-content a:hover {background-color: #ddd}
+ /* Change color of dropdown links on hover */
+ // .dropdown-content a:hover {background-color: #ddd}
-/* Show the dropdown menu on hover */
-.dropdown:hover .dropdown-content {display: block;}
+ /* Show the dropdown menu on hover */
+ // .dropdown:hover .dropdown-content {display: block;}
-/* Change the background color of the dropdown button when the dropdown content is shown */
-.dropdown:hover .dropbtn {background-color: #3e8e41;}
+ /* Change the background color of the dropdown button when the dropdown content is shown */
+ .dropdown:hover .dropbtn {background-color: #3e8e41;}
-/* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */
-.show {display:block;}
+ /* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */
+ .show {
+ display:block;
+ background-color: #3e8e41;
+ }
}
.swffg-destiny-container {
@@ -126,6 +134,7 @@
position: relative;
padding: 6px 0 12px 0;
pointer-events: auto;
+ cursor: pointer;
span {
bottom: 2px;
font-family: "mason-serif", "Nodesto", "Signika", "Palatino Linotype", serif;
diff --git a/scss/components/_dicepool.scss b/scss/components/_dicepool.scss
index 90d4f2e0..118a1ad0 100644
--- a/scss/components/_dicepool.scss
+++ b/scss/components/_dicepool.scss
@@ -105,5 +105,38 @@
font-style: italic;
color: darkred;
}
+
+ .item-pill {
+ list-style-type: none;
+ padding: 0 5px;
+ background-color: #aaa;
+ border: 1px solid black;
+ border-radius: 12px;
+ display: inline-block;
+ margin: 1px;
+ cursor: pointer;
+ font-size: 12px;
+ line-height: 20px;
+ white-space: nowrap;
+
+ &.adjusted {
+ color: #8b0000;
+ }
+
+ .rank {
+ padding-right: 15px;
+ &:hover {
+ color: green;
+ }
+ }
+
+ .item-delete {
+ cursor: pointer;
+ &:hover {
+ color: red;
+ }
+ }
+ }
+
}
diff --git a/scss/components/_editor.scss b/scss/components/_editor.scss
index 2cf7f625..67fdacd5 100644
--- a/scss/components/_editor.scss
+++ b/scss/components/_editor.scss
@@ -100,7 +100,7 @@ span.dietype {
}
&.restricted {
- color: red;
+ color: #8b0000;;
&::after {
content : "-";
@@ -200,7 +200,7 @@ span {
position: relative;
p {
- line-height: 1.25rem;
+ line-height: .875rem;
}
.popout-editor-button {
diff --git a/scss/components/_itemattachment.scss b/scss/components/_itemattachment.scss
new file mode 100644
index 00000000..bbfdbdcc
--- /dev/null
+++ b/scss/components/_itemattachment.scss
@@ -0,0 +1,61 @@
+.item-sheet-itemattachment {
+ .profile-img-field {
+ width: 25%;
+
+ img {
+
+ &.profile-img {
+ width: 100%;
+ height: auto;
+ }
+ }
+ }
+
+ .sheet-body {
+ height: calc(100% - 19rem);
+
+ table {
+ th:first-child {
+ text-align: left;
+ padding-left: 3px;
+ }
+
+ td {
+ &:not(:first-child) {
+ position: relative;
+ text-align: center;
+
+ .quantity {
+ top: 3px;
+ }
+ }
+
+ .modifier-active {
+ i {
+ cursor: pointer;
+ }
+ }
+
+ &.add-modifier {
+ i {
+ padding: 0 2px;
+ cursor: pointer;
+ }
+ }
+ }
+
+ }
+ }
+}
+
+&.sheet {
+ &.item {
+ &.v2 {
+ .item-sheet-itemattachment {
+ .sheet-body {
+ height: calc(100% - 16.5rem);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/scss/components/_itemmodifier.scss b/scss/components/_itemmodifier.scss
new file mode 100644
index 00000000..3f2e77fc
--- /dev/null
+++ b/scss/components/_itemmodifier.scss
@@ -0,0 +1,53 @@
+.item-sheet-modifiers {
+ $swffg-modifiers-color: rgb(44, 3, 68);
+ $swffg-modifiers-color-light: rgb(233, 198, 253);
+
+ .charname {
+ input {
+ color: $swffg-modifiers-color
+ }
+ }
+
+ .characteristic-item {
+ .characteristic-label {
+ background-color: $swffg-modifiers-color
+ }
+ }
+
+ .block-background {
+ background-color: $swffg-modifiers-color-light;
+ }
+
+ .attribute {
+ .block-title {
+ background-color: $swffg-modifiers-color;
+ }
+ }
+
+ .sheet-body {
+ height: calc(100% - 12.7rem);
+ overflow:auto;
+ }
+}
+
+&.sheet {
+ &.item {
+ &.v2 {
+ .item-sheet-modifiers {
+ .container.flex-group-center {
+ padding: 0 5px 0 5px;
+ margin-bottom: 0;
+ }
+
+ .weapon-values {
+ margin: 0 5px;
+ padding: 0 5px 0 5px;
+ }
+
+ .sheet-body {
+ height: calc(100% - 8.6rem);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/scss/components/_items.scss b/scss/components/_items.scss
index 2b0cdbd0..ba92c67d 100644
--- a/scss/components/_items.scss
+++ b/scss/components/_items.scss
@@ -102,6 +102,10 @@
background: rgba(0, 0, 0, 0.1);
}
}
+
+ .restricted {
+ color: #8b0000;
+ }
}
.item-rollable {
@@ -122,3 +126,26 @@
}
}
+&.item-card {
+ .item-details {
+ .item-properties {
+ .item-pill {
+ list-style-type: none;
+ padding: 0 5px;
+ background-color: #aaa;
+ border: 1px solid black;
+ border-radius: 12px;
+ display: inline-block;
+ margin: 1px;
+ cursor: pointer;
+ font-size: 12px;
+ line-height: 20px;
+ white-space: nowrap;
+
+ &.adjusted {
+ color: #8b0000;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/scss/components/_itemsheet_base.scss b/scss/components/_itemsheet_base.scss
index a24be487..87910852 100644
--- a/scss/components/_itemsheet_base.scss
+++ b/scss/components/_itemsheet_base.scss
@@ -15,6 +15,21 @@
line-height: 15px;
}
}
+
+ .characteristic {
+ .characteristic-value {
+ &.restricted {
+ &::after {
+ content: 'R';
+ position: absolute;
+ font-size: 15px;
+ left: 1px;
+ top: -1px;
+ color: #8b0000;;
+ }
+ }
+ }
+ }
}
.window-content {
@@ -25,6 +40,53 @@
margin: 10px 5px;
overflow: auto;
}
+
+ .item-pill-list {
+ padding: 0;
+ &:after {
+ content:".";
+ display:block;
+ height:0;
+ clear:both;
+ visibility:hidden;
+ }
+ }
+
+ .item-pill {
+ list-style-type: none;
+ padding: 0 5px;
+ background-color: #aaa;
+ border: 1px solid black;
+ border-radius: 12px;
+ float: left;
+ margin: 1px;
+ cursor: pointer;
+ font-size: 12px;
+ line-height: 20px;
+ white-space: nowrap;
+
+ &.adjusted {
+ color: #8b0000;
+ }
+
+ .rank {
+ padding-right: 15px;
+ &:hover {
+ color: green;
+ }
+ }
+
+ .item-delete {
+ cursor: pointer;
+ &:hover {
+ color: red;
+ }
+ }
+ }
+
+ .add-new-item {
+ cursor: pointer;
+ }
}
&.sheet {
diff --git a/scss/components/_itemweaponsheet.scss b/scss/components/_itemweaponsheet.scss
index 3bcc46cf..b3eba6f9 100644
--- a/scss/components/_itemweaponsheet.scss
+++ b/scss/components/_itemweaponsheet.scss
@@ -15,9 +15,24 @@
.characteristic {
border: 5px double $swffg-weapon-color;
+
+ .adjustedvalues-left {
+ position: absolute;
+ top: 0;
+ right: 2px;
+ color: red;
+ &.positive {
+ color: green;
+ }
+ }
}
}
+ .adjustedvalue {
+ color: red;
+
+ }
+
.block-background {
background-color: $swffg-weapon-color-light;
}
@@ -29,9 +44,24 @@
}
.sheet-body {
- height: calc(100% - 31.875rem);
+ height: calc(100% - 27.5rem);
}
+ .pills {
+ &.itemmodifier, &.itemattachment {
+ width: auto;
+ max-width: 50%;
+ width: 50%;
+
+ .item-pill-list {
+ max-height: 75px;
+ height: 75px;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ }
+ }
+ }
+
}
&.sheet {
diff --git a/scss/components/_sheet_base.scss b/scss/components/_sheet_base.scss
index 19474b4d..cc899cf3 100644
--- a/scss/components/_sheet_base.scss
+++ b/scss/components/_sheet_base.scss
@@ -141,7 +141,6 @@
&.block-single {
input {
width: 100%;
- padding: 6px;
}
}
diff --git a/scss/components/_talent_blocks.scss b/scss/components/_talent_blocks.scss
index 98c5b35c..8f77b383 100644
--- a/scss/components/_talent_blocks.scss
+++ b/scss/components/_talent_blocks.scss
@@ -35,7 +35,7 @@
top: 15px;
font-size: 15px;
color: white;
-
+ z-index: 20;
i {
transform: rotate(45deg);
}
@@ -213,7 +213,7 @@
.talent-connection-point-right {
position: absolute;
- right: -5px;
+ right: -15px;
top: 50%;
transform: translateY(-50%);
@@ -230,7 +230,7 @@
.talent-connector-up,
.talent-connector-side {
display: inline-block;
- width: 20px;
+ width: 34px;
height: 12px;
background-color: black;
position: absolute;
@@ -250,14 +250,16 @@
i {
position: absolute;
top: 50%;
- left: 3px;
+ left: 0;
+ width: 34px;
}
}
.talent-connector-side {
- right: -15px;
- width: 20px;
- height: 20px;
+ width: 35px;
+ height: 35px;
+ left: -15px;
+ padding-top: 8px;
}
}
diff --git a/scss/global/_window.scss b/scss/global/_window.scss
index 736417de..65bc02f1 100644
--- a/scss/global/_window.scss
+++ b/scss/global/_window.scss
@@ -161,8 +161,15 @@ span.dietype {
}
}
-.paused {
- img {
- content: url("/systems/starwarsffg/images/paused.png");
- }
-}
\ No newline at end of file
+.clearfix:after {
+ content:".";
+ display:block;
+ height:0;
+ clear:both;
+ visibility:hidden;
+}
+// .paused {
+// img {
+// content: url("/systems/starwarsffg/images/paused.png");
+// }
+// }
\ No newline at end of file
diff --git a/scss/starwarsffg.scss b/scss/starwarsffg.scss
index ee6fdabf..e9988e8b 100644
--- a/scss/starwarsffg.scss
+++ b/scss/starwarsffg.scss
@@ -45,7 +45,8 @@
@import "components/career";
@import "components/modifiers";
@import "components/signatureability";
-
+ @import "components/itemmodifier";
+ @import "components/itemattachment";
}
@import "components/initiative_dialog";
diff --git a/styles/mandar.css b/styles/mandar.css
new file mode 100644
index 00000000..493ce778
--- /dev/null
+++ b/styles/mandar.css
@@ -0,0 +1,3795 @@
+@font-face {
+ font-family: "Roboto";
+ src: url("../fonts/Roboto-Regular.ttf") format("truetype");
+}
+
+@font-face {
+ font-family: "StarJedi";
+ src: url("../fonts/StarJedi-Regular.ttf") format("truetype");
+}
+
+@font-face {
+ font-family: "Signika";
+ src: url("../fonts/Signika-Regular.ttf") format("truetype");
+}
+
+@font-face {
+ font-family: "EotESymbol";
+ src: url("../fonts/EotESymbol-Regular-PLUS.otf") format("opentype");
+}
+
+@font-face {
+ font-family: "SWRPG-Symbol-Regular";
+ src: url("../fonts/swrpg_symbol-regular_v1-webfont.woff2") format("woff2"), url("../fonts/swrpg_symbol-regular_v1-webfont.woff") format("woff");
+ font-weight: normal;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "Genesys";
+ src: url("../fonts/genesysglyphsanddice.woff2") format("woff2"), url("../fonts/genesysglyphsanddice.woff") format("woff");
+ font-weight: normal;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: "GenesysSymbol";
+ src: url("../fonts/Genesys.ttf") format("truetype");
+}
+
+/* Global styles */
+div,
+section {
+ scrollbar-width: thin;
+}
+
+.window-app {
+ font-family: "Roboto", sans-serif;
+}
+
+.window-app .window-content {
+ flex: 1;
+ height: 100%;
+ font-size: 10pt;
+ padding: 0.25rem;
+ background: #d8cbc0 url(../images/bgWindow.jpg) no-repeat;
+}
+
+.window-app .window-content form {
+ height: 100%;
+}
+
+.window-app.sheet.actor .window-content {
+ background: #d8cbc0 url(../images/bgSheet.jpg) no-repeat;
+}
+
+.window-app .window-content::after {
+ content: "";
+ display: block;
+ height: 0;
+ width: 0;
+ clear: both;
+ position: relative;
+}
+
+.window-app .window-resizable-handle {
+ z-index: 99999;
+}
+
+.sidebar-tab {
+ height: 100%;
+}
+
+.hidden-content {
+ display: none;
+}
+
+.rollable:hover, .rollable:focus {
+ color: #000;
+ text-shadow: 0 0 10px red;
+ cursor: pointer;
+}
+
+input[type="checkbox"] {
+ height: auto;
+ margin: 0;
+ padding: 0;
+ line-height: 1rem;
+}
+
+#chat-log .message {
+ background: #eeeeee;
+}
+
+#chat-form textarea {
+ background: whitesmoke;
+}
+
+span.dietype.starwars {
+ font-family: "EotESymbol", sans-serif;
+}
+
+span.dietype.genesys {
+ font-family: "GenesysSymbol", sans-serif;
+}
+
+span.dietype.ability {
+ color: green;
+}
+
+span.dietype.advantage {
+ color: black;
+}
+
+span.dietype.boost {
+ color: lightskyblue;
+}
+
+span.dietype.challenge {
+ color: red;
+}
+
+span.dietype.dark {
+ color: black;
+}
+
+span.dietype.despair {
+ color: black;
+}
+
+span.dietype.difficulty {
+ color: purple;
+}
+
+span.dietype.forcepoint {
+ color: black;
+}
+
+span.dietype.failure {
+ color: black;
+}
+
+span.dietype.force {
+ color: black;
+}
+
+span.dietype.light {
+ color: black;
+}
+
+span.dietype.proficiency {
+ color: yellow;
+}
+
+span.dietype.setback {
+ color: black;
+}
+
+span.dietype.success {
+ color: black;
+}
+
+span.dietype.threat {
+ color: black;
+}
+
+span.dietype.triumph {
+ color: black;
+}
+
+.clearfix:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+
+.grid,
+.grid-2col {
+ display: grid;
+ grid-column: span 2 / span 2;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 0.5rem;
+ margin: 0;
+ padding: 0;
+}
+
+.grid-3col {
+ grid-column: span 3 / span 3;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+}
+
+.grid-4col {
+ grid-column: span 4 / span 4;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+}
+
+.grid-5col {
+ grid-column: span 5 / span 5;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+}
+
+.grid-6col {
+ grid-column: span 6 / span 6;
+ grid-template-columns: repeat(6, minmax(0, 1fr));
+}
+
+.grid-7col {
+ grid-column: span 7 / span 7;
+ grid-template-columns: repeat(7, minmax(0, 1fr));
+}
+
+.grid-8col {
+ grid-column: span 8 / span 8;
+ grid-template-columns: repeat(8, minmax(0, 1fr));
+}
+
+.grid-9col {
+ grid-column: span 9 / span 9;
+ grid-template-columns: repeat(9, minmax(0, 1fr));
+}
+
+.grid-10col {
+ grid-column: span 10 / span 10;
+ grid-template-columns: repeat(10, minmax(0, 1fr));
+}
+
+.grid-11col {
+ grid-column: span 11 / span 11;
+ grid-template-columns: repeat(11, minmax(0, 1fr));
+}
+
+.grid-12col {
+ grid-column: span 12 / span 12;
+ grid-template-columns: repeat(12, minmax(0, 1fr));
+}
+
+.flex-group-center,
+.flex-group-left,
+.flex-group-right {
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ padding: 0.25rem;
+ border: 1px solid #999;
+}
+
+.flex-group-left {
+ justify-content: flex-start;
+ text-align: left;
+}
+
+.flex-group-right {
+ justify-content: flex-end;
+ text-align: right;
+}
+
+.flex-center {
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+}
+
+.flex-between {
+ justify-content: space-between;
+}
+
+img {
+ border: 0;
+}
+
+.starwarsffg .item-form {
+ font-family: "Roboto", sans-serif;
+}
+
+.starwarsffg .block-background {
+ background-color: #eeeeee;
+ padding: 5px;
+ --notchSize: 0.5rem;
+ -webkit-clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg .character {
+ min-width: 575px;
+ flex: auto;
+}
+
+.starwarsffg .position-relative {
+ position: relative;
+}
+
+.starwarsffg .resource-label {
+ font-weight: bold;
+ text-transform: uppercase;
+}
+
+.starwarsffg .resource {
+ flex: 25%;
+ padding: 0;
+ margin: 0.25rem;
+}
+
+.starwarsffg .resource input {
+ width: 100%;
+ height: 1.5rem;
+}
+
+.starwarsffg.sheet.actor {
+ min-width: 600px;
+ min-height: 525px;
+}
+
+.starwarsffg.sheet.actor.adversary {
+ min-height: 100px;
+}
+
+.starwarsffg.sheet.actor .characteristic-item .characteristic-label {
+ background-color: #490c0b;
+}
+
+.starwarsffg.sheet.actor .character .header-fields {
+ flex: 1;
+ height: auto;
+}
+
+.starwarsffg.sheet.actor .character .header-fields h1 {
+ margin: 0;
+ width: 100%;
+}
+
+.starwarsffg.sheet.actor .character .header-fields .header-name {
+ margin: 0;
+ position: relative;
+ width: 100%;
+}
+
+.starwarsffg.sheet.actor .character .header-fields .header-name input {
+ margin: 0;
+ width: 100%;
+ border: 0;
+ background-color: rgba(0, 0, 0, 0);
+}
+
+.starwarsffg.sheet.actor .character .header-fields .header-name input:focus {
+ box-shadow: 0 0 5px #7da9c2;
+ background-color: #7da9c2;
+}
+
+.starwarsffg.sheet.actor .character .header-fields .row {
+ display: table-row;
+ width: 100%;
+}
+
+.starwarsffg.sheet.actor .character .header-fields .row div {
+ display: table-cell;
+}
+
+.starwarsffg.sheet.actor .character .header-fields .row .row-input .specialization-pill, .starwarsffg.sheet.actor .character .header-fields .row .row-input .species-pill, .starwarsffg.sheet.actor .character .header-fields .row .row-input .career-pill {
+ padding: 0.1rem 0.5rem;
+ background-color: rgba(255, 255, 255, 0.25);
+ border: 1px solid rgba(255, 255, 255, 0.5);
+ border-radius: 1rem;
+ cursor: pointer;
+}
+
+.starwarsffg.sheet.actor .character .header-fields .row .row-input .specialization-pill .item-delete, .starwarsffg.sheet.actor .character .header-fields .row .row-input .species-pill .item-delete, .starwarsffg.sheet.actor .character .header-fields .row .row-input .career-pill .item-delete {
+ cursor: pointer;
+}
+
+.starwarsffg.sheet.actor .character .header-fields .row input {
+ width: 100%;
+ border: 0;
+ border-bottom: 1px solid black;
+ border-radius: 0;
+}
+
+.starwarsffg.sheet.actor .character .header-fields .row input:focus {
+ box-shadow: 0 0 5px #7da9c2;
+ background-color: #7da9c2;
+}
+
+.starwarsffg.sheet.actor .character .header-fields .profile-block {
+ margin-right: 5px;
+ margin-left: 5px;
+ height: 130px;
+ max-width: 130px;
+}
+
+.starwarsffg.sheet.actor .character .header-fields .profile-img {
+ flex: 0 0 100px;
+ height: 124px;
+ width: auto;
+ max-width: 124px;
+ border: 0;
+ margin: auto;
+ -o-object-fit: cover;
+ object-fit: cover;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body {
+ overflow: hidden;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .general .grid, .starwarsffg.sheet.actor .character .sheet-body .obligation .grid {
+ margin: 0;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .general .resource.full table thead, .starwarsffg.sheet.actor .character .sheet-body .obligation .resource.full table thead {
+ background: #919191;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .general .resource.full table .motivation.description, .starwarsffg.sheet.actor .character .sheet-body .obligation .resource.full table .motivation.description {
+ width: 66%;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .general .resource.full table input, .starwarsffg.sheet.actor .character .sheet-body .obligation .resource.full table input {
+ width: 100%;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .general .genesys.grid, .starwarsffg.sheet.actor .character .sheet-body .obligation .genesys.grid {
+ gap: 0px;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .general .genesys .resource, .starwarsffg.sheet.actor .character .sheet-body .obligation .genesys .resource {
+ min-height: 6rem;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .general .genesys .resource .attribute, .starwarsffg.sheet.actor .character .sheet-body .obligation .genesys .resource .attribute {
+ height: 100%;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .general .genesys .resource .attribute .block-background, .starwarsffg.sheet.actor .character .sheet-body .obligation .genesys .resource .attribute .block-background {
+ height: 100%;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .general .genesys .resource .attribute .block-background .block-attribute, .starwarsffg.sheet.actor .character .sheet-body .obligation .genesys .resource .attribute .block-background .block-attribute {
+ height: calc(100% - 0.9375rem);
+ overflow: hidden;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .general .genesys .resource .attribute .block-background .block-attribute .block-value, .starwarsffg.sheet.actor .character .sheet-body .obligation .genesys .resource .attribute .block-background .block-attribute .block-value {
+ overflow: auto;
+ width: 100%;
+ height: 100%;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .general .block-editor .resource, .starwarsffg.sheet.actor .character .sheet-body .obligation .block-editor .resource {
+ min-height: 4.6875rem;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .general .block-editor .resource .attribute, .starwarsffg.sheet.actor .character .sheet-body .obligation .block-editor .resource .attribute {
+ height: 100%;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .general .block-editor .resource .attribute .block-background, .starwarsffg.sheet.actor .character .sheet-body .obligation .block-editor .resource .attribute .block-background {
+ height: 100%;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .general .block-editor .resource .attribute .block-background .block-attribute, .starwarsffg.sheet.actor .character .sheet-body .obligation .block-editor .resource .attribute .block-background .block-attribute {
+ height: calc(100% - 0.9375rem);
+ overflow: hidden;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .general .block-editor .resource .attribute .block-background .block-attribute .block-value, .starwarsffg.sheet.actor .character .sheet-body .obligation .block-editor .resource .attribute .block-background .block-attribute .block-value {
+ overflow: auto;
+ width: 100%;
+ height: 100%;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .obligation .resource.full table {
+ width: 97.4%;
+ margin: 0 0 0 0.5rem;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .obligation .block-editor .resource {
+ min-height: 4.6875rem;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body .obligation .block-editor .popout-editor {
+ min-height: 4.6875rem;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body table.force-powers {
+ border: 0;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body table.force-powers th {
+ text-align: left;
+ padding: 3px;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.25);
+ background-color: #490c0b;
+ color: white;
+ font-size: 12px;
+ font-weight: normal;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body table.force-powers td {
+ height: 30px;
+ line-height: 24px;
+ padding: 3px;
+ position: relative;
+}
+
+.starwarsffg.sheet.actor .character .sheet-body table.force-powers td .item-controls {
+ text-align: right;
+ width: auto;
+ z-index: auto;
+}
+
+.starwarsffg .container {
+ width: 100%;
+ height: auto;
+ display: flex;
+ flex-wrap: wrap;
+ font-size: 11pt;
+}
+
+.starwarsffg .container.flex-group-center {
+ border: 0;
+ padding: 0;
+ align-items: flex-start;
+}
+
+.starwarsffg.sheet.item .tab[data-tab].active {
+ height: 100%;
+}
+
+.starwarsffg.sheet.item .characteristic .characteristic-value.restricted::after {
+ content: 'R';
+ position: absolute;
+ font-size: 15px;
+ left: 1px;
+ top: -1px;
+ color: #8b0000;
+}
+
+.starwarsffg.sheet .window-content {
+ overflow-y: hidden;
+}
+
+.starwarsffg.sheet .sheet-body {
+ overflow: auto;
+}
+
+.starwarsffg.sheet .item-pill-list {
+ padding: 0;
+}
+
+.starwarsffg.sheet .item-pill-list:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+
+.starwarsffg.sheet .item-pill {
+ list-style-type: none;
+ display: inline-block;
+ cursor: pointer;
+ white-space: nowrap;
+ padding: 0.1rem 0.5rem;
+ margin: 0.1rem;
+ background-color: rgba(255, 255, 255, 0.25);
+ border: 1px solid rgba(255, 255, 255, 0.5);
+ border-radius: 1rem;
+}
+
+.starwarsffg.sheet .item-pill.adjusted {
+ color: #8b0000;
+}
+
+.starwarsffg.sheet .item-pill .rank {
+ padding-right: 15px;
+}
+
+.starwarsffg.sheet .item-pill .rank:hover {
+ color: green;
+}
+
+.starwarsffg.sheet .item-pill .item-delete {
+ cursor: pointer;
+}
+
+.starwarsffg.sheet .item-pill .item-delete:hover {
+ color: red;
+}
+
+.starwarsffg.sheet .add-new-item {
+ cursor: pointer;
+}
+
+.starwarsffg .charname {
+ width: 100%;
+}
+
+.starwarsffg .charname input {
+ color: black;
+ border: 0;
+}
+
+.starwarsffg .charname input:focus {
+ box-shadow: 0 0 5px #7da9c2;
+ background-color: #7da9c2;
+}
+
+.starwarsffg.sheet .sheet-body {
+ height: calc(100% - 15.75rem);
+ min-height: 100px;
+ display: flex;
+ flex-direction: column;
+ overflow: auto;
+ margin: 0 0.25rem;
+ padding: 0.5rem;
+ background: rgba(255, 255, 255, 0.25);
+ border-top: 1px solid #878787;
+ -o-border-image: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(73, 12, 11, 0.85), rgba(0, 0, 0, 0)) 5%;
+ border-image: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(73, 12, 11, 0.85), rgba(0, 0, 0, 0)) 5%;
+}
+
+.starwarsffg.sheet .sheet-body .tab {
+ height: 100%;
+ padding: 0;
+}
+
+.starwarsffg.sheet .sheet-body .tab.characteristics {
+ overflow: hidden;
+}
+
+.starwarsffg.sheet .sheet-body .tab.characteristics > div.grid {
+ padding: 0 0.6rem 0.5rem 0;
+}
+
+.starwarsffg.sheet .sheet-body .editor {
+ width: 100%;
+ height: calc(100% - 1.5rem);
+}
+
+.starwarsffg.sheet.v2 .sheet-body {
+ height: calc(100% - 14rem);
+}
+
+.starwarsffg.sheet.v2.minion .sheet-body {
+ height: calc(100% - 13rem);
+}
+
+.starwarsffg.sheet .sheet-header {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: flex-start;
+ flex: 0 0 100px;
+}
+
+.starwarsffg.sheet .tabs .item.active {
+ text-decoration: none;
+ text-shadow: none;
+}
+
+.starwarsffg.sheet div.container {
+ width: 100%;
+ height: auto;
+ display: flex;
+ flex-wrap: wrap;
+ font-size: 11pt;
+}
+
+.starwarsffg.sheet div.container .attributes {
+ margin-top: 0.25rem;
+}
+
+.starwarsffg.sheet .character {
+ height: 100%;
+ overflow: hidden;
+}
+
+.starwarsffg.sheet .character .resource {
+ margin: 0.5rem 0.25rem 1.25rem;
+}
+
+.starwarsffg.sheet .character .tab .resource {
+ margin-top: 0;
+}
+
+.starwarsffg.sheet select {
+ width: 100%;
+}
+
+.starwarsffg.sheet header.sheet-header h1 input {
+ height: 3rem;
+ line-height: 3rem;
+ margin: 0.5rem 0;
+ width: 100%;
+}
+
+.starwarsffg .item-name {
+ margin: 0;
+ padding-bottom: 0;
+}
+
+.starwarsffg .item-name .restricted {
+ color: #8b0000;
+}
+
+.starwarsffg .item-quantity {
+ position: relative;
+}
+
+.starwarsffg .grid {
+ margin-top: 0;
+}
+
+.starwarsffg .split .attribute .block-attribute {
+ grid-column: span 2 / span 2;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+}
+
+.starwarsffg .attribute {
+ z-index: 1;
+ position: relative;
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+
+.starwarsffg .attribute .block-title {
+ line-height: initial;
+ color: white;
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ margin: auto auto 0.25rem;
+ background-color: #415064;
+ line-height: 1rem;
+ --notchSize: 0.25rem;
+ -webkit-clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg .attribute .attribute-value.checkbox {
+ text-align: center;
+}
+
+.starwarsffg .attribute .block-attribute {
+ display: grid;
+ gap: 0.25rem;
+ margin: 0;
+ padding: 0;
+}
+
+.starwarsffg .attribute .block-attribute select:focus {
+ box-shadow: 0 0 5px #7da9c2;
+ background-color: #7da9c2;
+}
+
+.starwarsffg .attribute .block-attribute .block-value.block-single {
+ width: 100%;
+}
+
+.starwarsffg .attribute .block-value textarea {
+ resize: none;
+ font-family: 'Roboto';
+ border: 0;
+}
+
+.starwarsffg .attribute .block-value textarea:focus {
+ box-shadow: 0 0 5px #7da9c2;
+ background-color: #7da9c2;
+ outline: none;
+}
+
+.starwarsffg .attribute .block-value.block-single input {
+ width: 100%;
+}
+
+.starwarsffg .attribute .block-value input {
+ border: 0;
+ font-size: 14px;
+}
+
+.starwarsffg .attribute .block-value input:focus {
+ box-shadow: 0 0 5px #7da9c2;
+ background-color: #7da9c2;
+}
+
+.starwarsffg .injuries .attribute .block-title {
+ background-color: rgba(73, 12, 11, 0.85);
+}
+
+.starwarsffg .block-background-shadow {
+ position: absolute;
+ width: 100%;
+ bottom: -0.95rem;
+ height: 40px;
+ background-color: #7da9c2;
+ padding: 0.9rem 0 0;
+ z-index: -1;
+ --notchSize: 9px;
+ -webkit-clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg .shadow-text {
+ position: absolute;
+ bottom: 2%;
+ font-size: 9px;
+ left: 50%;
+ transform: translateX(-50%);
+ line-height: 1rem;
+}
+
+.starwarsffg .shadow-left {
+ background-color: rgba(73, 12, 11, 0.85);
+ width: 50%;
+ height: 100%;
+ float: left;
+ position: relative;
+ padding-top: 25px;
+}
+
+.starwarsffg .shadow-left .shadow-text {
+ color: white;
+ text-transform: uppercase;
+}
+
+.starwarsffg .shadow-right {
+ background-color: #7da9c2;
+ width: 50%;
+ height: 100%;
+ float: right;
+ position: relative;
+ padding-top: 25px;
+}
+
+.starwarsffg .shadow-right .shadow-text {
+ color: black;
+ font-weight: bold;
+ text-transform: uppercase;
+}
+
+.starwarsffg .tab {
+ height: 100%;
+ overflow: auto;
+}
+
+.starwarsffg form {
+ overflow: hidden;
+}
+
+.starwarsffg .quantity {
+ position: absolute;
+ top: 0;
+}
+
+.starwarsffg .quantity.increase {
+ right: 25%;
+ cursor: pointer;
+}
+
+.starwarsffg .quantity.decrease {
+ left: 25%;
+ cursor: pointer;
+}
+
+.starwarsffg .talent-body {
+ height: 100px;
+ overflow: auto;
+}
+
+.starwarsffg .talent-hidden {
+ display: none;
+}
+
+.starwarsffg .talent-grid {
+ display: flex;
+ flex-wrap: wrap;
+ position: relative;
+}
+
+.starwarsffg .talent-upgrade {
+ position: relative;
+ margin: 10px 10px 0 10px;
+}
+
+.starwarsffg .talent-upgrade .talent-actions {
+ display: none;
+}
+
+.starwarsffg .talent-upgrade .talent-actions .split,
+.starwarsffg .talent-upgrade .talent-actions .combine {
+ display: none;
+}
+
+.starwarsffg .talent-upgrade .talent-actions.talent-canSplit {
+ display: block;
+}
+
+.starwarsffg .talent-upgrade .talent-actions.talent-canSplit .split {
+ display: initial;
+ position: absolute;
+ right: 30px;
+ top: 15px;
+ font-size: 15px;
+ color: white;
+ z-index: 20;
+}
+
+.starwarsffg .talent-upgrade .talent-actions.talent-canSplit .split i {
+ transform: rotate(45deg);
+}
+
+.starwarsffg .talent-upgrade .talent-actions.talent-canCombine {
+ display: block;
+}
+
+.starwarsffg .talent-upgrade .talent-actions.talent-canCombine .combine {
+ display: initial;
+ position: absolute;
+ right: -16px;
+ font-size: 15px;
+ top: 15px;
+}
+
+.starwarsffg .talent-upgrade .talent-actions.talent-canCombine .combine i {
+ transform: rotate(45deg);
+}
+
+.starwarsffg .talent-upgrade.talent-single {
+ flex: 1 0 21%;
+}
+
+.starwarsffg .talent-upgrade.talent-double {
+ flex: 1 0 46%;
+}
+
+.starwarsffg .talent-upgrade.talent-triple {
+ flex: 1 0 71%;
+}
+
+.starwarsffg .talent-upgrade.talent-full {
+ flex: 1 0 97%;
+}
+
+.starwarsffg .talent-background {
+ position: relative;
+ z-index: 10;
+ background-color: #ddd;
+ padding: 5px;
+ --notchSize: 0.25rem;
+ -webkit-clip-path: polygon(0% 0, var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0% 0, var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg .talent-background .talent-header {
+ position: relative;
+ background-color: #000544;
+ padding: 5px;
+ --notchSize: 0.25rem;
+ -webkit-clip-path: polygon(0 0, var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, 0 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0 0, var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, 0 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg .talent-background .talent-header div {
+ display: inline-block;
+ vertical-align: top;
+ color: white;
+}
+
+.starwarsffg .talent-background .talent-header div.talent-name {
+ line-height: 0.75rem;
+ height: 1.5rem;
+ width: 85%;
+ text-align: right;
+}
+
+.starwarsffg .talent-background .talent-header div.talent-name.talent-modifiers {
+ cursor: pointer;
+}
+
+.starwarsffg .talent-background .talent-header div.talent-name span {
+ font-size: 0.55rem;
+ position: absolute;
+ bottom: 0;
+ left: 0.25rem;
+}
+
+.starwarsffg .talent-background .talent-header div input[type="text"] {
+ border: 0px;
+ background-color: #001e82;
+ color: white;
+}
+
+.starwarsffg .talent-background .talent-header div input[type="checkbox"] {
+ height: 1rem;
+ margin: 0;
+ padding: 0;
+ line-height: 0.75rem;
+ width: 1rem;
+}
+
+.starwarsffg .talent-background.talent-active .talent-header {
+ background-color: #490c0b;
+}
+
+.starwarsffg .talent-background.talent-passive .talent-header {
+ background-color: #464855;
+}
+
+.starwarsffg .sheet-header .talent-background {
+ margin: 0 0.25rem;
+}
+
+.starwarsffg .sheet-header .talent-background .talent-header .talent-name {
+ height: 2rem;
+}
+
+.starwarsffg .sheet-header .talent-background .talent-header .talent-name input {
+ font-size: 2rem;
+ height: 2rem;
+ text-align: left;
+}
+
+.starwarsffg .talent-row {
+ margin: 10px 0;
+}
+
+.starwarsffg .talent-row .talent-upgrade {
+ margin: 0 3px;
+}
+
+.starwarsffg .talent-connections {
+ font-size: 15px;
+}
+
+.starwarsffg .talent-single textarea {
+ max-width: 145px;
+}
+
+.starwarsffg .talent-double textarea {
+ max-width: 315px;
+}
+
+.starwarsffg .talent-triple textarea {
+ max-width: 495px;
+}
+
+.starwarsffg .talent-full textarea {
+ max-width: 670px;
+}
+
+.starwarsffg .talent-connection-point-top {
+ position: absolute;
+ top: -6px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 100%;
+ display: flex;
+}
+
+.starwarsffg .talent-connection-point-top.talent-single {
+ position: relative;
+ text-align: center;
+ transform: initial;
+ left: initial;
+ display: inline-block;
+}
+
+.starwarsffg .talent-connection-point-top.talent-double {
+ position: relative;
+ display: inline-block;
+ width: 50%;
+ left: initial;
+ transform: none;
+ text-align: center;
+}
+
+.starwarsffg .talent-connection-point-top.talent-triple {
+ position: relative;
+ display: inline-block;
+ width: 33%;
+ left: initial;
+ transform: none;
+ text-align: center;
+}
+
+.starwarsffg .talent-connection-point-top.talent-full {
+ position: relative;
+ display: inline-block;
+ width: 25%;
+ left: initial;
+ transform: none;
+ text-align: center;
+}
+
+.starwarsffg .talent-connection-point-top .talent-connector-up {
+ display: none;
+}
+
+.starwarsffg .talent-connection-point-right {
+ position: absolute;
+ right: -15px;
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+.starwarsffg .talent-connection-point-right .talent-connector-side {
+ display: none;
+}
+
+.starwarsffg .talent-connector i {
+ display: none;
+}
+
+.starwarsffg .talent-connector .talent-connector-up,
+.starwarsffg .talent-connector .talent-connector-side {
+ display: inline-block;
+ width: 34px;
+ height: 12px;
+ background-color: black;
+ position: absolute;
+ top: -3px;
+}
+
+.starwarsffg .talent-connector .talent-connector-up i,
+.starwarsffg .talent-connector .talent-connector-side i {
+ display: initial;
+ color: white;
+}
+
+.starwarsffg .talent-connector .talent-connector-up {
+ height: 100px;
+ top: -50px;
+ z-index: 5;
+}
+
+.starwarsffg .talent-connector .talent-connector-up i {
+ position: absolute;
+ top: 50%;
+ left: 0;
+ width: 34px;
+}
+
+.starwarsffg .talent-connector .talent-connector-side {
+ width: 35px;
+ height: 35px;
+ left: -15px;
+ padding-top: 8px;
+}
+
+.starwarsffg .talent-disable-edit .talent-actions {
+ display: none !important;
+}
+
+.starwarsffg .talent-disable-edit .talent-connections i {
+ display: none !important;
+}
+
+.starwarsffg .talent-action {
+ cursor: pointer;
+}
+
+.starwarsffg .header-fields {
+ position: relative;
+}
+
+.starwarsffg .header-fields .talent-actions {
+ position: absolute;
+ right: 10px;
+ top: 14px;
+ color: white;
+}
+
+.starwarsffg .dice-pool-dialog .dice-pool {
+ min-height: 54px;
+}
+
+.starwarsffg .dice-pool-dialog .pool-value {
+ border: 1px solid black;
+ background-color: #ddd;
+}
+
+.starwarsffg .dice-pool-dialog .pool-value input {
+ border: 0;
+ font-size: 25px;
+ text-align: center;
+ background: none;
+ width: 100%;
+}
+
+.starwarsffg .dice-pool-dialog .upgrade-buttons {
+ display: flex;
+ margin: 10px;
+}
+
+.starwarsffg .dice-pool-dialog .upgrade-buttons button {
+ line-height: 20px;
+ font-size: 12px;
+}
+
+.starwarsffg .dice-pool-dialog .pool-additional {
+ text-align: left;
+}
+
+.starwarsffg .dice-pool-dialog .pool-additional input[type="button"] {
+ margin: 0 3px;
+}
+
+.starwarsffg .dice-pool-dialog table {
+ width: 300px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.starwarsffg .dice-pool-dialog .hide {
+ display: none;
+}
+
+.starwarsffg .dice-pool-dialog .minimize {
+ max-width: 20%;
+}
+
+.starwarsffg .dice-pool-dialog .maximize {
+ width: 100%;
+ max-width: 78%;
+}
+
+.starwarsffg .ffgDiceArray {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: flex-start;
+}
+
+.starwarsffg .ffgDiceArray .dice-rolls {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+}
+
+.starwarsffg .ffgDiceArray .dice-rolls .roll {
+ width: 40px;
+ float: left;
+}
+
+.starwarsffg .dice-result .roll-success {
+ color: darkgreen;
+}
+
+.starwarsffg .dice-result .roll-failure {
+ color: darkred;
+}
+
+.starwarsffg .dice-additional-flavor {
+ font-style: oblique;
+ font-size: 12px;
+}
+
+.starwarsffg .item-display {
+ margin: 0.5rem 0 0.8rem 0;
+ padding: 0.5rem;
+ border: 1px solid #782e22;
+ background: rgba(255, 255, 255, 0.75);
+}
+
+.starwarsffg .item-display img {
+ height: 50px;
+ width: auto;
+}
+
+.starwarsffg .item-display h4 {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-around;
+}
+
+.starwarsffg .item-display h4 input {
+ width: 70%;
+}
+
+.starwarsffg .item-display .item-miss {
+ text-align: center;
+ font-style: italic;
+ color: darkred;
+}
+
+.starwarsffg .item-display .item-pill {
+ list-style-type: none;
+ padding: 0 5px;
+ background-color: #aaa;
+ border: 1px solid black;
+ border-radius: 12px;
+ display: inline-block;
+ margin: 1px;
+ cursor: pointer;
+ font-size: 12px;
+ line-height: 20px;
+ white-space: nowrap;
+}
+
+.starwarsffg .item-display .item-pill.adjusted {
+ color: #8b0000;
+}
+
+.starwarsffg .item-display .item-pill .rank {
+ padding-right: 15px;
+}
+
+.starwarsffg .item-display .item-pill .rank:hover {
+ color: green;
+}
+
+.starwarsffg .item-display .item-pill .item-delete {
+ cursor: pointer;
+}
+
+.starwarsffg .item-display .item-pill .item-delete:hover {
+ color: red;
+}
+
+.starwarsffg span.dietype {
+ position: relative;
+ font-size: 1rem;
+}
+
+.starwarsffg span.dietype.ability {
+ color: green;
+}
+
+.starwarsffg span.dietype.advantage {
+ color: black;
+}
+
+.starwarsffg span.dietype.boost {
+ color: lightskyblue;
+}
+
+.starwarsffg span.dietype.challenge {
+ color: red;
+}
+
+.starwarsffg span.dietype.dark {
+ color: black;
+}
+
+.starwarsffg span.dietype.despair {
+ color: black;
+}
+
+.starwarsffg span.dietype.difficulty {
+ color: purple;
+}
+
+.starwarsffg span.dietype.forcepoint {
+ color: black;
+}
+
+.starwarsffg span.dietype.failure {
+ color: black;
+}
+
+.starwarsffg span.dietype.force {
+ color: black;
+}
+
+.starwarsffg span.dietype.light {
+ color: black;
+}
+
+.starwarsffg span.dietype.proficiency {
+ color: yellow;
+}
+
+.starwarsffg span.dietype.remsetback {
+ color: black;
+}
+
+.starwarsffg span.dietype.remsetback::after {
+ content: "-";
+ color: white;
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+.starwarsffg span.dietype.restricted {
+ color: #8b0000;
+}
+
+.starwarsffg span.dietype.restricted::after {
+ content: "-";
+ color: white;
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+.starwarsffg span.dietype.setback {
+ color: black;
+}
+
+.starwarsffg span.dietype.success {
+ color: black;
+}
+
+.starwarsffg span.dietype.threat {
+ color: black;
+}
+
+.starwarsffg span.dietype.triumph {
+ color: black;
+}
+
+.starwarsffg span.dietype.adddifficulty {
+ color: purple;
+}
+
+.starwarsffg span.dietype.adddifficulty::after {
+ content: "+";
+ color: white;
+ position: absolute;
+ left: 50%;
+ top: 5%;
+ transform: translateX(-50%);
+}
+
+.starwarsffg span.dietype.updifficulty {
+ color: purple;
+}
+
+.starwarsffg span.dietype.updifficulty::after {
+ content: "\2191";
+ color: white;
+ position: absolute;
+ left: 50%;
+ top: 5%;
+ transform: translateX(-50%);
+}
+
+.starwarsffg span.dietype.cancelthreat {
+ color: black;
+}
+
+.starwarsffg span.dietype.cancelthreat::after {
+ content: "\2716";
+ color: red;
+ position: absolute;
+ left: 50%;
+ top: 5%;
+ transform: translateX(-50%);
+}
+
+.starwarsffg span.bold {
+ font-weight: bold;
+}
+
+.starwarsffg span.italic {
+ font-style: italic;
+}
+
+.starwarsffg .popout-editor {
+ text-align: left;
+ font-size: 12px;
+ overflow: auto;
+ height: 100%;
+ width: 100%;
+ min-height: 2rem;
+ position: relative;
+ padding: 0.25rem;
+ background: rgba(0, 0, 0, 0.05);
+ border-radius: 0.25rem;
+}
+
+.starwarsffg .popout-editor p {
+ line-height: .875rem;
+}
+
+.starwarsffg .popout-editor .popout-editor-button {
+ display: none;
+ position: absolute;
+ right: 0;
+ top: 0;
+ cursor: pointer;
+}
+
+.starwarsffg .popout-editor-window .sheet-body {
+ height: 100%;
+}
+
+.starwarsffg .popout-editor-window .editor {
+ width: 100%;
+ height: 100%;
+}
+
+.starwarsffg .editor {
+ width: 100%;
+ height: 100%;
+ min-height: 2rem;
+}
+
+.starwarsffg .tox.tox-tinymce {
+ height: 100% !important;
+}
+
+.starwarsffg .item-sheet-gear .tabs, .starwarsffg .item-sheet-talent .tabs, .starwarsffg .item-sheet-weapon .tabs, .starwarsffg .item-sheet-armor .tabs, .starwarsffg .item-sheet-vehicle-weapon .tabs, .starwarsffg .item-sheet-vehicle-attachment .tabs {
+ margin-top: 0.5rem;
+}
+
+.starwarsffg .item-sheet-gear .tabs .item, .starwarsffg .item-sheet-talent .tabs .item, .starwarsffg .item-sheet-weapon .tabs .item, .starwarsffg .item-sheet-armor .tabs .item, .starwarsffg .item-sheet-vehicle-weapon .tabs .item, .starwarsffg .item-sheet-vehicle-attachment .tabs .item {
+ height: auto;
+ -webkit-clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% 100%, 0 100%, 0% 0%, 0% 100%);
+ clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% 100%, 0 100%, 0% 0%, 0% 100%);
+}
+
+.starwarsffg .tabs {
+ line-height: 1rem;
+}
+
+.starwarsffg .tabs .item {
+ line-height: 1.5rem;
+ height: 2rem;
+ background-color: #535d60;
+ color: rgba(255, 255, 255, 0.9);
+ text-transform: Capitalise;
+ text-shadow: black 1px 2px 0;
+ padding: 0.25rem;
+ margin: 0 0.25rem;
+ --notchSize: 0.5rem;
+ -webkit-clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg .tabs .item:hover {
+ background-color: rgba(0, 0, 0, 0.75);
+ color: white;
+ text-shadow: black 1px 2px 0;
+}
+
+.starwarsffg .tabs .item.active {
+ height: 2.5rem;
+ line-height: 2rem;
+ background-color: rgba(73, 12, 11, 0.85);
+ color: white;
+ background-color: rgba(73, 12, 11, 0.85);
+ -webkit-clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% 100%, 0 100%, 0% 0%, 0% 100%);
+ clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% 100%, 0 100%, 0% 0%, 0% 100%);
+}
+
+.starwarsffg .tabs .item.active:hover {
+ background-color: rgba(73, 12, 11, 0.85);
+ cursor: default;
+}
+
+.starwarsffg.v2 .tabs {
+ position: absolute;
+ width: 2.5rem;
+ right: -2rem;
+ top: 2rem;
+}
+
+.starwarsffg.v2 .tabs .item {
+ line-height: 2rem;
+ font-size: 1.5rem;
+ margin: 0.25rem;
+ font-size: 1rem;
+ height: 2.5rem;
+ flex: 0 0 2.5rem;
+ -webkit-clip-path: polygon(0% 0, var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, 0 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0% 0, var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, 0 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg.v2 .tabs .item.active {
+ flex: 0 0 3rem;
+ height: 3rem;
+ line-height: 2.5rem;
+}
+
+.starwarsffg.v2.minimized nav.sheet-tabs {
+ display: none;
+}
+
+.starwarsffg.sheet nav.sheet-tabs {
+ flex: 0 0 auto;
+ height: auto;
+ line-height: auto;
+ margin: 0;
+ border: 0 none;
+}
+
+.starwarsffg .header-fields {
+ flex: 1;
+ height: auto;
+ padding: 0.25rem;
+ margin: 0;
+}
+
+.starwarsffg .header-fields h1, .starwarsffg .header-fields h2, .starwarsffg .header-fields h3, .starwarsffg .header-fields h4 {
+ margin: 0 0 0.25rem;
+ padding: 0;
+ font-weight: normal;
+}
+
+.starwarsffg .header-fields h1 {
+ margin: 0;
+ width: 100%;
+}
+
+.starwarsffg .header-fields .header-name {
+ margin: 0;
+ padding: 0.25rem 0.5rem;
+ position: relative;
+ width: 100%;
+ background: rgba(150, 150, 150, 0.25);
+ --notchSize: 0.5rem;
+ -webkit-clip-path: polygon(0% 0, 0 0%, 0 0%, 100% 0, 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0% 0, 0 0%, 0 0%, 100% 0, 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg .header-fields .header-name input {
+ width: 100%;
+ margin: 0;
+ background-color: rgba(0, 0, 0, 0);
+ border: 0 none;
+ border-radius: 0;
+ font-family: "StarJedi", sans-serif;
+ font-size: 1rem;
+ text-transform: uppercase;
+}
+
+.starwarsffg .header-fields .header-name input:focus {
+ box-shadow: 0 0 5px #7da9c2;
+ background-color: #7da9c2;
+}
+
+.starwarsffg .header-fields .header-name h2 {
+ border: 0 none;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.5);
+ -o-border-image: linear-gradient(to right, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)) 1%;
+ border-image: linear-gradient(to right, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)) 1%;
+}
+
+.starwarsffg .header-fields .header-name h2 input {
+ font-size: 1.5rem;
+ color: #781428;
+}
+
+.starwarsffg .header-fields .header-description-block {
+ margin: 0;
+ height: 130px;
+ overflow-y: auto;
+}
+
+.starwarsffg .header-fields .header-description {
+ background-color: #c3dce6;
+ padding: 0.25rem;
+ --notchSize: 0.5rem;
+ -webkit-clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg .header-fields .table {
+ display: table;
+ width: 100%;
+}
+
+.starwarsffg .header-fields .row {
+ display: flex;
+ width: 100%;
+ padding: 0.2rem 0 0;
+ border: 0 none;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.5);
+ -o-border-image: linear-gradient(to right, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)) 1%;
+ border-image: linear-gradient(to right, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)) 1%;
+}
+
+.starwarsffg .header-fields .row:last-child {
+ border: 0 none;
+}
+
+.starwarsffg .header-fields .row div {
+ float: left;
+ padding: 0.25rem;
+ line-height: 1rem;
+ text-transform: uppercase;
+ font-size: 0.8rem;
+ color: rgba(0, 0, 0, 0.5);
+}
+
+.starwarsffg .header-fields .profile-block {
+ margin: 0 0.25rem 0 0;
+ height: 130px;
+ max-width: 130px;
+}
+
+.starwarsffg .header-fields .profile-img {
+ flex: 0 0 100px;
+ height: 130px;
+ width: auto;
+ max-width: 130px;
+ border: 0;
+ margin: auto;
+ -o-object-fit: contain;
+ object-fit: contain;
+ --notchSize: 0.5rem;
+ -webkit-clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg .header-fields .resource {
+ flex: 4;
+}
+
+.starwarsffg .header-fields .resource:first-child {
+ margin-left: 0;
+}
+
+.starwarsffg .header-fields .resource:last-child {
+ margin-right: 0;
+}
+
+.starwarsffg .header-fields .full .resource.double {
+ flex: 100%;
+}
+
+.starwarsffg .item-sheet-talent .header-fields h1, .starwarsffg .item-sheet-gear .header-fields h1, .starwarsffg .item-sheet-weapon .header-fields h1, .starwarsffg .item-sheet-armor .header-fields h1, .starwarsffg .item-sheet-vehicle-weapon .header-fields h1, .starwarsffg .item-sheet-vehicle-attachment .header-fields h1, .starwarsffg .item-sheet-criticalinjury .header-fields h1 {
+ font-size: 1rem;
+}
+
+.starwarsffg .item-sheet-talent .header-fields h1 input, .starwarsffg .item-sheet-gear .header-fields h1 input, .starwarsffg .item-sheet-weapon .header-fields h1 input, .starwarsffg .item-sheet-armor .header-fields h1 input, .starwarsffg .item-sheet-vehicle-weapon .header-fields h1 input, .starwarsffg .item-sheet-vehicle-attachment .header-fields h1 input, .starwarsffg .item-sheet-criticalinjury .header-fields h1 input {
+ padding-left: 3rem;
+ margin: 0;
+ width: 100%;
+ text-align: center;
+}
+
+.starwarsffg .item-sheet-talent .header-fields .profile-img, .starwarsffg .item-sheet-gear .header-fields .profile-img, .starwarsffg .item-sheet-weapon .header-fields .profile-img, .starwarsffg .item-sheet-armor .header-fields .profile-img, .starwarsffg .item-sheet-vehicle-weapon .header-fields .profile-img, .starwarsffg .item-sheet-vehicle-attachment .header-fields .profile-img, .starwarsffg .item-sheet-criticalinjury .header-fields .profile-img {
+ position: absolute;
+ left: 0.5rem;
+ top: 0.5rem;
+ height: 3rem;
+ max-width: 3rem;
+ -webkit-clip-path: none;
+ clip-path: none;
+ -o-object-fit: cover;
+ object-fit: cover;
+ -o-object-position: 50% 0;
+ object-position: 50% 0;
+}
+
+.starwarsffg .item-sheet-talent .header-fields .item-name, .starwarsffg .item-sheet-gear .header-fields .item-name, .starwarsffg .item-sheet-weapon .header-fields .item-name, .starwarsffg .item-sheet-armor .header-fields .item-name, .starwarsffg .item-sheet-vehicle-weapon .header-fields .item-name, .starwarsffg .item-sheet-vehicle-attachment .header-fields .item-name, .starwarsffg .item-sheet-criticalinjury .header-fields .item-name {
+ padding: 0;
+ text-align: left;
+}
+
+.starwarsffg .characteristics {
+ height: 6rem;
+}
+
+.starwarsffg .characteristics fieldset.skillfilter {
+ display: none;
+}
+
+.starwarsffg .characteristics .characteristic-item {
+ position: relative;
+ background-color: rgba(84, 106, 83, 0.85);
+ border: 0;
+ --notchSize: 0.4rem;
+ -webkit-clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg .characteristics .characteristic-item .characteristic-label {
+ margin-top: 0.25rem;
+ width: 100%;
+ text-align: center;
+ background-color: rgba(73, 12, 11, 0.85);
+ color: white;
+ text-transform: uppercase;
+ font-size: 0.7rem;
+ line-height: 1rem;
+ --notchSize: 0.25rem;
+ -webkit-clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg .characteristic {
+ position: relative;
+ width: 55px;
+ height: 55px;
+ border-radius: 50%;
+ border: 2px solid rgba(0, 0, 0, 0.5);
+ margin: auto;
+ background: rgba(255, 255, 255, 0.9);
+}
+
+.starwarsffg .characteristic select {
+ width: 100px;
+}
+
+.starwarsffg .characteristic .characteristic-value input {
+ height: 40px;
+ border: 0;
+ font-size: 30px;
+ text-align: center;
+}
+
+.starwarsffg .characteristic .characteristic-value input {
+ height: 49px;
+ font-size: 2rem;
+ text-align: center;
+ border: 1px solid rgba(0, 0, 0, 0.5);
+ border-radius: 50%;
+ margin: 1px 0 0;
+}
+
+.starwarsffg .characteristic .characteristic-value input:focus {
+ box-shadow: 0 0 5px #7da9c2;
+ background-color: #7da9c2;
+}
+
+.starwarsffg .item-sheet-gear .characteristic-item, .starwarsffg .item-sheet-talent .characteristic-item, .starwarsffg .item-sheet-weapon .characteristic-item, .starwarsffg .item-sheet-armor .characteristic-item, .starwarsffg .item-sheet-vehicle-weapon .characteristic-item, .starwarsffg .item-sheet-vehicle-attachment .characteristic-item {
+ position: relative;
+ border: 0;
+ padding: 0.25rem;
+ margin: 0.25rem;
+ --notchSize: 7px;
+ -webkit-clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg .item-sheet-gear .characteristic-item .characteristic-label, .starwarsffg .item-sheet-talent .characteristic-item .characteristic-label, .starwarsffg .item-sheet-weapon .characteristic-item .characteristic-label, .starwarsffg .item-sheet-armor .characteristic-item .characteristic-label, .starwarsffg .item-sheet-vehicle-weapon .characteristic-item .characteristic-label, .starwarsffg .item-sheet-vehicle-attachment .characteristic-item .characteristic-label {
+ width: 100%;
+ text-align: center;
+ text-transform: uppercase;
+ font-size: 0.6rem;
+ margin: 0.25rem 0 0;
+}
+
+.starwarsffg .item-sheet-gear .characteristic-item .characteristic, .starwarsffg .item-sheet-talent .characteristic-item .characteristic, .starwarsffg .item-sheet-weapon .characteristic-item .characteristic, .starwarsffg .item-sheet-armor .characteristic-item .characteristic, .starwarsffg .item-sheet-vehicle-weapon .characteristic-item .characteristic, .starwarsffg .item-sheet-vehicle-attachment .characteristic-item .characteristic {
+ position: relative;
+ width: 3rem;
+ height: 3rem;
+ margin: auto;
+ border-radius: 0.5rem;
+ background: rgba(255, 255, 255, 0.75);
+ border: 1px solid rgba(0, 0, 0, 0.5);
+}
+
+.starwarsffg .item-sheet-gear .characteristic-item .characteristic .characteristic-value, .starwarsffg .item-sheet-talent .characteristic-item .characteristic .characteristic-value, .starwarsffg .item-sheet-weapon .characteristic-item .characteristic .characteristic-value, .starwarsffg .item-sheet-armor .characteristic-item .characteristic .characteristic-value, .starwarsffg .item-sheet-vehicle-weapon .characteristic-item .characteristic .characteristic-value, .starwarsffg .item-sheet-vehicle-attachment .characteristic-item .characteristic .characteristic-value {
+ height: 100%;
+}
+
+.starwarsffg .item-sheet-gear .characteristic-item .characteristic .characteristic-value input, .starwarsffg .item-sheet-talent .characteristic-item .characteristic .characteristic-value input, .starwarsffg .item-sheet-weapon .characteristic-item .characteristic .characteristic-value input, .starwarsffg .item-sheet-armor .characteristic-item .characteristic .characteristic-value input, .starwarsffg .item-sheet-vehicle-weapon .characteristic-item .characteristic .characteristic-value input, .starwarsffg .item-sheet-vehicle-attachment .characteristic-item .characteristic .characteristic-value input {
+ height: 100%;
+ border: 0;
+ font-size: 2rem;
+ text-align: center;
+ border-radius: 50%;
+ margin: 1px 0 0;
+}
+
+.starwarsffg .item-sheet-gear .characteristic-item .characteristic .characteristic-value input:focus, .starwarsffg .item-sheet-talent .characteristic-item .characteristic .characteristic-value input:focus, .starwarsffg .item-sheet-weapon .characteristic-item .characteristic .characteristic-value input:focus, .starwarsffg .item-sheet-armor .characteristic-item .characteristic .characteristic-value input:focus, .starwarsffg .item-sheet-vehicle-weapon .characteristic-item .characteristic .characteristic-value input:focus, .starwarsffg .item-sheet-vehicle-attachment .characteristic-item .characteristic .characteristic-value input:focus {
+ box-shadow: 0 0 5px rgba(125, 170, 195, 0.5);
+ background-color: rgba(125, 170, 195, 0.25);
+ border-radius: 50%;
+}
+
+.starwarsffg .item-sheet-gear .characteristic-item .characteristic select, .starwarsffg .item-sheet-talent .characteristic-item .characteristic select, .starwarsffg .item-sheet-weapon .characteristic-item .characteristic select, .starwarsffg .item-sheet-armor .characteristic-item .characteristic select, .starwarsffg .item-sheet-vehicle-weapon .characteristic-item .characteristic select, .starwarsffg .item-sheet-vehicle-attachment .characteristic-item .characteristic select {
+ width: 100px;
+}
+
+.starwarsffg .item-sheet-gear .item-name, .starwarsffg .item-sheet-talent .item-name, .starwarsffg .item-sheet-weapon .item-name, .starwarsffg .item-sheet-armor .item-name, .starwarsffg .item-sheet-vehicle-weapon .item-name, .starwarsffg .item-sheet-vehicle-attachment .item-name {
+ margin: 0;
+ padding-bottom: 0 !important;
+}
+
+.starwarsffg .skill-name {
+ position: relative;
+ font-size: 0.75rem;
+ padding-right: 1.5rem;
+}
+
+.starwarsffg .skill-characteristic {
+ display: inline;
+ font-size: 0.65rem;
+ font-style: italic;
+ text-transform: uppercase;
+ position: absolute;
+ right: 0;
+}
+
+.starwarsffg .skill-characteristic-select-dialog {
+ display: inline-block;
+ margin: auto;
+}
+
+.starwarsffg .skill-characteristic-select-dialog li {
+ list-style: none;
+}
+
+.starwarsffg .skill-create-dialog {
+ display: inline-flex;
+ margin: auto;
+}
+
+.starwarsffg .tableWithHeader {
+ overflow-y: auto;
+ margin: 0;
+ height: calc(100% - 6.5rem);
+ display: flex;
+}
+
+.starwarsffg .tableWithHeader .tableWithHeader-container {
+ flex: 1;
+ margin: 0 0.25rem 0 0;
+ position: relative;
+}
+
+.starwarsffg .skillsTablesGrid {
+ position: relative;
+ overflow-y: auto;
+ overflow-x: hidden;
+ height: calc(100% - 5rem);
+ float: left;
+ width: 100%;
+ padding: 0.25rem 0.25rem 0.25rem 0;
+}
+
+.starwarsffg .skillsGrid {
+ display: flex;
+ align-content: flex-start;
+ width: 100%;
+ padding: 0;
+}
+
+.starwarsffg .skillsGrid > .skillTypeGrid {
+ margin-left: 0.25rem;
+}
+
+.starwarsffg .skillsGrid > .skillTypeGrid:first-child {
+ margin-left: 0;
+ margin-right: 0.25rem;
+}
+
+.starwarsffg .skillsGrid .skillTypeGrid {
+ flex: 0 1 auto;
+ flex-direction: column;
+ margin: 0 0 0.5rem;
+ padding: 0;
+ border-color: rgba(0, 0, 0, 0.1);
+ --notchSize: 0.5rem;
+ -webkit-clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% 100%, 0 100%, 0% 0%, 0% 100%);
+ clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% 100%, 0 100%, 0% 0%, 0% 100%);
+}
+
+.starwarsffg .skillsGrid .skillTypeGrid.skillColumnGrid {
+ border: 1px solid #7a7971;
+ background: rgba(255, 255, 255, 0.5);
+ flex: calc(50% - 0.25rem);
+}
+
+.starwarsffg .skillsGrid .pure-g {
+ letter-spacing: initial;
+}
+
+.starwarsffg .skillsGrid .skillTable .skillsHeader {
+ line-height: 1.313rem;
+}
+
+.starwarsffg .skillsGrid .skillTable .skillsHeader div {
+ text-align: left;
+ padding: 0.25rem 0 0.25rem 0.5rem;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+ background-color: #535d60;
+ color: white;
+ font-size: 0.7rem;
+ font-weight: normal;
+}
+
+.starwarsffg .skillsGrid .skillTable .skillsHeader div:last-child {
+ text-align: center;
+ padding-right: 0.75rem;
+}
+
+.starwarsffg .skillsGrid .skillTable div.skill {
+ position: relative;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.starwarsffg .skillsGrid .skillTable div.skill > div {
+ text-align: center;
+}
+
+.starwarsffg .skillsGrid .skillTable div.skill > div:first-of-type {
+ text-align: left;
+ padding-left: 0.25rem;
+ padding-top: 0.25rem;
+}
+
+.starwarsffg .skillsGrid .skillTable div.skill input[type="checkbox"] {
+ width: 1rem;
+ height: 1rem;
+ margin: 0.25rem;
+}
+
+.starwarsffg .skillsGrid .skillTable .careerskill-toggle {
+ margin: 0;
+ padding: 0;
+}
+
+.starwarsffg .skillsGrid .skillTable .careerskill-rank {
+ margin-top: 0.25rem;
+ height: 1.5rem;
+ width: 1.75rem;
+ text-align: center;
+}
+
+.starwarsffg .skillsGrid .dice-pool {
+ margin: 0.5em 0;
+}
+
+.starwarsffg .skillsGrid .dice-pool img {
+ width: 1em;
+ height: 1em;
+ border: 0;
+}
+
+.starwarsffg .items {
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+
+.starwarsffg .items .items-header {
+ text-align: center;
+ padding: 3px;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.25);
+ background-color: rgba(73, 12, 11, 0.85);
+ color: white;
+ font-size: 12px;
+ font-weight: normal;
+}
+
+.starwarsffg .items .items-header .header-name {
+ flex: 1 0 120px;
+ text-align: left;
+}
+
+.starwarsffg .items li {
+ padding: 3px;
+}
+
+.starwarsffg .items h1 {
+ font-size: 1rem;
+}
+
+.starwarsffg .items h1 input {
+ padding-left: 130px;
+}
+
+.starwarsffg .items .skillsTablesGrid .skillsHeader {
+ text-align: left;
+ padding: 0.25rem;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.25);
+ background-color: rgba(73, 12, 11, 0.85);
+ color: white;
+ font-size: 12px;
+ font-weight: normal;
+}
+
+.starwarsffg .items .skillsTablesGrid .skill {
+ padding: 0.25rem;
+}
+
+.starwarsffg .items .resource {
+ line-height: 1rem;
+}
+
+.starwarsffg .items .item-values {
+ border: 0;
+}
+
+.starwarsffg .items .items-list {
+ padding: 0;
+}
+
+.starwarsffg .items .items-list .item {
+ line-height: 24px;
+ padding: 3px 0;
+ border-bottom: 1px solid #bbb;
+ text-align: center;
+}
+
+.starwarsffg .items .items-list .item:hover {
+ background: rgba(25, 24, 19, 0.12);
+}
+
+.starwarsffg .items .items-list .item img {
+ flex: 0 0 24px;
+ margin-right: 5px;
+}
+
+.starwarsffg .items .items-list .item .toggle-equipped {
+ color: #888;
+}
+
+.starwarsffg .items .items-list .item .toggle-equipped.active {
+ color: #191813;
+}
+
+.starwarsffg .items .items-list .force-power {
+ line-height: 24px;
+ padding: 3px 0;
+ border-bottom: 1px solid #bbb;
+ text-align: center;
+}
+
+.starwarsffg .items .items-list .force-power:hover {
+ background: rgba(25, 24, 19, 0.12);
+}
+
+.starwarsffg .items .items-list .force-power img {
+ flex: 0 0 24px;
+ margin-right: 5px;
+}
+
+.starwarsffg .items .items-list .item-name {
+ margin: 0;
+ cursor: pointer;
+ flex: 1 0 120px;
+ text-align: left;
+ display: flex;
+}
+
+.starwarsffg .items .items-list .item-controls {
+ z-index: 500;
+ width: 20px;
+ text-align: end;
+ margin: auto;
+}
+
+.starwarsffg .items .items-list .item-controls .item-control {
+ padding: 5px;
+}
+
+.starwarsffg .item-details {
+ flex: 0 0 100%;
+ text-align: left;
+ line-height: 16px;
+ padding: 0.25em 0.5em;
+ background: rgba(0, 0, 0, 0.1);
+ border: 1px groove rgba(0, 0, 0, 0.2);
+ grid-area: inherit;
+}
+
+.starwarsffg .item-details .item-properties {
+ position: relative;
+ flex: 0 0 120px;
+ margin: 5px 5px 5px 0;
+ padding-right: 5px;
+ font-weight: bold;
+ color: rgba(0, 0, 0, 0.75);
+}
+
+.starwarsffg .item-details .item-properties .tag {
+ display: inline-block;
+ margin: 0 2px 0 0;
+ padding: 0 3px;
+ font-size: 10px;
+ line-height: 16px;
+ border: 1px solid rgba(0, 0, 0, 0.5);
+ border-radius: 3px;
+ background: rgba(0, 0, 0, 0.1);
+}
+
+.starwarsffg .item-details .item-properties ul {
+ padding: 0;
+ margin: 0.25rem 0;
+}
+
+.starwarsffg .item-details .restricted {
+ color: #8b0000;
+}
+
+.starwarsffg .item-rollable {
+ height: 24px;
+ width: 48px;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ margin-right: 5px;
+ border: none;
+}
+
+.starwarsffg .item-rollable.rollable:hover {
+ background-image: url(/systems/starwarsffg/images/generic-die.svg) !important;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+.starwarsffg.item-card .item-details .item-properties .item-pill {
+ list-style-type: none;
+ display: inline-block;
+ cursor: pointer;
+ white-space: nowrap;
+ padding: 0.1rem 0.5rem;
+ margin: 0.1rem;
+ background-color: rgba(255, 255, 255, 0.25);
+ border: 1px solid rgba(255, 255, 255, 0.5);
+ border-radius: 1rem;
+}
+
+.starwarsffg.item-card .item-details .item-properties .item-pill.adjusted {
+ color: #8b0000;
+}
+
+.starwarsffg .talents {
+ margin: 0;
+}
+
+.starwarsffg table.force-powers {
+ border: 0;
+}
+
+.starwarsffg table.force-powers th {
+ text-align: left;
+ padding: 3px;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.25);
+ background-color: rgba(73, 12, 11, 0.85);
+ color: white;
+ font-size: 12px;
+ font-weight: normal;
+}
+
+.starwarsffg table.force-powers td {
+ height: 30px;
+ line-height: 24px;
+ padding: 3px;
+ position: relative;
+}
+
+.starwarsffg table.force-powers td .item-controls {
+ text-align: right;
+ width: auto;
+ z-index: auto;
+}
+
+.starwarsffg .talent-list {
+ border: 0;
+ padding: 0;
+ margin: 1em 0 0 0;
+}
+
+.starwarsffg .talent-list .item:hover {
+ background: rgba(25, 24, 19, 0.12);
+}
+
+.starwarsffg .talent-list .talents-header {
+ text-align: center;
+ padding: 3px;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.25);
+ background-color: #490c0b;
+ color: white;
+ font-size: 12px;
+ font-weight: normal;
+}
+
+.starwarsffg .talent-list .talents-header .header-name {
+ flex: 1 0 120px;
+ text-align: left;
+}
+
+.starwarsffg .talent-list .force-powers {
+ flex: 1 0 150px;
+}
+
+.starwarsffg .talent-list .item-controls {
+ z-index: 500;
+ width: 20px;
+ text-align: end;
+ margin: auto;
+}
+
+.starwarsffg .talent-list .item-controls .item-control {
+ padding: 5px;
+}
+
+.starwarsffg .sheet-header .header-fields .talents-header .header-name {
+ font-size: 2rem;
+ text-align: left;
+}
+
+.starwarsffg .biography .biography-editor {
+ height: 70%;
+}
+
+.starwarsffg .biography .bio-grid {
+ grid-column: span 5 / span 5;
+ grid-template-columns: auto auto auto auto auto;
+}
+
+.starwarsffg .biography .resource.split {
+ width: 10rem;
+}
+
+.starwarsffg.actor .biography .container.biography-values {
+ height: 30%;
+ display: flex;
+ flex-direction: column-reverse;
+ flex-wrap: initial;
+}
+
+.starwarsffg .sheet-body .biography .editor {
+ height: 100%;
+}
+
+.starwarsffg.actor .vehicle .biography .container.biography-values {
+ height: 30%;
+ display: grid;
+ grid-auto-flow: column;
+}
+
+.starwarsffg.actor .vehicle .biography .container.biography-values .block-title {
+ font-size: 0.6rem;
+}
+
+.starwarsffg.actor .vehicle .biography .container.biography-values .resource:nth-child(1), .starwarsffg.actor .vehicle .biography .container.biography-values .resource:nth-child(4) {
+ width: 5rem;
+}
+
+.starwarsffg.actor .vehicle .biography .container.biography-values .resource:nth-child(6) {
+ width: 7rem;
+}
+
+.starwarsffg .item-sheet-weapon .charname {
+ width: 100%;
+}
+
+.starwarsffg .item-sheet-weapon .charname input {
+ color: #134103;
+ border: 0;
+}
+
+.starwarsffg .item-sheet-weapon .charname input:focus {
+ box-shadow: 0 0 5px #7da9c2;
+ background-color: #7da9c2;
+}
+
+.starwarsffg .item-sheet-weapon .characteristic-item, .starwarsffg .item-sheet-weapon .block-background {
+ background-color: #bffebf;
+}
+
+.starwarsffg .item-sheet-weapon .characteristic-item .characteristic .adjustedvalues-left {
+ position: absolute;
+ top: 0;
+ right: 0;
+ background: red;
+ color: white;
+ width: 1rem;
+ border-radius: 0 0.5rem;
+ border: 1px solid transparent;
+}
+
+.starwarsffg .item-sheet-weapon .characteristic-item .characteristic .adjustedvalues-left.positive {
+ background: green;
+}
+
+.starwarsffg .item-sheet-weapon .adjustedvalue {
+ color: red;
+}
+
+.starwarsffg .item-sheet-weapon .attribute .block-title {
+ background-color: #134103;
+}
+
+.starwarsffg .item-sheet-weapon .pills.itemmodifier, .starwarsffg .item-sheet-weapon .pills.itemattachment {
+ width: auto;
+ max-width: 50%;
+ width: 50%;
+}
+
+.starwarsffg .item-sheet-weapon .pills.itemmodifier .item-pill-list, .starwarsffg .item-sheet-weapon .pills.itemattachment .item-pill-list {
+ max-height: 75px;
+ height: 75px;
+ overflow-y: scroll;
+ overflow-x: hidden;
+}
+
+.starwarsffg.sheet.item.v2 .item-sheet-weapon .container.flex-group-center {
+ padding: 0 5px 0 5px;
+ margin-bottom: 0;
+}
+
+.starwarsffg.sheet.item.v2 .item-sheet-weapon .weapon-values {
+ margin: 0 5px;
+ padding: 0 5px 0 5px;
+}
+
+.starwarsffg.sheet.item.v2 .item-sheet-weapon .sheet-body {
+ height: calc(100% - 26.6125rem);
+}
+
+.starwarsffg .item-sheet-armor .charname input {
+ color: #553113;
+}
+
+.starwarsffg .item-sheet-armor .characteristic-item, .starwarsffg .item-sheet-armor .block-background {
+ background-color: #e9c09e;
+}
+
+.starwarsffg .item-sheet-armor .attribute .block-title {
+ background-color: #553113;
+}
+
+.starwarsffg .item-sheet-gear .charname input {
+ color: #2c0344;
+}
+
+.starwarsffg .item-sheet-gear .characteristic-item, .starwarsffg .item-sheet-gear .block-background {
+ background-color: #e9c6fd;
+}
+
+.starwarsffg .item-sheet-gear .attribute .block-title {
+ background-color: #2c0344;
+}
+
+.starwarsffg .item-sheet-talent .charname input {
+ color: #821517;
+}
+
+.starwarsffg .item-sheet-talent .characteristic-item .characteristic-label {
+ background-color: #821517;
+}
+
+.starwarsffg .item-sheet-talent .characteristic-item .characteristic {
+ position: relative;
+ width: 55px;
+ height: 55px;
+ margin: auto;
+ border-radius: 10px;
+ background: rgba(255, 255, 255, 0.75);
+ border: 2px solid rgba(0, 0, 0, 0.5);
+}
+
+.starwarsffg .item-sheet-talent .characteristic-item .characteristic .characteristic-value {
+ height: 100%;
+}
+
+.starwarsffg .item-sheet-talent .characteristic-item .characteristic .characteristic-value input {
+ height: 100%;
+ border: 0;
+ font-size: 2rem;
+ text-align: center;
+ border-radius: 50%;
+ margin: 1px 0 0;
+}
+
+.starwarsffg .item-sheet-talent .characteristic-item .characteristic .characteristic-value input:focus {
+ box-shadow: 0 0 5px #7da9c2;
+ background-color: #7da9c2;
+ border-radius: 5px;
+}
+
+.starwarsffg .item-sheet-talent .block-background {
+ background-color: #f2ebde;
+}
+
+.starwarsffg .item-sheet-talent .attribute .block-title {
+ background-color: #821517;
+}
+
+.starwarsffg .minion {
+ flex: auto;
+}
+
+.starwarsffg .minion .sheet-header .minion-name {
+ flex: 1;
+ flex-wrap: wrap;
+}
+
+.starwarsffg .minion .sheet-header .minion-name h1 {
+ font-size: 1.5rem;
+ padding-left: 2rem;
+}
+
+.starwarsffg .minion .sheet-header .minion-name h1 input {
+ margin: 0;
+ color: rgba(73, 12, 11, 0.85);
+}
+
+.starwarsffg .minion .sheet-header .minion-name input {
+ border: 0;
+}
+
+.starwarsffg .minion .sheet-header .minion-name input:focus {
+ box-shadow: 0 0 5px #7da9c2;
+ background-color: #7da9c2;
+}
+
+.starwarsffg .minion .sheet-header .tab {
+ height: 100%;
+ padding: 0;
+}
+
+.starwarsffg .minion .sheet-header .tab.characteristics {
+ overflow: hidden;
+}
+
+.starwarsffg .minion .sheet-header .tab.characteristics > div.grid {
+ padding: 0.25rem 0;
+}
+
+.starwarsffg .minion .sheet-header .profile-block .block-background {
+ background: rgba(255, 255, 255, 0.5);
+}
+
+.starwarsffg .minion .minion-stats {
+ width: 100%;
+ margin: 0 0 1em;
+}
+
+.starwarsffg .minion table.items {
+ margin: 1em 0;
+}
+
+.starwarsffg .minion .biography-editor {
+ height: 100%;
+}
+
+.starwarsffg .minion .profile-block {
+ margin-right: 5px;
+ margin-left: 5px;
+ height: 130px;
+ max-width: 130px;
+}
+
+.starwarsffg .minion .profile-img {
+ flex: 0 0 100px;
+ height: 124px;
+ width: auto;
+ border: 0;
+ margin: auto;
+ -o-object-fit: contain;
+ object-fit: contain;
+}
+
+.starwarsffg .minion .sheet-body.double {
+ width: 235px;
+}
+
+.starwarsffg .minion table.items {
+ margin: 1em 0;
+}
+
+.starwarsffg .minion .biography-editor {
+ height: 100%;
+}
+
+.starwarsffg .minion .header-fields .resource:first-child {
+ margin-left: 0;
+}
+
+.starwarsffg .minion .header-fields .resource:last-child {
+ margin-right: 0;
+}
+
+.starwarsffg .minion .header-fields .resource.single {
+ flex: auto;
+}
+
+.starwarsffg .minion.v2 .sheet-body {
+ height: calc(100% - 13.5rem);
+}
+
+.starwarsffg.sheet.actor .vehicle .grid {
+ width: 100%;
+}
+
+.starwarsffg.sheet.actor .vehicle .header-fields .container.flex-group-center {
+ padding: 0;
+ margin-bottom: 0.25rem;
+}
+
+.starwarsffg.sheet.actor .vehicle .header-fields .block-background {
+ position: relative;
+}
+
+.starwarsffg.sheet.actor .vehicle .header-fields .block-background.defense-group, .starwarsffg.sheet.actor .vehicle .header-fields .block-background.vehicle-img {
+ background: rgba(255, 255, 255, 0.5);
+}
+
+.starwarsffg.sheet.actor .vehicle .header-fields .flex-group-right {
+ border: 0;
+ padding: 0 0.25rem;
+}
+
+.starwarsffg.sheet.actor .vehicle .header-fields .profile-block {
+ width: 100%;
+ height: 100%;
+ display: grid;
+ max-width: 100%;
+}
+
+.starwarsffg.sheet.actor .vehicle .header-fields .profile-block .profile-img {
+ height: 100%;
+ width: 100%;
+ max-width: 100%;
+ -o-object-fit: contain;
+ object-fit: contain;
+ flex: 1;
+}
+
+.starwarsffg.sheet.actor .vehicle .defense-group {
+ width: 100%;
+}
+
+.starwarsffg.sheet.actor .vehicle .defense-group .defense-decoration {
+ position: absolute;
+ width: 175px;
+ height: 175px;
+ border-radius: 50%;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%) rotate(45deg);
+ background: conic-gradient(#c7794b 0% 25%, #dbc48e 25% 50%, #c7794b 50% 75%, #dbc48e 75% 100%);
+}
+
+.starwarsffg.sheet.actor .vehicle .defense-group .defense-decoration img {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%) rotate(-45deg);
+ border: 0;
+}
+
+.starwarsffg.sheet.actor .vehicle .defense-block {
+ height: 200px;
+ width: 100%;
+ position: relative;
+}
+
+.starwarsffg.sheet.actor .vehicle .defense-block .defense-block-title {
+ position: absolute;
+ top: 0;
+ left: 0;
+ text-transform: uppercase;
+ background-color: #21192e;
+ color: white;
+ width: 90px;
+ text-align: center;
+ --notchSize: 9px;
+ -webkit-clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg.sheet.actor .vehicle .defense-block .defense-value {
+ border-radius: 10px;
+ border: 5px double #1f1c39;
+ margin: auto;
+ position: absolute;
+ background-color: white;
+}
+
+.starwarsffg.sheet.actor .vehicle .defense-block .defense-value .defense-value-label {
+ position: absolute;
+ text-transform: uppercase;
+ background-color: #490c0b;
+ width: 100%;
+ text-align: center;
+ color: white;
+ font-size: 10px;
+ left: 50%;
+ transform: translateX(-50%);
+ --notchSize: 7px;
+ -webkit-clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg.sheet.actor .vehicle .defense-block .defense-value.fore {
+ top: 0;
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+.starwarsffg.sheet.actor .vehicle .defense-block .defense-value.fore .defense-value-label {
+ bottom: -20px;
+}
+
+.starwarsffg.sheet.actor .vehicle .defense-block .defense-value.aft {
+ bottom: 0;
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+.starwarsffg.sheet.actor .vehicle .defense-block .defense-value.aft .defense-value-label {
+ top: -20px;
+}
+
+.starwarsffg.sheet.actor .vehicle .defense-block .defense-value.port {
+ left: 0;
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+.starwarsffg.sheet.actor .vehicle .defense-block .defense-value.port .defense-value-label {
+ left: 50px;
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+.starwarsffg.sheet.actor .vehicle .defense-block .defense-value.starboard {
+ right: 0;
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+.starwarsffg.sheet.actor .vehicle .defense-block .defense-value.starboard .defense-value-label {
+ left: -50px;
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+.starwarsffg.sheet.actor .vehicle .defense-block .defense-value input {
+ height: 40px;
+ width: 40px;
+ border: 0;
+ font-size: 30px;
+ text-align: center;
+}
+
+.starwarsffg.sheet.actor .vehicle .defense-block .defense-value input:focus {
+ box-shadow: 0 0 5px #7da9c2;
+ background-color: #7da9c2;
+ border-radius: 5px;
+}
+
+.starwarsffg.sheet.actor .vehicle .characteristic-item {
+ width: 100%;
+}
+
+.starwarsffg.sheet.actor .vehicle .characteristic-item .characteristic-label {
+ background-color: #226cc5;
+}
+
+.starwarsffg.sheet.actor .vehicle .characteristic-item .characteristic .characteristic-value input:focus {
+ border-radius: 5px;
+}
+
+.starwarsffg.sheet.actor .vehicle .attribute .block-title {
+ background-color: #1f1c39;
+}
+
+.starwarsffg.sheet.actor .vehicle .biography-values {
+ border: 0;
+ bottom: 1rem;
+}
+
+.starwarsffg.sheet.actor .vehicle .sheet-body {
+ height: calc(100% - 31rem);
+ overflow: auto;
+}
+
+.starwarsffg.sheet.actor .vehicle .injuries .resource.full {
+ flex: 100%;
+}
+
+.starwarsffg.sheet.actor .vehicle nav.sheet-tabs {
+ margin-top: 1rem;
+}
+
+.starwarsffg.sheet.actor .vehicle .injuries {
+ position: initial;
+}
+
+.starwarsffg .item-sheet-vehicle-weapon .popout-editor {
+ height: 100%;
+ width: 100%;
+}
+
+.starwarsffg .item-sheet-vehicle-weapon .charname {
+ width: 100%;
+}
+
+.starwarsffg .item-sheet-vehicle-weapon .charname input {
+ color: #1f1c37;
+ border: 0;
+}
+
+.starwarsffg .item-sheet-vehicle-weapon .charname input:focus {
+ box-shadow: 0 0 5px #7da9c2;
+ background-color: #7da9c2;
+}
+
+.starwarsffg .item-sheet-vehicle-weapon .characteristic-item, .starwarsffg .item-sheet-vehicle-weapon .block-background {
+ background-color: #cccae3;
+}
+
+.starwarsffg .item-sheet-vehicle-weapon .attribute .block-title {
+ background-color: #1f1c37;
+}
+
+.starwarsffg .item-sheet-vehicle-attachment .charname {
+ width: 100%;
+}
+
+.starwarsffg .item-sheet-vehicle-attachment .charname input {
+ color: #c87b4d;
+ border: 0;
+}
+
+.starwarsffg .item-sheet-vehicle-attachment .charname input:focus {
+ box-shadow: 0 0 5px #7da9c2;
+ background-color: #7da9c2;
+}
+
+.starwarsffg .item-sheet-vehicle-attachment .characteristic-item, .starwarsffg .item-sheet-vehicle-attachment .block-background {
+ background-color: #dfc68d;
+}
+
+.starwarsffg .item-sheet-vehicle-attachment .attribute .block-title {
+ background-color: #c87b4d;
+}
+
+.starwarsffg .group-manager {
+ font-family: Roboto, sans-serif;
+ color: #222222;
+ height: 100%;
+ width: auto;
+ text-align: center;
+}
+
+.starwarsffg .group-manager .PC-list {
+ margin: 20px 0;
+}
+
+.starwarsffg .group-manager .initiative-mode {
+ width: 100%;
+}
+
+.starwarsffg .group-manager .player-character:hover {
+ background: rgba(25, 24, 19, 0.03);
+ margin: +2px;
+}
+
+.starwarsffg .item:hover .hover {
+ text-decoration: underline;
+}
+
+.starwarsffg .hover:hover .tooltip {
+ opacity: 1;
+ z-index: 500;
+}
+
+.starwarsffg .hover:hover .tooltip2 {
+ opacity: 1;
+ z-index: 500;
+}
+
+.starwarsffg .hover:focus .tooltip2 {
+ opacity: 1;
+ z-index: 500;
+}
+
+.starwarsffg .hover .tooltip {
+ position: absolute;
+ top: 100%;
+ margin: 0;
+ padding: 0.25rem 0.5rem;
+ color: white;
+ background-color: rgba(25, 24, 19, 0.9);
+ border-radius: 0.25rem;
+ opacity: 0;
+ transition: opacity 0.5s;
+ z-index: -500;
+ padding: 0 10px;
+}
+
+.starwarsffg .hover .tooltip2 {
+ width: 100%;
+ height: -webkit-max-content;
+ height: -moz-max-content;
+ height: max-content;
+ min-width: 150px;
+ max-width: 360px;
+ position: absolute;
+ transition: opacity 0.5s;
+ left: 0;
+ background: #23221d;
+ border: 1px solid #000;
+ border-radius: 5px;
+ color: #EEE;
+ z-index: -500;
+ opacity: 0;
+ padding: 5px 10px;
+}
+
+.starwarsffg .hover .tooltip2 li {
+ text-align: left;
+}
+
+.starwarsffg .hover .tooltip2 .dietype {
+ background-color: white;
+}
+
+.starwarsffg .ffg-sendtochat.hover:hover .tooltip2 {
+ background: #fff;
+ border: 1px solid #23221d;
+ color: #23221d;
+}
+
+.starwarsffg .skillsGrid .skillTable div.skill:last-child .tooltip2 {
+ bottom: 2rem;
+}
+
+.starwarsffg .attributes {
+ margin: 0;
+ border: 0;
+}
+
+.starwarsffg .attributes .attributes-header {
+ padding: 5px;
+ margin: 0 0 5px 0;
+ background: rgba(0, 0, 0, 0.05);
+ border: 1px solid #aaa;
+ border-radius: 2px;
+ font-weight: bold;
+}
+
+.starwarsffg .attributes .attribute-label {
+ flex: 1.5;
+}
+
+.starwarsffg .attributes .attribute-control {
+ flex: 0 0 20px;
+}
+
+.starwarsffg .attributes .attributes-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.starwarsffg .attributes .attributes-list tr > * {
+ margin: 0 3px;
+ height: 28px;
+ line-height: 24px;
+ background: transparent;
+ border: none;
+ border-radius: 0;
+ border-bottom: 1px solid #aaa;
+ overflow: auto;
+}
+
+.starwarsffg .attributes .attributes-list .attribute.item {
+ position: relative;
+}
+
+.starwarsffg .attributes .attributes-list .attribute.item:hover {
+ background: rgba(25, 24, 19, 0.12);
+ margin: +2px;
+}
+
+.starwarsffg .attributes .attribute-value.checkbox {
+ text-align: center;
+}
+
+.starwarsffg .item-sheet-criticalinjury .sheet-header {
+ overflow: visible;
+}
+
+.starwarsffg .item-sheet-criticalinjury .header-fields .container.flex-group-center.full {
+ margin-top: 1rem;
+}
+
+.starwarsffg .item-sheet-criticalinjury .charname input {
+ color: #830707;
+ border: 0 none;
+}
+
+.starwarsffg .item-sheet-criticalinjury .charname input:focus {
+ box-shadow: 0 0 5px #7da9c2;
+ background-color: #7da9c2;
+}
+
+.starwarsffg .item-sheet-criticalinjury .block-background {
+ background-color: #da9797;
+}
+
+.starwarsffg .item-sheet-criticalinjury .block-background .block-title {
+ background-color: #830707;
+}
+
+.starwarsffg .item-sheet-criticalinjury .severity-img {
+ max-height: 15px;
+ max-width: 15px;
+ border: 0;
+}
+
+.starwarsffg .item-sheet-criticalinjury .resource {
+ flex: 100%;
+}
+
+.starwarsffg .item-sheet-criticalinjury select:focus {
+ box-shadow: 0 0 5px #7da9c2;
+ background-color: #7da9c2;
+}
+
+.starwarsffg .item-sheet-criticalinjury .editor-wrapper {
+ display: block;
+}
+
+.starwarsffg .item-sheet-criticalinjury .block-background.block-editor {
+ background-color: #da9797;
+ height: 12.5rem;
+}
+
+.starwarsffg .item-sheet-criticalinjury .severity-block {
+ position: relative;
+}
+
+.starwarsffg .injuries {
+ width: calc(100% - 0.25rem);
+ overflow-y: scroll;
+ margin-left: 0.25rem;
+}
+
+.starwarsffg .injuries .grid, .starwarsffg .injuries .grid-2col {
+ margin: 0;
+}
+
+.starwarsffg .injuries div.item {
+ cursor: pointer;
+ border-bottom: 1px solid #bbb;
+}
+
+.starwarsffg .injuries div.item:nth-last-of-type(1) {
+ border-bottom: none;
+}
+
+.starwarsffg .injuries div.item:hover {
+ background: rgba(25, 24, 19, 0.12);
+}
+
+.starwarsffg .injuries .severity {
+ position: relative;
+}
+
+.starwarsffg .injuries .severity-img {
+ max-height: 15px;
+ max-width: 15px;
+ border: 0;
+}
+
+.starwarsffg .injuries .resource.full {
+ margin: 0;
+}
+
+.starwarsffg .injuries .block-header > div:first-child {
+ line-height: initial;
+ background-color: #415064;
+ color: white;
+ font-size: 0.75rem;
+ line-height: 1.25rem;
+ text-transform: uppercase;
+ margin: auto;
+ --notchSize: 0.25rem;
+ -webkit-clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg .injuries .block-header > div:last-child {
+ background-color: rgba(73, 12, 11, 0.85);
+ margin: 0.25rem 0;
+ color: white;
+ font-size: 0.75rem;
+ line-height: 1.25rem;
+}
+
+.starwarsffg .injuries .item-controls {
+ z-index: 500;
+ text-align: end;
+ margin: auto;
+}
+
+.starwarsffg .injuries .item-controls .item-control {
+ padding: 5px;
+}
+
+.starwarsffg.sheet.item .item-sheet-forcepower .popout-editor {
+ height: 100%;
+ width: 100%;
+ min-height: 2rem;
+ position: relative;
+}
+
+.starwarsffg .item-sheet-forcepower {
+ min-width: 700px;
+ min-height: 600px;
+}
+
+.starwarsffg .item-sheet-forcepower .talent-background .talent-header {
+ background-color: #0e4400;
+}
+
+.starwarsffg .item-sheet-forcepower .talent-background .talent-header div input[type="text"] {
+ background-color: #1a8200;
+}
+
+.starwarsffg .item-sheet-forcepower .header-fields .talent-body {
+ margin-top: 0.25rem;
+ padding: 0;
+}
+
+.starwarsffg .item-sheet-forcepower .header-fields {
+ position: relative;
+ padding-bottom: 0;
+}
+
+.starwarsffg .item-sheet-forcepower .header-fields .talent-actions {
+ position: absolute;
+ right: 0.5rem;
+ top: 15px;
+ color: white;
+}
+
+.starwarsffg .item-sheet-forcepower .header-fields textarea {
+ max-width: 695px;
+}
+
+.starwarsffg .item-sheet-forcepower .sheet-body {
+ height: calc(100% - 10.25rem);
+ margin: 0;
+ padding: 0;
+ border: 0 none;
+ background: transparent;
+}
+
+.starwarsffg .item-sheet-forcepower .talent-cost {
+ bottom: 0;
+ left: 0;
+ position: absolute;
+ z-index: 10;
+ width: auto;
+ height: 0.75rem;
+ padding: 0 0.25rem 0 0.5rem;
+ font-size: 0.75rem;
+ background: #ddd;
+ color: rgba(0, 0, 0, 0.75);
+ --notchSize: 0.5rem;
+ -webkit-clip-path: polygon(0 0, var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, 0 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0 0, var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, 0 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg .item-sheet-forcepower .talent-cost input {
+ width: 1rem;
+ height: 0.75rem;
+ border: 0;
+ background: rgba(0, 0, 0, 0);
+ color: #0e4400;
+}
+
+.starwarsffg .item-sheet-forcepower .talent-modifiers {
+ position: absolute;
+ right: 5px;
+ bottom: 2px;
+ z-index: 10;
+}
+
+.starwarsffg .talent-list .item.forcepower #context-menu {
+ max-width: none;
+}
+
+.starwarsffg.sheet.item .item-sheet-specialization .popout-editor {
+ height: 100%;
+ width: 100%;
+ min-height: 2rem;
+ position: relative;
+}
+
+.starwarsffg .item-sheet-specialization {
+ min-width: 700px;
+ min-height: 600px;
+}
+
+.starwarsffg .item-sheet-specialization .talent-upgrade {
+ margin: 1px 10px 4px 10px;
+}
+
+.starwarsffg .item-sheet-specialization .sheet-body {
+ height: calc(100% - 10.5rem);
+ margin: 0;
+ padding: 0;
+ border: 0 none;
+ background: transparent;
+}
+
+.starwarsffg .item-sheet-specialization input.conflict {
+ opacity: .5;
+}
+
+.starwarsffg .item-sheet-specialization input.conflict:after {
+ content: "";
+ background-color: red;
+}
+
+.starwarsffg .item-sheet-specialization .header-fields {
+ padding-bottom: 0;
+}
+
+.starwarsffg .burst-8 {
+ z-index: -1;
+ left: 0.4rem;
+ background: rgba(255, 255, 255, 0.5);
+ width: 1rem;
+ height: 1rem;
+ position: absolute;
+ text-align: center;
+ transform: rotate(20deg);
+}
+
+.starwarsffg .burst-8:before {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 1rem;
+ width: 1rem;
+ background: rgba(255, 255, 255, 0.5);
+ transform: rotate(135deg);
+}
+
+.starwarsffg div.conflict {
+ height: 1rem;
+ width: 1rem;
+ position: absolute;
+ background: red;
+ left: 9px;
+ top: 7px;
+ z-index: -1;
+}
+
+.starwarsffg.data-import .import-file-selector div {
+ display: inline-block;
+}
+
+.starwarsffg.data-import .import-invalid {
+ display: none;
+}
+
+.starwarsffg.data-import .import-hidden {
+ visibility: hidden;
+}
+
+.starwarsffg.data-import .import-progress {
+ background-color: #f1f1f1;
+ margin: 0.25rem 0.25rem;
+ width: 100%;
+}
+
+.starwarsffg.data-import .import-progress.current {
+ width: inherit;
+}
+
+.starwarsffg.data-import .import-progress.overall {
+ width: inherit;
+}
+
+.starwarsffg.data-import .import-progress .import-progress-bar {
+ height: 24px;
+ background-color: green;
+ text-align: center;
+}
+
+.starwarsffg.data-import .import-progress .import-progress-bar span {
+ line-height: 24px;
+}
+
+.starwarsffg.data-import .skilllist table {
+ width: 100%;
+ table-layout: fixed;
+ border-collapse: collapse;
+}
+
+.starwarsffg.data-import .skilllist th, .starwarsffg.data-import .skilllist td {
+ padding: 0.3125rem;
+ text-align: center;
+ width: 7.8125rem;
+}
+
+.starwarsffg.data-import .skilllist tbody {
+ display: block;
+ overflow: auto;
+ height: 4.5rem;
+ width: 100%;
+}
+
+.starwarsffg.data-import .skilllist thead tr {
+ display: block;
+}
+
+.starwarsffg.data-import .skilllist .skilltheme {
+ text-align: left;
+}
+
+.starwarsffg.data-import .skilllist .fa-download {
+ cursor: pointer;
+}
+
+.starwarsffg.data-import .skilllist .skillactive {
+ text-align: center;
+}
+
+.starwarsffg.data-import .skilllist .skillactive .fa-check-circle {
+ color: green;
+}
+
+.starwarsffg.data-import .skilllist .skillactive .fa-times-circle {
+ color: red;
+}
+
+.starwarsffg .debug label {
+ display: block;
+ padding-left: 15px;
+ text-indent: -15px;
+}
+
+.starwarsffg .debug input[type="checkbox"] {
+ position: relative;
+ padding: 0;
+ margin: 0;
+ width: 13px;
+ height: 13px;
+}
+
+.starwarsffg .item-sheet-species .sheet-header img.profile-img {
+ flex: 0 0 100px;
+ height: 124px;
+ width: auto;
+ max-width: 124px;
+ border: 0;
+ margin: auto;
+ -o-object-fit: contain;
+ object-fit: contain;
+}
+
+.starwarsffg .item-sheet-species .characteristics .characteristic-item .characteristic-label {
+ background-color: #490c0b;
+}
+
+.starwarsffg .item-sheet-species .attribute .block-title {
+ background-color: #490c0b;
+}
+
+.starwarsffg .item-sheet-species .sheet-body {
+ height: calc(100% - 25.25rem);
+}
+
+.starwarsffg .item-sheet-career .sheet-body {
+ height: calc(100% - 11.75rem);
+}
+
+.starwarsffg .attributes {
+ margin: 0;
+ border: 0;
+}
+
+.starwarsffg .attributes .attributes-header {
+ padding: 5px;
+ margin: 0 0 5px 0;
+ background: rgba(0, 0, 0, 0.05);
+ border: 1px solid #aaa;
+ border-radius: 2px;
+ font-weight: bold;
+}
+
+.starwarsffg .attributes .attribute-label {
+ flex: 1.5;
+}
+
+.starwarsffg .attributes .attribute-control {
+ flex: 0 0 20px;
+}
+
+.starwarsffg .attributes .attributes-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.starwarsffg .attributes .attributes-list tr > * {
+ margin: 0 3px;
+ height: 28px;
+ line-height: 24px;
+ background: transparent;
+ border: none;
+ border-radius: 0;
+ border-bottom: 1px solid #aaa;
+ overflow: auto;
+}
+
+.starwarsffg .attributes .attributes-list .attribute.item {
+ position: relative;
+}
+
+.starwarsffg .attributes .attributes-list .attribute.item:hover {
+ background: rgba(25, 24, 19, 0.12);
+ margin: +2px;
+}
+
+.starwarsffg .attributes .attribute-value.checkbox {
+ text-align: center;
+}
+
+.starwarsffg .attributes {
+ margin: 0;
+ border: 0;
+}
+
+.starwarsffg .attributes .attributes-header {
+ padding: 5px;
+ margin: 0 0 5px 0;
+ background: rgba(0, 0, 0, 0.05);
+ border: 1px solid #aaa;
+ border-radius: 2px;
+ font-weight: bold;
+}
+
+.starwarsffg .attributes .attribute-label {
+ flex: 1.5;
+}
+
+.starwarsffg .attributes .attribute-control {
+ flex: 0 0 20px;
+}
+
+.starwarsffg .attributes .attributes-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.starwarsffg .attributes .attributes-list tr > * {
+ margin: 0 3px;
+ height: 28px;
+ line-height: 24px;
+ background: transparent;
+ border: none;
+ border-radius: 0;
+ border-bottom: 1px solid #aaa;
+ overflow: auto;
+}
+
+.starwarsffg .attributes .attributes-list .attribute.item {
+ position: relative;
+}
+
+.starwarsffg .attributes .attributes-list .attribute.item:hover {
+ background: rgba(25, 24, 19, 0.12);
+ margin: +2px;
+}
+
+.starwarsffg .attributes .attribute-value.checkbox {
+ text-align: center;
+}
+
+.starwarsffg.sheet.item .item-sheet-signatureability .popout-editor {
+ height: 100%;
+ width: 100%;
+ min-height: 2rem;
+ position: relative;
+}
+
+.starwarsffg .item-sheet-signatureability .talent-background .talent-header {
+ background-color: #630000;
+}
+
+.starwarsffg .item-sheet-signatureability .talent-background .talent-header div input[type="text"] {
+ background-color: #fcadad;
+}
+
+.starwarsffg .item-sheet-signatureability .header-fields {
+ position: relative;
+}
+
+.starwarsffg .item-sheet-signatureability .header-fields .talent-actions {
+ position: absolute;
+ right: 10px;
+ top: 14px;
+ color: white;
+}
+
+.starwarsffg .item-sheet-signatureability .header-fields textarea {
+ max-width: 695px;
+}
+
+.starwarsffg .item-sheet-signatureability .sheet-body {
+ margin: 0;
+ overflow: hidden;
+}
+
+.starwarsffg .item-sheet-signatureability .talent-cost {
+ bottom: 0;
+ left: 0;
+ position: absolute;
+ z-index: 10;
+ width: auto;
+ height: 0.75rem;
+ padding: 0 0.25rem 0 0.5rem;
+ font-size: 0.75rem;
+ background: #ddd;
+ color: rgba(0, 0, 0, 0.75);
+ --notchSize: 0.5rem;
+ -webkit-clip-path: polygon(0 0, var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, 0 100%, 0% calc(100% - var(--notchSize)));
+ clip-path: polygon(0 0, var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, 0 100%, 0% calc(100% - var(--notchSize)));
+}
+
+.starwarsffg .item-sheet-signatureability .talent-cost input {
+ width: 1rem;
+ height: 0.75rem;
+ border: 0;
+ background: rgba(0, 0, 0, 0);
+ color: #630000;
+}
+
+.starwarsffg .item-sheet-signatureability .talent-modifiers {
+ position: absolute;
+ right: 5px;
+ bottom: 2px;
+ z-index: 10;
+}
+
+.starwarsffg .item-sheet-modifiers .charname input {
+ color: #2c0344;
+}
+
+.starwarsffg .item-sheet-modifiers .characteristic-item .characteristic-label {
+ background-color: #2c0344;
+}
+
+.starwarsffg .item-sheet-modifiers .block-background {
+ background-color: #e9c6fd;
+}
+
+.starwarsffg .item-sheet-modifiers .attribute .block-title {
+ background-color: #2c0344;
+}
+
+.starwarsffg .item-sheet-modifiers .sheet-body {
+ height: calc(100% - 12.7rem);
+ overflow: auto;
+}
+
+.starwarsffg.sheet.item.v2 .item-sheet-modifiers .container.flex-group-center {
+ padding: 0 5px 0 5px;
+ margin-bottom: 0;
+}
+
+.starwarsffg.sheet.item.v2 .item-sheet-modifiers .weapon-values {
+ margin: 0 5px;
+ padding: 0 5px 0 5px;
+}
+
+.starwarsffg.sheet.item.v2 .item-sheet-modifiers .sheet-body {
+ height: calc(100% - 8.6rem);
+}
+
+.starwarsffg .item-sheet-itemattachment .profile-img-field {
+ width: 25%;
+}
+
+.starwarsffg .item-sheet-itemattachment .profile-img-field img.profile-img {
+ width: 100%;
+ height: auto;
+}
+
+.starwarsffg .item-sheet-itemattachment .sheet-body {
+ height: calc(100% - 19rem);
+}
+
+.starwarsffg .item-sheet-itemattachment .sheet-body table th:first-child {
+ text-align: left;
+ padding-left: 3px;
+}
+
+.starwarsffg .item-sheet-itemattachment .sheet-body table td:not(:first-child) {
+ position: relative;
+ text-align: center;
+}
+
+.starwarsffg .item-sheet-itemattachment .sheet-body table td:not(:first-child) .quantity {
+ top: 3px;
+}
+
+.starwarsffg .item-sheet-itemattachment .sheet-body table td .modifier-active i {
+ cursor: pointer;
+}
+
+.starwarsffg .item-sheet-itemattachment .sheet-body table td.add-modifier i {
+ padding: 0 2px;
+ cursor: pointer;
+}
+
+.starwarsffg.sheet.item.v2 .item-sheet-itemattachment .sheet-body {
+ height: calc(100% - 16.5rem);
+}
+
+.initiative-dialog .pool-selector {
+ font-size: 1rem;
+ text-align: center;
+ margin-bottom: 1rem;
+}
+
+.initiative-dialog .pool-additional img {
+ vertical-align: middle;
+}
+
+.initiative-dialog .pool-additional input {
+ margin: 0 0.1875rem;
+}
+
+.og-character-import {
+ min-width: 96%;
+}
+
+.og-character-import h4 {
+ min-width: 70%;
+ text-align: center;
+}
+
+.og-character-import button {
+ min-width: 48%;
+ margin: 5px 2px;
+}
+
+#destiny-tracker {
+ box-shadow: none;
+ background: transparent;
+ position: absolute;
+ /* Dropdown Button */
+ /* Dropdown button on hover & focus */
+ /* The container
- needed to position the dropdown content */
+ /* Dropdown Content (Hidden by Default) */
+ /* Change color of dropdown links on hover */
+ /* Show the dropdown menu on hover */
+ /* Change the background color of the dropdown button when the dropdown content is shown */
+ /* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */
+}
+
+#destiny-tracker .window-content {
+ background: transparent;
+}
+
+#destiny-tracker header.window-header {
+ display: none;
+}
+
+#destiny-tracker .dropbtn {
+ background: url("../../../ui/denim075.png") repeat;
+ color: white;
+ font-size: 12px;
+ border: none;
+ cursor: pointer;
+ text-shadow: 0 0 5px black;
+}
+
+#destiny-tracker .dropbtn i {
+ margin: 0;
+}
+
+#destiny-tracker .dropbtn:hover, #destiny-tracker .dropbtn:focus {
+ background-color: rgba(0, 0, 0, 0.5);
+ box-shadow: 0 0 1rem #ff6400;
+}
+
+#destiny-tracker .dropdown {
+ position: relative;
+ display: inline-block;
+}
+
+#destiny-tracker .dropdown-content {
+ display: none;
+ position: absolute;
+ background: url("../../../ui/denim075.png") repeat;
+ max-width: 50px;
+ box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
+ z-index: 1;
+}
+
+#destiny-tracker .dropdown-content a {
+ color: white;
+ padding: 5px 4px;
+ text-decoration: none;
+ display: block;
+ font-size: 15px;
+ width: 50%;
+ float: left;
+}
+
+#destiny-tracker .dropdown:hover .dropbtn {
+ background-color: rgba(255, 100, 0, 0.5);
+}
+
+#destiny-tracker .show {
+ display: block;
+ width: 200%;
+}
+
+#destiny-tracker .show a:hover {
+ background-color: rgba(255, 100, 0, 0.5);
+}
+
+.swffg-destiny-container {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.swffg-destiny {
+ box-sizing: content-box;
+ color: white;
+ font-family: "warnock-pro", "sans-serif";
+ font-size: 2rem;
+ height: auto;
+ bottom: 0px;
+ line-height: auto;
+ margin: 0.5rem;
+ pointer-events: none;
+ text-align: center;
+ width: auto;
+ display: flex;
+ z-index: 70;
+}
+
+.swffg-destiny.swffg-groupmanager {
+ pointer-events: auto;
+}
+
+.swffg-destiny #destinyLight {
+ background: rgba(0, 0, 0, 0.5) url("../images/dice/starwars/lightside.png") no-repeat center;
+ background-size: contain;
+}
+
+.swffg-destiny #destinyDark {
+ background: rgba(0, 0, 0, 0.5) url("../images/dice/starwars/darkside.png") no-repeat center;
+ background-size: contain;
+}
+
+.swffg-destiny .destiny-points {
+ flex: 0 0 4rem;
+ position: relative;
+ cursor: pointer;
+ padding: 0.25rem;
+ pointer-events: auto;
+ margin: 0.25rem;
+ width: 4rem;
+ height: 4rem;
+ line-height: 3.25rem;
+ text-shadow: 1px 2px 1px black;
+ border-radius: 50%;
+ box-shadow: inset 0 0 3rem rgba(0, 0, 0, 0.75);
+}
+
+.swffg-destiny .destiny-points:hover {
+ box-shadow: 0 0 1rem #ff6400;
+}
+
+.swffg-destiny .destiny-points span {
+ bottom: 0;
+ font-family: "mason-serif", "Nodesto", "Signika", "Palatino Linotype", serif;
+ font-size: 0.75rem;
+ left: 50%;
+ line-height: 0.5rem;
+ position: absolute;
+ transform: translateX(-50%);
+ width: 80px;
+}
+
+.swffg-destiny #context-menu {
+ font-size: 0.875rem;
+}
+
+.ffg-destiny-roll {
+ cursor: pointer;
+}
diff --git a/styles/starwarsffg.css b/styles/starwarsffg.css
index 8901bc33..a7552bd5 100644
--- a/styles/starwarsffg.css
+++ b/styles/starwarsffg.css
@@ -142,8 +142,12 @@ span.dietype.triumph {
color: black;
}
-.paused img {
- content: url("/systems/starwarsffg/images/paused.png");
+.clearfix:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
}
.grid,
@@ -483,7 +487,6 @@ img {
overflow: auto;
width: 100%;
height: 100%;
- max-height: 3.5rem;
}
.starwarsffg.sheet.actor .character .sheet-body .general .block-editor .resource, .starwarsffg.sheet.actor .character .sheet-body .obligation .block-editor .resource {
@@ -600,6 +603,7 @@ img {
border: 0;
margin-bottom: 5px;
padding: 0 5px 5px 5px;
+ align-items: flex-start;
}
.starwarsffg.sheet.item .popout-editor {
@@ -620,6 +624,15 @@ img {
line-height: 15px;
}
+.starwarsffg.sheet.item .characteristic .characteristic-value.restricted::after {
+ content: 'R';
+ position: absolute;
+ font-size: 15px;
+ left: 1px;
+ top: -1px;
+ color: #8b0000;
+}
+
.starwarsffg.sheet .window-content {
overflow-y: hidden;
}
@@ -629,6 +642,56 @@ img {
overflow: auto;
}
+.starwarsffg.sheet .item-pill-list {
+ padding: 0;
+}
+
+.starwarsffg.sheet .item-pill-list:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+
+.starwarsffg.sheet .item-pill {
+ list-style-type: none;
+ padding: 0 5px;
+ background-color: #aaa;
+ border: 1px solid black;
+ border-radius: 12px;
+ float: left;
+ margin: 1px;
+ cursor: pointer;
+ font-size: 12px;
+ line-height: 20px;
+ white-space: nowrap;
+}
+
+.starwarsffg.sheet .item-pill.adjusted {
+ color: #8b0000;
+}
+
+.starwarsffg.sheet .item-pill .rank {
+ padding-right: 15px;
+}
+
+.starwarsffg.sheet .item-pill .rank:hover {
+ color: green;
+}
+
+.starwarsffg.sheet .item-pill .item-delete {
+ cursor: pointer;
+}
+
+.starwarsffg.sheet .item-pill .item-delete:hover {
+ color: red;
+}
+
+.starwarsffg.sheet .add-new-item {
+ cursor: pointer;
+}
+
.starwarsffg.sheet.item.v2 .sheet-body {
margin: 0px 5px;
}
@@ -777,7 +840,6 @@ img {
.starwarsffg .attribute .block-value.block-single input {
width: 100%;
- padding: 6px;
}
.starwarsffg .attribute .block-value input {
@@ -934,6 +996,7 @@ img {
top: 15px;
font-size: 15px;
color: white;
+ z-index: 20;
}
.starwarsffg .talent-upgrade .talent-actions.talent-canSplit .split i {
@@ -1102,7 +1165,7 @@ img {
.starwarsffg .talent-connection-point-right {
position: absolute;
- right: -5px;
+ right: -15px;
top: 50%;
transform: translateY(-50%);
}
@@ -1118,7 +1181,7 @@ img {
.starwarsffg .talent-connector .talent-connector-up,
.starwarsffg .talent-connector .talent-connector-side {
display: inline-block;
- width: 20px;
+ width: 34px;
height: 12px;
background-color: black;
position: absolute;
@@ -1140,13 +1203,15 @@ img {
.starwarsffg .talent-connector .talent-connector-up i {
position: absolute;
top: 50%;
- left: 3px;
+ left: 0;
+ width: 34px;
}
.starwarsffg .talent-connector .talent-connector-side {
- right: -15px;
- width: 20px;
- height: 20px;
+ width: 35px;
+ height: 35px;
+ left: -15px;
+ padding-top: 8px;
}
.starwarsffg .talent-disable-edit .talent-actions {
@@ -1287,6 +1352,40 @@ img {
color: darkred;
}
+.starwarsffg .item-display .item-pill {
+ list-style-type: none;
+ padding: 0 5px;
+ background-color: #aaa;
+ border: 1px solid black;
+ border-radius: 12px;
+ display: inline-block;
+ margin: 1px;
+ cursor: pointer;
+ font-size: 12px;
+ line-height: 20px;
+ white-space: nowrap;
+}
+
+.starwarsffg .item-display .item-pill.adjusted {
+ color: #8b0000;
+}
+
+.starwarsffg .item-display .item-pill .rank {
+ padding-right: 15px;
+}
+
+.starwarsffg .item-display .item-pill .rank:hover {
+ color: green;
+}
+
+.starwarsffg .item-display .item-pill .item-delete {
+ cursor: pointer;
+}
+
+.starwarsffg .item-display .item-pill .item-delete:hover {
+ color: red;
+}
+
.starwarsffg span.dietype {
position: relative;
}
@@ -1352,7 +1451,7 @@ img {
}
.starwarsffg span.dietype.restricted {
- color: red;
+ color: #8b0000;
}
.starwarsffg span.dietype.restricted::after {
@@ -1437,7 +1536,7 @@ img {
}
.starwarsffg .popout-editor p {
- line-height: 1.25rem;
+ line-height: .875rem;
}
.starwarsffg .popout-editor .popout-editor-button {
@@ -1758,6 +1857,10 @@ img {
background: rgba(0, 0, 0, 0.1);
}
+.starwarsffg .item-details .restricted {
+ color: #8b0000;
+}
+
.starwarsffg .item-rollable {
height: 24px;
width: 48px;
@@ -1775,6 +1878,24 @@ img {
background-position: center;
}
+.starwarsffg.item-card .item-details .item-properties .item-pill {
+ list-style-type: none;
+ padding: 0 5px;
+ background-color: #aaa;
+ border: 1px solid black;
+ border-radius: 12px;
+ display: inline-block;
+ margin: 1px;
+ cursor: pointer;
+ font-size: 12px;
+ line-height: 20px;
+ white-space: nowrap;
+}
+
+.starwarsffg.item-card .item-details .item-properties .item-pill.adjusted {
+ color: #8b0000;
+}
+
.starwarsffg .talents {
margin: 0;
}
@@ -1849,6 +1970,21 @@ img {
border: 5px double #134103;
}
+.starwarsffg .item-sheet-weapon .characteristic-item .characteristic .adjustedvalues-left {
+ position: absolute;
+ top: 0;
+ right: 2px;
+ color: red;
+}
+
+.starwarsffg .item-sheet-weapon .characteristic-item .characteristic .adjustedvalues-left.positive {
+ color: green;
+}
+
+.starwarsffg .item-sheet-weapon .adjustedvalue {
+ color: red;
+}
+
.starwarsffg .item-sheet-weapon .block-background {
background-color: #bffebf;
}
@@ -1858,7 +1994,20 @@ img {
}
.starwarsffg .item-sheet-weapon .sheet-body {
- height: calc(100% - 31.875rem);
+ height: calc(100% - 27.5rem);
+}
+
+.starwarsffg .item-sheet-weapon .pills.itemmodifier, .starwarsffg .item-sheet-weapon .pills.itemattachment {
+ width: auto;
+ max-width: 50%;
+ width: 50%;
+}
+
+.starwarsffg .item-sheet-weapon .pills.itemmodifier .item-pill-list, .starwarsffg .item-sheet-weapon .pills.itemattachment .item-pill-list {
+ max-height: 75px;
+ height: 75px;
+ overflow-y: scroll;
+ overflow-x: hidden;
}
.starwarsffg.sheet.item.v2 .item-sheet-weapon .container.flex-group-center {
@@ -2934,6 +3083,81 @@ img {
z-index: 10;
}
+.starwarsffg .item-sheet-modifiers .charname input {
+ color: #2c0344;
+}
+
+.starwarsffg .item-sheet-modifiers .characteristic-item .characteristic-label {
+ background-color: #2c0344;
+}
+
+.starwarsffg .item-sheet-modifiers .block-background {
+ background-color: #e9c6fd;
+}
+
+.starwarsffg .item-sheet-modifiers .attribute .block-title {
+ background-color: #2c0344;
+}
+
+.starwarsffg .item-sheet-modifiers .sheet-body {
+ height: calc(100% - 12.7rem);
+ overflow: auto;
+}
+
+.starwarsffg.sheet.item.v2 .item-sheet-modifiers .container.flex-group-center {
+ padding: 0 5px 0 5px;
+ margin-bottom: 0;
+}
+
+.starwarsffg.sheet.item.v2 .item-sheet-modifiers .weapon-values {
+ margin: 0 5px;
+ padding: 0 5px 0 5px;
+}
+
+.starwarsffg.sheet.item.v2 .item-sheet-modifiers .sheet-body {
+ height: calc(100% - 8.6rem);
+}
+
+.starwarsffg .item-sheet-itemattachment .profile-img-field {
+ width: 25%;
+}
+
+.starwarsffg .item-sheet-itemattachment .profile-img-field img.profile-img {
+ width: 100%;
+ height: auto;
+}
+
+.starwarsffg .item-sheet-itemattachment .sheet-body {
+ height: calc(100% - 19rem);
+}
+
+.starwarsffg .item-sheet-itemattachment .sheet-body table th:first-child {
+ text-align: left;
+ padding-left: 3px;
+}
+
+.starwarsffg .item-sheet-itemattachment .sheet-body table td:not(:first-child) {
+ position: relative;
+ text-align: center;
+}
+
+.starwarsffg .item-sheet-itemattachment .sheet-body table td:not(:first-child) .quantity {
+ top: 3px;
+}
+
+.starwarsffg .item-sheet-itemattachment .sheet-body table td .modifier-active i {
+ cursor: pointer;
+}
+
+.starwarsffg .item-sheet-itemattachment .sheet-body table td.add-modifier i {
+ padding: 0 2px;
+ cursor: pointer;
+}
+
+.starwarsffg.sheet.item.v2 .item-sheet-itemattachment .sheet-body {
+ height: calc(100% - 16.5rem);
+}
+
.initiative-dialog .pool-selector {
font-size: 1rem;
text-align: center;
@@ -3015,19 +3239,16 @@ img {
z-index: 1;
}
+#destiny-tracker .dropdown-content.vertical {
+ margin-top: calc(-13vh + 40px);
+}
+
#destiny-tracker .dropdown-content a {
color: white;
- padding: 12px 5px;
+ padding: 5px 4px;
text-decoration: none;
display: block;
-}
-
-#destiny-tracker .dropdown-content a:hover {
- background-color: #ddd;
-}
-
-#destiny-tracker .dropdown:hover .dropdown-content {
- display: block;
+ font-size: 15px;
}
#destiny-tracker .dropdown:hover .dropbtn {
@@ -3036,6 +3257,7 @@ img {
#destiny-tracker .show {
display: block;
+ background-color: #3e8e41;
}
.swffg-destiny-container {
@@ -3092,6 +3314,7 @@ img {
position: relative;
padding: 6px 0 12px 0;
pointer-events: auto;
+ cursor: pointer;
}
.swffg-destiny .destiny-points span {
diff --git a/system.json b/system.json
index b870ab1f..d93dbb09 100644
--- a/system.json
+++ b/system.json
@@ -2,8 +2,8 @@
"name": "starwarsffg",
"title": "Star Wars FFG",
"description": "A system for playing Star Wars FFG games.",
- "version": "1.3",
- "minimumCoreVersion": "0.7.3",
+ "version": "1.4",
+ "minimumCoreVersion": "0.7.9",
"compatibleCoreVersion": "0.7.9",
"templateVersion": 1,
"author": "Esrin, CStadther and Jaxxa",
@@ -40,6 +40,6 @@
"secondaryTokenAttribute": "strain",
"url": "https://github.com/StarWarsFoundryVTT/StarWarsFFG",
"manifest": "https://raw.githubusercontent.com/StarWarsFoundryVTT/StarWarsFFG/master/system.json",
- "download": "https://github.com/StarWarsFoundryVTT/StarWarsFFG/releases/download/v1.3/starwarsffg_1.3.zip",
+ "download": "https://github.com/StarWarsFoundryVTT/StarWarsFFG/releases/download/v1.4/starwarsffg_1.4.zip",
"license": "LICENSE.txt"
}
diff --git a/template.json b/template.json
index d43a1188..1121ac11 100644
--- a/template.json
+++ b/template.json
@@ -518,7 +518,7 @@
},
"Item": {
"types": [
- "armour", "career", "criticaldamage", "criticalinjury", "forcepower", "gear", "talent", "shipattachment", "shipweapon", "signatureability", "specialization", "species", "weapon"],
+ "armour", "career", "criticaldamage", "criticalinjury", "forcepower", "gear", "itemattachment", "itemmodifier", "talent", "shipattachment", "shipweapon", "signatureability", "specialization", "species", "weapon"],
"templates": {
"core": {
"description": "",
@@ -562,13 +562,19 @@
"type": "Boolean",
"equipped": false
}
+ },
+ "itemattachments": {
+ "itemattachment":[]
+ },
+ "qualities" : {
+ "itemmodifier": []
}
},
"gear": {
- "templates": ["core", "basic"]
+ "templates": ["core", "basic", "itemattachments", "qualities"]
},
"weapon": {
- "templates": ["core", "basic", "hardpoints", "equippable"],
+ "templates": ["core", "basic", "hardpoints", "equippable", "itemattachments", "qualities"],
"skill": {
"value": "Ranged: Light",
"type": "String",
@@ -598,7 +604,7 @@
}
},
"armour": {
- "templates": ["core", "basic", "hardpoints", "equippable"],
+ "templates": ["core", "basic", "hardpoints", "equippable", "itemattachments", "qualities"],
"defence": {
"value": 0,
"type": "Number",
@@ -629,7 +635,7 @@
"trees": []
},
"shipweapon": {
- "templates": ["core", "basic", "hardpoints", "equippable"],
+ "templates": ["core", "basic", "hardpoints", "equippable", "itemattachments", "qualities"],
"label": "Ship Weapon",
"firingarc": {
"fore": false,
@@ -663,7 +669,7 @@
}
},
"shipattachment": {
- "templates": ["core", "basic", "hardpoints", "equippable"],
+ "templates": ["core", "basic", "hardpoints", "equippable", "itemattachments", "qualities"],
"label": "Ship Attachment"
},
"species": {
@@ -742,6 +748,15 @@
"upgrade6": {},
"upgrade7": {}
}
+ },
+ "itemattachment": {
+ "templates": ["core", "basic", "hardpoints", "qualities"],
+ "type": ""
+ },
+ "itemmodifier": {
+ "templates": ["core"],
+ "type": "",
+ "rank": 0
}
}
}
diff --git a/templates/actors/ffg-character-sheet.html b/templates/actors/ffg-character-sheet.html
index a999bb9a..367c0efc 100644
--- a/templates/actors/ffg-character-sheet.html
+++ b/templates/actors/ffg-character-sheet.html
@@ -63,7 +63,7 @@
"systems/starwarsffg/templates/parts/shared/ffg-tabs.html" displayLimited=true limited=limited items=(array (object tab="characteristics" label="SWFFG.TabCharacteristics" icon="fas fa-user-circle" cls=classType) (object tab="items" label="SWFFG.TabGear" icon="fas fa-toolbox" cls=classType) (object tab="talents" label="SWFFG.TabTalents" icon="fab fa-superpowers" cls=classType) (object tab="general" label="SWFFG.TabGeneral" icon="fas fa-address-card" cls=classType isHidden=true) (object tab="description" label="SWFFG.TabBiography" icon="fas fa-sticky-note" cls=classType) (object tab="obligation" label="SWFFG.TabObligationDutyMorality" icon="fas fa-balance-scale" cls=classType isHidden=true isHiddenV2=hideObligationDutyMoralityConflictTab) )}} {{!-- Sheet Body --}}
{{!-- Characteristics Tab --}}
{{!-- Modifiers Tab --}}
diff --git a/templates/actors/ffg-vehicle-sheet.html b/templates/actors/ffg-vehicle-sheet.html
index daeecff9..5e71cebb 100644
--- a/templates/actors/ffg-vehicle-sheet.html
+++ b/templates/actors/ffg-vehicle-sheet.html
@@ -4,7 +4,7 @@